// SPDX-License-Identifier: GPL-2.0
/* Copyright 2021 NXP
 */
#include <linux/pcs/pcs-xpcs.h>
#include <linux/of_mdio.h>
#include "sja1105.h"

#define SJA1110_PCS_BANK_REG		SJA1110_SPI_ADDR(0x3fc)

int sja1105_pcs_mdio_read(struct mii_bus *bus, int phy, int reg)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	u64 addr;
	u32 tmp;
	u16 mmd;
	int rc;

	if (!(reg & MII_ADDR_C45))
		return -EINVAL;

	mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;
	addr = (mmd << 16) | (reg & GENMASK(15, 0));

	if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
		return 0xffff;

	if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID1)
		return NXP_SJA1105_XPCS_ID >> 16;
	if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID2)
		return NXP_SJA1105_XPCS_ID & GENMASK(15, 0);

	rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
	if (rc < 0)
		return rc;

	return tmp & 0xffff;
}

int sja1105_pcs_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	u64 addr;
	u32 tmp;
	u16 mmd;

	if (!(reg & MII_ADDR_C45))
		return -EINVAL;

	mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;
	addr = (mmd << 16) | (reg & GENMASK(15, 0));
	tmp = val;

	if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
		return -EINVAL;

	return sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
}

int sja1110_pcs_mdio_read(struct mii_bus *bus, int phy, int reg)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	const struct sja1105_regs *regs = priv->info->regs;
	int offset, bank;
	u64 addr;
	u32 tmp;
	u16 mmd;
	int rc;

	if (!(reg & MII_ADDR_C45))
		return -EINVAL;

	if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
		return -ENODEV;

	mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;
	addr = (mmd << 16) | (reg & GENMASK(15, 0));

	if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID1)
		return NXP_SJA1110_XPCS_ID >> 16;
	if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID2)
		return NXP_SJA1110_XPCS_ID & GENMASK(15, 0);

	bank = addr >> 8;
	offset = addr & GENMASK(7, 0);

	/* This addressing scheme reserves register 0xff for the bank address
	 * register, so that can never be addressed.
	 */
	if (WARN_ON(offset == 0xff))
		return -ENODEV;

	tmp = bank;

	rc = sja1105_xfer_u32(priv, SPI_WRITE,
			      regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
			      &tmp, NULL);
	if (rc < 0)
		return rc;

	rc = sja1105_xfer_u32(priv, SPI_READ, regs->pcs_base[phy] + offset,
			      &tmp, NULL);
	if (rc < 0)
		return rc;

	return tmp & 0xffff;
}

int sja1110_pcs_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	const struct sja1105_regs *regs = priv->info->regs;
	int offset, bank;
	u64 addr;
	u32 tmp;
	u16 mmd;
	int rc;

	if (!(reg & MII_ADDR_C45))
		return -EINVAL;

	if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
		return -ENODEV;

	mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;
	addr = (mmd << 16) | (reg & GENMASK(15, 0));

	bank = addr >> 8;
	offset = addr & GENMASK(7, 0);

	/* This addressing scheme reserves register 0xff for the bank address
	 * register, so that can never be addressed.
	 */
	if (WARN_ON(offset == 0xff))
		return -ENODEV;

	tmp = bank;

	rc = sja1105_xfer_u32(priv, SPI_WRITE,
			      regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
			      &tmp, NULL);
	if (rc < 0)
		return rc;

	tmp = val;

	return sja1105_xfer_u32(priv, SPI_WRITE, regs->pcs_base[phy] + offset,
				&tmp, NULL);
}

enum sja1105_mdio_opcode {
	SJA1105_C45_ADDR = 0,
	SJA1105_C22 = 1,
	SJA1105_C45_DATA = 2,
	SJA1105_C45_DATA_AUTOINC = 3,
};

static u64 sja1105_base_t1_encode_addr(struct sja1105_private *priv,
				       int phy, enum sja1105_mdio_opcode op,
				       int xad)
{
	const struct sja1105_regs *regs = priv->info->regs;

	return regs->mdio_100base_t1 | (phy << 7) | (op << 5) | (xad << 0);
}

static int sja1105_base_t1_mdio_read(struct mii_bus *bus, int phy, int reg)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	u64 addr;
	u32 tmp;
	int rc;

	if (reg & MII_ADDR_C45) {
		u16 mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;

		addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_ADDR,
						   mmd);

		tmp = reg & MII_REGADDR_C45_MASK;

		rc = sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
		if (rc < 0)
			return rc;

		addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_DATA,
						   mmd);

		rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
		if (rc < 0)
			return rc;

		return tmp & 0xffff;
	}

	/* Clause 22 read */
	addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C22, reg & 0x1f);

	rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
	if (rc < 0)
		return rc;

	return tmp & 0xffff;
}

