1 // SPDX-License-Identifier: GPL-2.0 2 /* Airoha AN7583 MDIO interface driver 3 * 4 * Copyright (C) 2025 Christian Marangi <ansuelsmth@gmail.com> 5 */ 6 7 #include <linux/clk.h> 8 #include <linux/delay.h> 9 #include <linux/kernel.h> 10 #include <linux/mfd/syscon.h> 11 #include <linux/module.h> 12 #include <linux/of_mdio.h> 13 #include <linux/of_address.h> 14 #include <linux/platform_device.h> 15 #include <linux/regmap.h> 16 #include <linux/reset.h> 17 18 /* MII address register definitions */ 19 #define AN7583_MII_BUSY BIT(31) 20 #define AN7583_MII_RDY BIT(30) /* RO signal BUS is ready */ 21 #define AN7583_MII_CL22_REG_ADDR GENMASK(29, 25) 22 #define AN7583_MII_CL45_DEV_ADDR AN7583_MII_CL22_REG_ADDR 23 #define AN7583_MII_PHY_ADDR GENMASK(24, 20) 24 #define AN7583_MII_CMD GENMASK(19, 18) 25 #define AN7583_MII_CMD_CL22_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) 26 #define AN7583_MII_CMD_CL22_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) 27 #define AN7583_MII_CMD_CL45_ADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x0) 28 #define AN7583_MII_CMD_CL45_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) 29 #define AN7583_MII_CMD_CL45_POSTREAD_INCADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) 30 #define AN7583_MII_CMD_CL45_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x3) 31 #define AN7583_MII_ST GENMASK(17, 16) 32 #define AN7583_MII_ST_CL45 FIELD_PREP_CONST(AN7583_MII_ST, 0x0) 33 #define AN7583_MII_ST_CL22 FIELD_PREP_CONST(AN7583_MII_ST, 0x1) 34 #define AN7583_MII_RWDATA GENMASK(15, 0) 35 #define AN7583_MII_CL45_REG_ADDR AN7583_MII_RWDATA 36 37 #define AN7583_MII_MDIO_DELAY_USEC 100 38 #define AN7583_MII_MDIO_RETRY_MSEC 100 39 40 struct airoha_mdio_data { 41 u32 base_addr; 42 struct regmap *regmap; 43 struct clk *clk; 44 struct reset_control *reset; 45 }; 46 47 static int airoha_mdio_wait_busy(struct airoha_mdio_data *priv) 48 { 49 u32 busy; 50 51 return regmap_read_poll_timeout(priv->regmap, priv->base_addr, busy, 52 !(busy & AN7583_MII_BUSY), 53 AN7583_MII_MDIO_DELAY_USEC, 54 AN7583_MII_MDIO_RETRY_MSEC * USEC_PER_MSEC); 55 } 56 57 static void airoha_mdio_reset(struct airoha_mdio_data *priv) 58 { 59 /* There seems to be Hardware bug where AN7583_MII_RWDATA 60 * is not wiped in the context of unconnected PHY and the 61 * previous read value is returned. 62 * 63 * Example: (only one PHY on the BUS at 0x1f) 64 * - read at 0x1f report at 0x2 0x7500 65 * - read at 0x0 report 0x7500 on every address 66 * 67 * To workaround this, we reset the Mdio BUS at every read 68 * to have consistent values on read operation. 69 */ 70 reset_control_assert(priv->reset); 71 reset_control_deassert(priv->reset); 72 } 73 74 static int airoha_mdio_read(struct mii_bus *bus, int addr, int regnum) 75 { 76 struct airoha_mdio_data *priv = bus->priv; 77 u32 val; 78 int ret; 79 80 airoha_mdio_reset(priv); 81 82 val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | 83 AN7583_MII_CMD_CL22_READ; 84 val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); 85 val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); 86 87 ret = regmap_write(priv->regmap, priv->base_addr, val); 88 if (ret) 89 return ret; 90 91 ret = airoha_mdio_wait_busy(priv); 92 if (ret) 93 return ret; 94 95 ret = regmap_read(priv->regmap, priv->base_addr, &val); 96 if (ret) 97 return ret; 98 99 return FIELD_GET(AN7583_MII_RWDATA, val); 100 } 101 102 static int airoha_mdio_write(struct mii_bus *bus, int addr, int regnum, 103 u16 value) 104 { 105 struct airoha_mdio_data *priv = bus->priv; 106 u32 val; 107 int ret; 108 109 val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | 110 AN7583_MII_CMD_CL22_WRITE; 111 val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); 112 val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); 113 val |= FIELD_PREP(AN7583_MII_RWDATA, value); 114 115 ret = regmap_write(priv->regmap, priv->base_addr, val); 116 if (ret) 117 return ret; 118 119 ret = airoha_mdio_wait_busy(priv); 120 121 return ret; 122 } 123 124 static int airoha_mdio_cl45_read(struct mii_bus *bus, int addr, int devnum, 125 int regnum) 126 { 127 struct airoha_mdio_data *priv = bus->priv; 128 u32 val; 129 int ret; 130 131 airoha_mdio_reset(priv); 132 133 val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | 134 AN7583_MII_CMD_CL45_ADDR; 135 val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); 136 val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); 137 val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); 138 139 ret = regmap_write(priv->regmap, priv->base_addr, val); 140 if (ret) 141 return ret; 142 143 ret = airoha_mdio_wait_busy(priv); 144 if (ret) 145 return ret; 146 147 val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | 148 AN7583_MII_CMD_CL45_READ; 149 val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); 150 val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); 151 152 ret = regmap_write(priv->regmap, priv->base_addr, val); 153 if (ret) 154 return ret; 155 156 ret = airoha_mdio_wait_busy(priv); 157 if (ret) 158 return ret; 159 160 ret = regmap_read(priv->regmap, priv->base_addr, &val); 161 if (ret) 162 return ret; 163 164 return FIELD_GET(AN7583_MII_RWDATA, val); 165 } 166 167 static int airoha_mdio_cl45_write(struct mii_bus *bus, int addr, int devnum, 168 int regnum, u16 value) 169 { 170 struct airoha_mdio_data *priv = bus->priv; 171 u32 val; 172 int ret; 173 174 val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | 175 AN7583_MII_CMD_CL45_ADDR; 176 val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); 177 val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); 178 val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); 179 180 ret = regmap_write(priv->regmap, priv->base_addr, val); 181 if (ret) 182 return ret; 183 184 ret = airoha_mdio_wait_busy(priv); 185 if (ret) 186 return ret; 187 188 val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | 189 AN7583_MII_CMD_CL45_WRITE; 190 val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); 191 val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); 192 val |= FIELD_PREP(AN7583_MII_RWDATA, value); 193 194 ret = regmap_write(priv->regmap, priv->base_addr, val); 195 if (ret) 196 return ret; 197 198 ret = airoha_mdio_wait_busy(priv); 199 200 return ret; 201 } 202 203 static int airoha_mdio_probe(struct platform_device *pdev) 204 { 205 struct device *dev = &pdev->dev; 206 struct airoha_mdio_data *priv; 207 struct mii_bus *bus; 208 u32 addr, freq; 209 int ret; 210 211 ret = of_property_read_u32(dev->of_node, "reg", &addr); 212 if (ret) 213 return ret; 214 215 bus = devm_mdiobus_alloc_size(dev, sizeof(*priv)); 216 if (!bus) 217 return -ENOMEM; 218 219 priv = bus->priv; 220 priv->base_addr = addr; 221 priv->regmap = device_node_to_regmap(dev->parent->of_node); 222 223 priv->clk = devm_clk_get_enabled(dev, NULL); 224 if (IS_ERR(priv->clk)) 225 return PTR_ERR(priv->clk); 226 227 priv->reset = devm_reset_control_get_exclusive(dev, NULL); 228 if (IS_ERR(priv->reset)) 229 return PTR_ERR(priv->reset); 230 231 reset_control_deassert(priv->reset); 232 233 bus->name = "airoha_mdio_bus"; 234 snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); 235 bus->parent = dev; 236 bus->read = airoha_mdio_read; 237 bus->write = airoha_mdio_write; 238 bus->read_c45 = airoha_mdio_cl45_read; 239 bus->write_c45 = airoha_mdio_cl45_write; 240 241 /* Check if a custom frequency is defined in DT or default to 2.5 MHz */ 242 if (of_property_read_u32(dev->of_node, "clock-frequency", &freq)) 243 freq = 2500000; 244 245 ret = clk_set_rate(priv->clk, freq); 246 if (ret) 247 return ret; 248 249 ret = devm_of_mdiobus_register(dev, bus, dev->of_node); 250 if (ret) { 251 reset_control_assert(priv->reset); 252 return ret; 253 } 254 255 return 0; 256 } 257 258 static const struct of_device_id airoha_mdio_dt_ids[] = { 259 { .compatible = "airoha,an7583-mdio" }, 260 { } 261 }; 262 MODULE_DEVICE_TABLE(of, airoha_mdio_dt_ids); 263 264 static struct platform_driver airoha_mdio_driver = { 265 .probe = airoha_mdio_probe, 266 .driver = { 267 .name = "airoha-mdio", 268 .of_match_table = airoha_mdio_dt_ids, 269 }, 270 }; 271 272 module_platform_driver(airoha_mdio_driver); 273 274 MODULE_DESCRIPTION("Airoha AN7583 MDIO interface driver"); 275 MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); 276 MODULE_LICENSE("GPL"); 277