xref: /linux/drivers/net/mdio/mdio-airoha.c (revision 566e8f108fc7847f2a8676ec6a101d37b7dd0fb4)
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