static int sja1105_base_t1_mdio_write(struct mii_bus *bus, int phy, int reg,
				      u16 val)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	u64 addr;
	u32 tmp;
	int rc;

	if (reg & MII_ADDR_C45) {
		u16 mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;

		addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_ADDR,
						   mmd);

		tmp = reg & MII_REGADDR_C45_MASK;

		rc = sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
		if (rc < 0)
			return rc;

		addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_DATA,
						   mmd);

		tmp = val & 0xffff;

		rc = sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
		if (rc < 0)
			return rc;

		return 0;
	}

	/* Clause 22 write */
	addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C22, reg & 0x1f);

	tmp = val & 0xffff;

	return sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
}

static int sja1105_base_tx_mdio_read(struct mii_bus *bus, int phy, int reg)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	const struct sja1105_regs *regs = priv->info->regs;
	u32 tmp;
	int rc;

	if (reg & MII_ADDR_C45)
		return -EOPNOTSUPP;

	rc = sja1105_xfer_u32(priv, SPI_READ, regs->mdio_100base_tx + reg,
			      &tmp, NULL);
	if (rc < 0)
		return rc;

	return tmp & 0xffff;
}

static int sja1105_base_tx_mdio_write(struct mii_bus *bus, int phy, int reg,
				      u16 val)
{
	struct sja1105_mdio_private *mdio_priv = bus->priv;
	struct sja1105_private *priv = mdio_priv->priv;
	const struct sja1105_regs *regs = priv->info->regs;
	u32 tmp = val;

	if (reg & MII_ADDR_C45)
		return -EOPNOTSUPP;

	return sja1105_xfer_u32(priv, SPI_WRITE, regs->mdio_100base_tx + reg,
				&tmp, NULL);
}

static int sja1105_mdiobus_base_tx_register(struct sja1105_private *priv,
					    struct device_node *mdio_node)
{
	struct sja1105_mdio_private *mdio_priv;
	struct device_node *np;
	struct mii_bus *bus;
	int rc = 0;

	np = of_get_compatible_child(mdio_node, "nxp,sja1110-base-tx-mdio");
	if (!np)
		return 0;

	if (!of_device_is_available(np))
		goto out_put_np;

	bus = mdiobus_alloc_size(sizeof(*mdio_priv));
	if (!bus) {
		rc = -ENOMEM;
		goto out_put_np;
	}

	bus->name = "SJA1110 100base-TX MDIO bus";
	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-base-tx",
		 dev_name(priv->ds->dev));
	bus->read = sja1105_base_tx_mdio_read;
	bus->write = sja1105_base_tx_mdio_write;
	bus->parent = priv->ds->dev;
	mdio_priv = bus->priv;
	mdio_priv->priv = priv;

	rc = of_mdiobus_register(bus, np);
	if (rc) {
		mdiobus_free(bus);
		goto out_put_np;
	}

	priv->mdio_base_tx = bus;

out_put_np:
	of_node_put(np);

	return rc;
}

static void sja1105_mdiobus_base_tx_unregister(struct sja1105_private *priv)
{
	if (!priv->mdio_base_tx)
		return;

	mdiobus_unregister(priv->mdio_base_tx);
	mdiobus_free(priv->mdio_base_tx);
	priv->mdio_base_tx = NULL;
}

static int sja1105_mdiobus_base_t1_register(struct sja1105_private *priv,
					    struct device_node *mdio_node)
{
	struct sja1105_mdio_private *mdio_priv;
	struct device_node *np;
	struct mii_bus *bus;
	int rc = 0;

	np = of_get_compatible_child(mdio_node, "nxp,sja1110-base-t1-mdio");
	if (!np)
		return 0;

	if (!of_device_is_available(np))
		goto out_put_np;

	bus = mdiobus_alloc_size(sizeof(*mdio_priv));
	if (!bus) {
		rc = -ENOMEM;
		goto out_put_np;
	}

	bus->name = "SJA1110 100base-T1 MDIO bus";
	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-base-t1",
		 dev_name(priv->ds->dev));
	bus->read = sja1105_base_t1_mdio_read;
	bus->write = sja1105_base_t1_mdio_write;
	bus->parent = priv->ds->dev;
	mdio_priv = bus->priv;
	mdio_priv->priv = priv;

	rc = of_mdiobus_register(bus, np);
	if (rc) {
		mdiobus_free(bus);
		goto out_put_np;
	}

	priv->mdio_base_t1 = bus;

out_put_np:
	of_node_put(np);

	return rc;
}

static void sja1105_mdiobus_base_t1_unregister(struct sja1105_private *priv)
{
	if (!priv->mdio_base_t1)
		return;

	mdiobus_unregister(priv->mdio_base_t1);
	mdiobus_free(priv->mdio_base_t1);
	priv->mdio_base_t1 = NULL;
}

