File Index
Files and directories
Sign in
FILE

mdio-airoha.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/mdio/mdio-airoha.c C
  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
 40struct airoha_mdio_data {
 41	u32 base_addr;
 42	struct regmap *regmap;
 43	struct clk *clk;
 44	struct reset_control *reset;
 45};
 46
 47static 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
 57static 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
 74static 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
102static 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
124static 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
167static 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
203static 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	if (IS_ERR(priv->regmap))
223		return PTR_ERR(priv->regmap);
224
225	priv->clk = devm_clk_get_enabled(dev, NULL);
226	if (IS_ERR(priv->clk))
227		return PTR_ERR(priv->clk);
228
229	priv->reset = devm_reset_control_get_exclusive(dev, NULL);
230	if (IS_ERR(priv->reset))
231		return PTR_ERR(priv->reset);
232
233	reset_control_deassert(priv->reset);
234
235	bus->name = "airoha_mdio_bus";
236	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev));
237	bus->parent = dev;
238	bus->read = airoha_mdio_read;
239	bus->write = airoha_mdio_write;
240	bus->read_c45 = airoha_mdio_cl45_read;
241	bus->write_c45 = airoha_mdio_cl45_write;
242
243	/* Check if a custom frequency is defined in DT or default to 2.5 MHz */
244	if (of_property_read_u32(dev->of_node, "clock-frequency", &freq))
245		freq = 2500000;
246
247	ret = clk_set_rate(priv->clk, freq);
248	if (ret)
249		return ret;
250
251	ret = devm_of_mdiobus_register(dev, bus, dev->of_node);
252	if (ret) {
253		reset_control_assert(priv->reset);
254		return ret;
255	}
256
257	return 0;
258}
259
260static const struct of_device_id airoha_mdio_dt_ids[] = {
261	{ .compatible = "airoha,an7583-mdio" },
262	{ }
263};
264MODULE_DEVICE_TABLE(of, airoha_mdio_dt_ids);
265
266static struct platform_driver airoha_mdio_driver = {
267	.probe = airoha_mdio_probe,
268	.driver = {
269		.name = "airoha-mdio",
270		.of_match_table = airoha_mdio_dt_ids,
271	},
272};
273
274module_platform_driver(airoha_mdio_driver);
275
276MODULE_DESCRIPTION("Airoha AN7583 MDIO interface driver");
277MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
278MODULE_LICENSE("GPL");
~/ Read-only file preview.