// SPDX-License-Identifier: GPL-2.0 /* Airoha AN7583 MDIO interface driver * * Copyright (C) 2025 Christian Marangi */ #include #include #include #include #include #include #include #include #include #include /* MII address register definitions */ #define AN7583_MII_BUSY BIT(31) #define AN7583_MII_RDY BIT(30) /* RO signal BUS is ready */ #define AN7583_MII_CL22_REG_ADDR GENMASK(29, 25) #define AN7583_MII_CL45_DEV_ADDR AN7583_MII_CL22_REG_ADDR #define AN7583_MII_PHY_ADDR GENMASK(24, 20) #define AN7583_MII_CMD GENMASK(19, 18) #define AN7583_MII_CMD_CL22_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) #define AN7583_MII_CMD_CL22_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) #define AN7583_MII_CMD_CL45_ADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x0) #define AN7583_MII_CMD_CL45_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) #define AN7583_MII_CMD_CL45_POSTREAD_INCADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) #define AN7583_MII_CMD_CL45_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x3) #define AN7583_MII_ST GENMASK(17, 16) #define AN7583_MII_ST_CL45 FIELD_PREP_CONST(AN7583_MII_ST, 0x0) #define AN7583_MII_ST_CL22 FIELD_PREP_CONST(AN7583_MII_ST, 0x1) #define AN7583_MII_RWDATA GENMASK(15, 0) #define AN7583_MII_CL45_REG_ADDR AN7583_MII_RWDATA #define AN7583_MII_MDIO_DELAY_USEC 100 #define AN7583_MII_MDIO_RETRY_MSEC 100 struct airoha_mdio_data { u32 base_addr; struct regmap *regmap; struct clk *clk; struct reset_control *reset; }; static int airoha_mdio_wait_busy(struct airoha_mdio_data *priv) { u32 busy; return regmap_read_poll_timeout(priv->regmap, priv->base_addr, busy, !(busy & AN7583_MII_BUSY), AN7583_MII_MDIO_DELAY_USEC, AN7583_MII_MDIO_RETRY_MSEC * USEC_PER_MSEC); } static void airoha_mdio_reset(struct airoha_mdio_data *priv) { /* There seems to be Hardware bug where AN7583_MII_RWDATA * is not wiped in the context of unconnected PHY and the * previous read value is returned. * * Example: (only one PHY on the BUS at 0x1f) * - read at 0x1f report at 0x2 0x7500 * - read at 0x0 report 0x7500 on every address * * To workaround this, we reset the Mdio BUS at every read * to have consistent values on read operation. */ reset_control_assert(priv->reset); reset_control_deassert(priv->reset); } static int airoha_mdio_read(struct mii_bus *bus, int addr, int regnum) { struct airoha_mdio_data *priv = bus->priv; u32 val; int ret; airoha_mdio_reset(priv); val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | AN7583_MII_CMD_CL22_READ; val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); ret = regmap_write(priv->regmap, priv->base_addr, val); if (ret) return ret; ret = airoha_mdio_wait_busy(priv); if (ret) return ret; ret = regmap_read(priv->regmap, priv->base_addr, &val); if (ret) return ret; return FIELD_GET(AN7583_MII_RWDATA, val); } static int airoha_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 value) { struct airoha_mdio_data *priv = bus->priv; u32 val; int ret; val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | AN7583_MII_CMD_CL22_WRITE; val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); val |= FIELD_PREP(AN7583_MII_RWDATA, value); ret = regmap_write(priv->regmap, priv->base_addr, val); if (ret) return ret; ret = airoha_mdio_wait_busy(priv); return ret; } static int airoha_mdio_cl45_read(struct mii_bus *bus, int addr, int devnum, int regnum) { struct airoha_mdio_data *priv = bus->priv; u32 val; int ret; airoha_mdio_reset(priv); val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | AN7583_MII_CMD_CL45_ADDR; val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); ret = regmap_write(priv->regmap, priv->base_addr, val); if (ret) return ret; ret = airoha_mdio_wait_busy(priv); if (ret) return ret; val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | AN7583_MII_CMD_CL45_READ; val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); ret = regmap_write(priv->regmap, priv->base_addr, val); if (ret) return ret; ret = airoha_mdio_wait_busy(priv); if (ret) return ret; ret = regmap_read(priv->regmap, priv->base_addr, &val); if (ret) return ret; return FIELD_GET(AN7583_MII_RWDATA, val); } static int airoha_mdio_cl45_write(struct mii_bus *bus, int addr, int devnum, int regnum, u16 value) { struct airoha_mdio_data *priv = bus->priv; u32 val; int ret; val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | AN7583_MII_CMD_CL45_ADDR; val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); ret = regmap_write(priv->regmap, priv->base_addr, val); if (ret) return ret; ret = airoha_mdio_wait_busy(priv); if (ret) return ret; val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | AN7583_MII_CMD_CL45_WRITE; val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); val |= FIELD_PREP(AN7583_MII_RWDATA, value); ret = regmap_write(priv->regmap, priv->base_addr, val); if (ret) return ret; ret = airoha_mdio_wait_busy(priv); return ret; } static int airoha_mdio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct airoha_mdio_data *priv; struct mii_bus *bus; u32 addr, freq; int ret; ret = of_property_read_u32(dev->of_node, "reg", &addr); if (ret) return ret; bus = devm_mdiobus_alloc_size(dev, sizeof(*priv)); if (!bus) return -ENOMEM; priv = bus->priv; priv->base_addr = addr; priv->regmap = device_node_to_regmap(dev->parent->of_node); priv->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); priv->reset = devm_reset_control_get_exclusive(dev, NULL); if (IS_ERR(priv->reset)) return PTR_ERR(priv->reset); reset_control_deassert(priv->reset); bus->name = "airoha_mdio_bus"; snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); bus->parent = dev; bus->read = airoha_mdio_read; bus->write = airoha_mdio_write; bus->read_c45 = airoha_mdio_cl45_read; bus->write_c45 = airoha_mdio_cl45_write; /* Check if a custom frequency is defined in DT or default to 2.5 MHz */ if (of_property_read_u32(dev->of_node, "clock-frequency", &freq)) freq = 2500000; ret = clk_set_rate(priv->clk, freq); if (ret) return ret; ret = devm_of_mdiobus_register(dev, bus, dev->of_node); if (ret) { reset_control_assert(priv->reset); return ret; } return 0; } static const struct of_device_id airoha_mdio_dt_ids[] = { { .compatible = "airoha,an7583-mdio" }, { } }; MODULE_DEVICE_TABLE(of, airoha_mdio_dt_ids); static struct platform_driver airoha_mdio_driver = { .probe = airoha_mdio_probe, .driver = { .name = "airoha-mdio", .of_match_table = airoha_mdio_dt_ids, }, }; module_platform_driver(airoha_mdio_driver); MODULE_DESCRIPTION("Airoha AN7583 MDIO interface driver"); MODULE_AUTHOR("Christian Marangi "); MODULE_LICENSE("GPL");