static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
{
	struct sja1105_mdio_private *mdio_priv;
	struct dsa_switch *ds = priv->ds;
	struct mii_bus *bus;
	int rc = 0;
	int port;

	if (!priv->info->pcs_mdio_read || !priv->info->pcs_mdio_write)
		return 0;

	bus = mdiobus_alloc_size(sizeof(*mdio_priv));
	if (!bus)
		return -ENOMEM;

	bus->name = "SJA1105 PCS MDIO bus";
	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs",
		 dev_name(ds->dev));
	bus->read = priv->info->pcs_mdio_read;
	bus->write = priv->info->pcs_mdio_write;
	bus->parent = ds->dev;
	/* There is no PHY on this MDIO bus => mask out all PHY addresses
	 * from auto probing.
	 */
	bus->phy_mask = ~0;
	mdio_priv = bus->priv;
	mdio_priv->priv = priv;

	rc = mdiobus_register(bus);
	if (rc) {
		mdiobus_free(bus);
		return rc;
	}

	for (port = 0; port < ds->num_ports; port++) {
		struct mdio_device *mdiodev;
		struct dw_xpcs *xpcs;

		if (dsa_is_unused_port(ds, port))
			continue;

		if (priv->phy_mode[port] != PHY_INTERFACE_MODE_SGMII &&
		    priv->phy_mode[port] != PHY_INTERFACE_MODE_2500BASEX)
			continue;

		mdiodev = mdio_device_create(bus, port);
		if (IS_ERR(mdiodev)) {
			rc = PTR_ERR(mdiodev);
			goto out_pcs_free;
		}

		xpcs = xpcs_create(mdiodev, priv->phy_mode[port]);
		if (IS_ERR(xpcs)) {
			rc = PTR_ERR(xpcs);
			goto out_pcs_free;
		}

		priv->xpcs[port] = xpcs;
	}

	priv->mdio_pcs = bus;

	return 0;

out_pcs_free:
	for (port = 0; port < ds->num_ports; port++) {
		if (!priv->xpcs[port])
			continue;

		mdio_device_free(priv->xpcs[port]->mdiodev);
		xpcs_destroy(priv->xpcs[port]);
		priv->xpcs[port] = NULL;
	}

	mdiobus_unregister(bus);
	mdiobus_free(bus);

	return rc;
}

static void sja1105_mdiobus_pcs_unregister(struct sja1105_private *priv)
{
	struct dsa_switch *ds = priv->ds;
	int port;

	if (!priv->mdio_pcs)
		return;

	for (port = 0; port < ds->num_ports; port++) {
		if (!priv->xpcs[port])
			continue;

		mdio_device_free(priv->xpcs[port]->mdiodev);
		xpcs_destroy(priv->xpcs[port]);
		priv->xpcs[port] = NULL;
	}

	mdiobus_unregister(priv->mdio_pcs);
	mdiobus_free(priv->mdio_pcs);
	priv->mdio_pcs = NULL;
}

int sja1105_mdiobus_register(struct dsa_switch *ds)
{
	struct sja1105_private *priv = ds->priv;
	const struct sja1105_regs *regs = priv->info->regs;
	struct device_node *switch_node = ds->dev->of_node;
	struct device_node *mdio_node;
	int rc;

	rc = sja1105_mdiobus_pcs_register(priv);
	if (rc)
		return rc;

	mdio_node = of_get_child_by_name(switch_node, "mdios");
	if (!mdio_node)
		return 0;

	if (!of_device_is_available(mdio_node))
		goto out_put_mdio_node;

	if (regs->mdio_100base_tx != SJA1105_RSV_ADDR) {
		rc = sja1105_mdiobus_base_tx_register(priv, mdio_node);
		if (rc)
			goto err_put_mdio_node;
	}

	if (regs->mdio_100base_t1 != SJA1105_RSV_ADDR) {
		rc = sja1105_mdiobus_base_t1_register(priv, mdio_node);
		if (rc)
			goto err_free_base_tx_mdiobus;
	}

out_put_mdio_node:
	of_node_put(mdio_node);

	return 0;

err_free_base_tx_mdiobus:
	sja1105_mdiobus_base_tx_unregister(priv);
err_put_mdio_node:
	of_node_put(mdio_node);
	sja1105_mdiobus_pcs_unregister(priv);

	return rc;
}

void sja1105_mdiobus_unregister(struct dsa_switch *ds)
{
	struct sja1105_private *priv = ds->priv;

	sja1105_mdiobus_base_t1_unregister(priv);
	sja1105_mdiobus_base_tx_unregister(priv);
	sja1105_mdiobus_pcs_unregister(priv);
}
