Commit | Line | Data |
---|---|---|
a2443fd1 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
11b0bacd | 2 | /* |
a79d8e93 | 3 | * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) |
11b0bacd | 4 | * |
a79d8e93 VB |
5 | * Author: Vitaly Bordug <vbordug@ru.mvista.com> |
6 | * Anton Vorontsov <avorontsov@ru.mvista.com> | |
11b0bacd | 7 | * |
a79d8e93 | 8 | * Copyright (c) 2006-2007 MontaVista Software, Inc. |
11b0bacd | 9 | */ |
a79d8e93 | 10 | |
11b0bacd | 11 | #include <linux/kernel.h> |
11b0bacd | 12 | #include <linux/module.h> |
a79d8e93 VB |
13 | #include <linux/platform_device.h> |
14 | #include <linux/list.h> | |
11b0bacd | 15 | #include <linux/mii.h> |
11b0bacd | 16 | #include <linux/phy.h> |
7c32f470 | 17 | #include <linux/phy_fixed.h> |
57401d5e | 18 | #include <linux/err.h> |
5a0e3ad6 | 19 | #include <linux/slab.h> |
a7595121 | 20 | #include <linux/of.h> |
5468e82f | 21 | #include <linux/gpio/consumer.h> |
69fc58a5 | 22 | #include <linux/idr.h> |
b3e5464e | 23 | #include <linux/netdevice.h> |
0f3b1cf2 | 24 | #include <linux/linkmode.h> |
11b0bacd | 25 | |
5ae68b0c RK |
26 | #include "swphy.h" |
27 | ||
a79d8e93 | 28 | struct fixed_mdio_bus { |
298cf9be | 29 | struct mii_bus *mii_bus; |
a79d8e93 VB |
30 | struct list_head phys; |
31 | }; | |
11b0bacd | 32 | |
a79d8e93 | 33 | struct fixed_phy { |
9b744942 | 34 | int addr; |
a79d8e93 VB |
35 | struct phy_device *phydev; |
36 | struct fixed_phy_status status; | |
b3e5464e | 37 | bool no_carrier; |
a79d8e93 VB |
38 | int (*link_update)(struct net_device *, struct fixed_phy_status *); |
39 | struct list_head node; | |
5468e82f | 40 | struct gpio_desc *link_gpiod; |
a79d8e93 | 41 | }; |
7c32f470 | 42 | |
a79d8e93 VB |
43 | static struct platform_device *pdev; |
44 | static struct fixed_mdio_bus platform_fmb = { | |
45 | .phys = LIST_HEAD_INIT(platform_fmb.phys), | |
46 | }; | |
7c32f470 | 47 | |
b3e5464e JT |
48 | int fixed_phy_change_carrier(struct net_device *dev, bool new_carrier) |
49 | { | |
50 | struct fixed_mdio_bus *fmb = &platform_fmb; | |
51 | struct phy_device *phydev = dev->phydev; | |
52 | struct fixed_phy *fp; | |
53 | ||
54 | if (!phydev || !phydev->mdio.bus) | |
55 | return -EINVAL; | |
56 | ||
57 | list_for_each_entry(fp, &fmb->phys, node) { | |
58 | if (fp->addr == phydev->mdio.addr) { | |
59 | fp->no_carrier = !new_carrier; | |
60 | return 0; | |
61 | } | |
62 | } | |
63 | return -EINVAL; | |
64 | } | |
65 | EXPORT_SYMBOL_GPL(fixed_phy_change_carrier); | |
66 | ||
37688e3f | 67 | static void fixed_phy_update(struct fixed_phy *fp) |
11b0bacd | 68 | { |
5468e82f LW |
69 | if (!fp->no_carrier && fp->link_gpiod) |
70 | fp->status.link = !!gpiod_get_value_cansleep(fp->link_gpiod); | |
11b0bacd VB |
71 | } |
72 | ||
9b744942 | 73 | static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) |
11b0bacd | 74 | { |
ec2a5652 | 75 | struct fixed_mdio_bus *fmb = bus->priv; |
a79d8e93 VB |
76 | struct fixed_phy *fp; |
77 | ||
a79d8e93 | 78 | list_for_each_entry(fp, &fmb->phys, node) { |
9b744942 | 79 | if (fp->addr == phy_addr) { |
bf7afb29 | 80 | struct fixed_phy_status state; |
79cbb6bc AD |
81 | |
82 | fp->status.link = !fp->no_carrier; | |
83 | ||
84 | /* Issue callback if user registered it. */ | |
85 | if (fp->link_update) | |
86 | fp->link_update(fp->phydev->attached_dev, | |
87 | &fp->status); | |
88 | ||
89 | /* Check the GPIO for change in status */ | |
90 | fixed_phy_update(fp); | |
91 | state = fp->status; | |
bf7afb29 RK |
92 | |
93 | return swphy_read_reg(reg_num, &state); | |
7c32f470 | 94 | } |
a79d8e93 | 95 | } |
11b0bacd | 96 | |
a79d8e93 | 97 | return 0xFFFF; |
11b0bacd VB |
98 | } |
99 | ||
9b744942 | 100 | static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, |
a79d8e93 | 101 | u16 val) |
11b0bacd | 102 | { |
11b0bacd VB |
103 | return 0; |
104 | } | |
105 | ||
a79d8e93 VB |
106 | /* |
107 | * If something weird is required to be done with link/speed, | |
108 | * network driver is able to assign a function to implement this. | |
109 | * May be useful for PHY's that need to be software-driven. | |
110 | */ | |
111 | int fixed_phy_set_link_update(struct phy_device *phydev, | |
112 | int (*link_update)(struct net_device *, | |
113 | struct fixed_phy_status *)) | |
11b0bacd | 114 | { |
a79d8e93 VB |
115 | struct fixed_mdio_bus *fmb = &platform_fmb; |
116 | struct fixed_phy *fp; | |
11b0bacd | 117 | |
e5a03bfd | 118 | if (!phydev || !phydev->mdio.bus) |
a79d8e93 | 119 | return -EINVAL; |
11b0bacd | 120 | |
a79d8e93 | 121 | list_for_each_entry(fp, &fmb->phys, node) { |
e5a03bfd | 122 | if (fp->addr == phydev->mdio.addr) { |
a79d8e93 VB |
123 | fp->link_update = link_update; |
124 | fp->phydev = phydev; | |
125 | return 0; | |
126 | } | |
127 | } | |
11b0bacd | 128 | |
a79d8e93 | 129 | return -ENOENT; |
7c32f470 | 130 | } |
a79d8e93 | 131 | EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); |
7c32f470 | 132 | |
5468e82f LW |
133 | static int fixed_phy_add_gpiod(unsigned int irq, int phy_addr, |
134 | struct fixed_phy_status *status, | |
135 | struct gpio_desc *gpiod) | |
11b0bacd | 136 | { |
a79d8e93 VB |
137 | int ret; |
138 | struct fixed_mdio_bus *fmb = &platform_fmb; | |
139 | struct fixed_phy *fp; | |
11b0bacd | 140 | |
68888ce0 RK |
141 | ret = swphy_validate_state(status); |
142 | if (ret < 0) | |
143 | return ret; | |
144 | ||
a79d8e93 VB |
145 | fp = kzalloc(sizeof(*fp), GFP_KERNEL); |
146 | if (!fp) | |
147 | return -ENOMEM; | |
11b0bacd | 148 | |
185be5ae RV |
149 | if (irq != PHY_POLL) |
150 | fmb->mii_bus->irq[phy_addr] = irq; | |
11b0bacd | 151 | |
9b744942 | 152 | fp->addr = phy_addr; |
a79d8e93 | 153 | fp->status = *status; |
5468e82f | 154 | fp->link_gpiod = gpiod; |
7c32f470 | 155 | |
37688e3f | 156 | fixed_phy_update(fp); |
11b0bacd | 157 | |
a79d8e93 | 158 | list_add_tail(&fp->node, &fmb->phys); |
7c32f470 | 159 | |
a79d8e93 | 160 | return 0; |
5468e82f | 161 | } |
11b0bacd | 162 | |
5468e82f | 163 | int fixed_phy_add(unsigned int irq, int phy_addr, |
169d7a40 WL |
164 | struct fixed_phy_status *status) |
165 | { | |
5468e82f | 166 | return fixed_phy_add_gpiod(irq, phy_addr, status, NULL); |
a79d8e93 VB |
167 | } |
168 | EXPORT_SYMBOL_GPL(fixed_phy_add); | |
11b0bacd | 169 | |
69fc58a5 FF |
170 | static DEFINE_IDA(phy_fixed_ida); |
171 | ||
5bcbe0f3 | 172 | static void fixed_phy_del(int phy_addr) |
a7595121 TP |
173 | { |
174 | struct fixed_mdio_bus *fmb = &platform_fmb; | |
175 | struct fixed_phy *fp, *tmp; | |
176 | ||
177 | list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { | |
178 | if (fp->addr == phy_addr) { | |
179 | list_del(&fp->node); | |
5468e82f LW |
180 | if (fp->link_gpiod) |
181 | gpiod_put(fp->link_gpiod); | |
a7595121 | 182 | kfree(fp); |
2f1de254 | 183 | ida_free(&phy_fixed_ida, phy_addr); |
a7595121 TP |
184 | return; |
185 | } | |
186 | } | |
187 | } | |
a7595121 | 188 | |
5468e82f LW |
189 | #ifdef CONFIG_OF_GPIO |
190 | static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np) | |
191 | { | |
192 | struct device_node *fixed_link_node; | |
193 | struct gpio_desc *gpiod; | |
194 | ||
195 | if (!np) | |
196 | return NULL; | |
197 | ||
198 | fixed_link_node = of_get_child_by_name(np, "fixed-link"); | |
199 | if (!fixed_link_node) | |
200 | return NULL; | |
201 | ||
202 | /* | |
203 | * As the fixed link is just a device tree node without any | |
204 | * Linux device associated with it, we simply have obtain | |
205 | * the GPIO descriptor from the device tree like this. | |
206 | */ | |
5ffcc858 DT |
207 | gpiod = fwnode_gpiod_get_index(of_fwnode_handle(fixed_link_node), |
208 | "link", 0, GPIOD_IN, "mdio"); | |
d266f19f | 209 | if (IS_ERR(gpiod) && PTR_ERR(gpiod) != -EPROBE_DEFER) { |
ab98c008 HF |
210 | if (PTR_ERR(gpiod) != -ENOENT) |
211 | pr_err("error getting GPIO for fixed link %pOF, proceed without\n", | |
212 | fixed_link_node); | |
5468e82f LW |
213 | gpiod = NULL; |
214 | } | |
d266f19f | 215 | of_node_put(fixed_link_node); |
5468e82f LW |
216 | |
217 | return gpiod; | |
218 | } | |
219 | #else | |
220 | static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np) | |
221 | { | |
222 | return NULL; | |
223 | } | |
224 | #endif | |
225 | ||
71bd106d MF |
226 | static struct phy_device *__fixed_phy_register(unsigned int irq, |
227 | struct fixed_phy_status *status, | |
228 | struct device_node *np, | |
229 | struct gpio_desc *gpiod) | |
a7595121 TP |
230 | { |
231 | struct fixed_mdio_bus *fmb = &platform_fmb; | |
232 | struct phy_device *phy; | |
233 | int phy_addr; | |
234 | int ret; | |
235 | ||
185be5ae RV |
236 | if (!fmb->mii_bus || fmb->mii_bus->state != MDIOBUS_REGISTERED) |
237 | return ERR_PTR(-EPROBE_DEFER); | |
238 | ||
5468e82f | 239 | /* Check if we have a GPIO associated with this fixed phy */ |
71bd106d MF |
240 | if (!gpiod) { |
241 | gpiod = fixed_phy_get_gpiod(np); | |
065e1ae0 FF |
242 | if (IS_ERR(gpiod)) |
243 | return ERR_CAST(gpiod); | |
71bd106d | 244 | } |
5468e82f | 245 | |
a7595121 | 246 | /* Get the next available PHY address, up to PHY_MAX_ADDR */ |
2f1de254 | 247 | phy_addr = ida_alloc_max(&phy_fixed_ida, PHY_MAX_ADDR - 1, GFP_KERNEL); |
69fc58a5 FF |
248 | if (phy_addr < 0) |
249 | return ERR_PTR(phy_addr); | |
a7595121 | 250 | |
5468e82f | 251 | ret = fixed_phy_add_gpiod(irq, phy_addr, status, gpiod); |
69fc58a5 | 252 | if (ret < 0) { |
2f1de254 | 253 | ida_free(&phy_fixed_ida, phy_addr); |
fd2ef0ba | 254 | return ERR_PTR(ret); |
69fc58a5 | 255 | } |
a7595121 TP |
256 | |
257 | phy = get_phy_device(fmb->mii_bus, phy_addr, false); | |
4914a584 | 258 | if (IS_ERR(phy)) { |
a7595121 | 259 | fixed_phy_del(phy_addr); |
fd2ef0ba | 260 | return ERR_PTR(-EINVAL); |
a7595121 TP |
261 | } |
262 | ||
4b195360 MB |
263 | /* propagate the fixed link values to struct phy_device */ |
264 | phy->link = status->link; | |
265 | if (status->link) { | |
266 | phy->speed = status->speed; | |
267 | phy->duplex = status->duplex; | |
268 | phy->pause = status->pause; | |
269 | phy->asym_pause = status->asym_pause; | |
270 | } | |
271 | ||
a7595121 | 272 | of_node_get(np); |
e5a03bfd | 273 | phy->mdio.dev.of_node = np; |
5a11dd7d | 274 | phy->is_pseudo_fixed_link = true; |
a7595121 | 275 | |
34b31da4 AL |
276 | switch (status->speed) { |
277 | case SPEED_1000: | |
3c1bcc86 AL |
278 | linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, |
279 | phy->supported); | |
280 | linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, | |
281 | phy->supported); | |
df561f66 | 282 | fallthrough; |
34b31da4 | 283 | case SPEED_100: |
3c1bcc86 AL |
284 | linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, |
285 | phy->supported); | |
286 | linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, | |
287 | phy->supported); | |
df561f66 | 288 | fallthrough; |
34b31da4 AL |
289 | case SPEED_10: |
290 | default: | |
3c1bcc86 AL |
291 | linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, |
292 | phy->supported); | |
293 | linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, | |
294 | phy->supported); | |
34b31da4 AL |
295 | } |
296 | ||
22c0ef6b | 297 | phy_advertise_supported(phy); |
0f3b1cf2 | 298 | |
a7595121 TP |
299 | ret = phy_device_register(phy); |
300 | if (ret) { | |
301 | phy_device_free(phy); | |
302 | of_node_put(np); | |
303 | fixed_phy_del(phy_addr); | |
fd2ef0ba | 304 | return ERR_PTR(ret); |
a7595121 TP |
305 | } |
306 | ||
fd2ef0ba | 307 | return phy; |
a7595121 | 308 | } |
71bd106d MF |
309 | |
310 | struct phy_device *fixed_phy_register(unsigned int irq, | |
311 | struct fixed_phy_status *status, | |
312 | struct device_node *np) | |
313 | { | |
314 | return __fixed_phy_register(irq, status, np, NULL); | |
315 | } | |
37e9a690 | 316 | EXPORT_SYMBOL_GPL(fixed_phy_register); |
a7595121 | 317 | |
71bd106d MF |
318 | struct phy_device * |
319 | fixed_phy_register_with_gpiod(unsigned int irq, | |
320 | struct fixed_phy_status *status, | |
321 | struct gpio_desc *gpiod) | |
322 | { | |
323 | return __fixed_phy_register(irq, status, NULL, gpiod); | |
324 | } | |
325 | EXPORT_SYMBOL_GPL(fixed_phy_register_with_gpiod); | |
326 | ||
5bcbe0f3 AL |
327 | void fixed_phy_unregister(struct phy_device *phy) |
328 | { | |
329 | phy_device_remove(phy); | |
13c9d934 | 330 | of_node_put(phy->mdio.dev.of_node); |
5bcbe0f3 AL |
331 | fixed_phy_del(phy->mdio.addr); |
332 | } | |
333 | EXPORT_SYMBOL_GPL(fixed_phy_unregister); | |
334 | ||
a79d8e93 VB |
335 | static int __init fixed_mdio_bus_init(void) |
336 | { | |
337 | struct fixed_mdio_bus *fmb = &platform_fmb; | |
338 | int ret; | |
11b0bacd | 339 | |
a79d8e93 | 340 | pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0); |
b0c1638f FE |
341 | if (IS_ERR(pdev)) |
342 | return PTR_ERR(pdev); | |
11b0bacd | 343 | |
298cf9be LB |
344 | fmb->mii_bus = mdiobus_alloc(); |
345 | if (fmb->mii_bus == NULL) { | |
346 | ret = -ENOMEM; | |
347 | goto err_mdiobus_reg; | |
348 | } | |
11b0bacd | 349 | |
9e6c643b | 350 | snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); |
298cf9be | 351 | fmb->mii_bus->name = "Fixed MDIO Bus"; |
ec2a5652 | 352 | fmb->mii_bus->priv = fmb; |
298cf9be LB |
353 | fmb->mii_bus->parent = &pdev->dev; |
354 | fmb->mii_bus->read = &fixed_mdio_read; | |
355 | fmb->mii_bus->write = &fixed_mdio_write; | |
bfa54812 | 356 | fmb->mii_bus->phy_mask = ~0; |
298cf9be LB |
357 | |
358 | ret = mdiobus_register(fmb->mii_bus); | |
a79d8e93 | 359 | if (ret) |
298cf9be | 360 | goto err_mdiobus_alloc; |
11b0bacd | 361 | |
a79d8e93 | 362 | return 0; |
11b0bacd | 363 | |
298cf9be LB |
364 | err_mdiobus_alloc: |
365 | mdiobus_free(fmb->mii_bus); | |
a79d8e93 VB |
366 | err_mdiobus_reg: |
367 | platform_device_unregister(pdev); | |
a79d8e93 VB |
368 | return ret; |
369 | } | |
370 | module_init(fixed_mdio_bus_init); | |
11b0bacd | 371 | |
a79d8e93 VB |
372 | static void __exit fixed_mdio_bus_exit(void) |
373 | { | |
374 | struct fixed_mdio_bus *fmb = &platform_fmb; | |
651be3a2 | 375 | struct fixed_phy *fp, *tmp; |
11b0bacd | 376 | |
298cf9be LB |
377 | mdiobus_unregister(fmb->mii_bus); |
378 | mdiobus_free(fmb->mii_bus); | |
a79d8e93 | 379 | platform_device_unregister(pdev); |
11b0bacd | 380 | |
651be3a2 | 381 | list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { |
a79d8e93 VB |
382 | list_del(&fp->node); |
383 | kfree(fp); | |
7c32f470 | 384 | } |
69fc58a5 | 385 | ida_destroy(&phy_fixed_ida); |
11b0bacd | 386 | } |
a79d8e93 | 387 | module_exit(fixed_mdio_bus_exit); |
11b0bacd | 388 | |
a79d8e93 | 389 | MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); |
11b0bacd VB |
390 | MODULE_AUTHOR("Vitaly Bordug"); |
391 | MODULE_LICENSE("GPL"); |