Loading...
// SPDX-License-Identifier: GPL-2.0+

#include <asm/io.h>
#include <dm.h>
#include <time.h>
#include <regmap.h>
#include <miiphy.h>
#include <linux/bitfield.h>

#define MSCC_MIIM_REG_STATUS		0x0
#define		MSCC_MIIM_STATUS_STAT_PENDING	BIT(2)
#define		MSCC_MIIM_STATUS_STAT_BUSY	BIT(3)
#define MSCC_MIIM_REG_CMD		0x8
#define		MSCC_MIIM_CMD_OPR_WRITE		BIT(1)
#define		MSCC_MIIM_CMD_OPR_READ		BIT(2)
#define		MSCC_MIIM_CMD_WRDATA_SHIFT	4
#define		MSCC_MIIM_CMD_REGAD_SHIFT	20
#define		MSCC_MIIM_CMD_PHYAD_SHIFT	25
#define		MSCC_MIIM_CMD_VLD		BIT(31)
#define MSCC_MIIM_REG_DATA		0xC
#define		MSCC_MIIM_DATA_ERROR		(BIT(16) | BIT(17))
#define		MSCC_MIIM_DATA_MASK		GENMASK(15, 0)
#define MSCC_MIIM_REG_CFG		0x10
#define		MSCC_MIIM_CFG_PRESCALE_MASK	GENMASK(7, 0)
/* 01 = Clause 22, 00 = Clause 45 */
#define		MSCC_MIIM_CFG_ST_CFG_MASK	GENMASK(10, 9)
#define		MSCC_MIIM_C22			1
#define		MSCC_MIIM_C45			0

#define MSCC_MDIO_TIMEOUT    10000
#define MSCC_MDIO_SLEEP      50

struct mscc_mdio_priv {
	struct regmap *map;
};

static int mscc_mdio_wait_busy(struct mscc_mdio_priv *priv)
{
	u32 busy;

	return regmap_read_poll_timeout(priv->map, MSCC_MIIM_REG_STATUS, busy,
				       (busy & MSCC_MIIM_STATUS_STAT_BUSY) == 0,
				       MSCC_MDIO_SLEEP,
				       MSCC_MDIO_TIMEOUT);
}

static int mscc_mdio_read(struct udevice *dev, int addr, int devad, int reg)
{
	struct mscc_mdio_priv *priv = dev_get_priv(dev);
	u32 val;
	int ret;

	if (mscc_mdio_wait_busy(priv))
		return -ETIMEDOUT;

	ret = regmap_write(priv->map, MSCC_MIIM_REG_CMD,
			   MSCC_MIIM_CMD_VLD |
			   (addr << MSCC_MIIM_CMD_PHYAD_SHIFT) |
			   (reg << MSCC_MIIM_CMD_REGAD_SHIFT) |
			   MSCC_MIIM_CMD_OPR_READ);
	if (ret)
		return ret;

	if (mscc_mdio_wait_busy(priv))
		return -ETIMEDOUT;

	regmap_read(priv->map, MSCC_MIIM_REG_DATA, &val);
	if (val & MSCC_MIIM_DATA_ERROR)
		return -EIO;

	return FIELD_GET(MSCC_MIIM_DATA_MASK, val);
}

int mscc_mdio_write(struct udevice *dev, int addr, int devad, int reg, u16 val)
{
	struct mscc_mdio_priv *priv = dev_get_priv(dev);
	int ret;

	if (mscc_mdio_wait_busy(priv))
		return -ETIMEDOUT;

	ret = regmap_write(priv->map, MSCC_MIIM_REG_CMD,
			   MSCC_MIIM_CMD_VLD |
			   (addr << MSCC_MIIM_CMD_PHYAD_SHIFT) |
			   (reg << MSCC_MIIM_CMD_REGAD_SHIFT) |
			   (val << MSCC_MIIM_CMD_WRDATA_SHIFT) |
			   MSCC_MIIM_CMD_OPR_WRITE);

	return ret;
}

static const struct mdio_ops mscc_mdio_ops = {
	.read = mscc_mdio_read,
	.write = mscc_mdio_write,
};

static int mscc_mdio_bind(struct udevice *dev)
{
	if (ofnode_valid(dev_ofnode(dev)))
		device_set_name(dev, ofnode_get_name(dev_ofnode(dev)));

	return 0;
}

static int mscc_mdio_probe(struct udevice *dev)
{
	struct mscc_mdio_priv *priv = dev_get_priv(dev);
	int ret;

	ret = regmap_init_mem(dev_ofnode(dev), &priv->map);
	if (ret)
		return -EINVAL;

	/* Enter Clause 22 mode */
	ret = regmap_update_bits(priv->map, MSCC_MIIM_REG_CFG,
				 MSCC_MIIM_CFG_ST_CFG_MASK,
				 FIELD_PREP(MSCC_MIIM_CFG_ST_CFG_MASK,
					    MSCC_MIIM_C22));

	return ret;
}

static const struct udevice_id mscc_mdio_ids[] = {
	{ .compatible = "mscc,ocelot-miim", },
	{ }
};

U_BOOT_DRIVER(mscc_mdio) = {
	.name           = "mscc_mdio",
	.id             = UCLASS_MDIO,
	.of_match       = mscc_mdio_ids,
	.bind           = mscc_mdio_bind,
	.probe          = mscc_mdio_probe,
	.ops            = &mscc_mdio_ops,
	.priv_auto	  = sizeof(struct mscc_mdio_priv),
};