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
airoha_mdio_wait_busy(struct airoha_mdio_data * priv)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
airoha_mdio_reset(struct airoha_mdio_data * priv)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
airoha_mdio_read(struct mii_bus * bus,int addr,int regnum)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
airoha_mdio_write(struct mii_bus * bus,int addr,int regnum,u16 value)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
airoha_mdio_cl45_read(struct mii_bus * bus,int addr,int devnum,int regnum)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
airoha_mdio_cl45_write(struct mii_bus * bus,int addr,int devnum,int regnum,u16 value)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
airoha_mdio_probe(struct platform_device * pdev)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