diff options
author | Andy Fleming <afleming@freescale.com> | 2008-04-18 18:29:54 -0400 |
---|---|---|
committer | Jeff Garzik <jgarzik@redhat.com> | 2008-04-25 02:08:52 -0400 |
commit | f62220d3a9ccb879c3f90f845ae57b724b7bbb62 (patch) | |
tree | 72697d5d0b7bfdebaf0fd74bea07212c9820a6df | |
parent | 8ec7226a93dcd4a314e2387d1033aef01145061b (diff) |
phylib: Add support for board-level PHY fixups
Sometimes the specific interaction between the platform and the PHY
requires special handling. For instance, to change where the PHY's
clock input is, or to add a delay to account for latency issues in the
data path. We add a mechanism for registering a callback with the PHY
Lib to be called on matching PHYs when they are brought up, or reset.
Signed-off-by: Andy Fleming <afleming@freescale.com>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
-rw-r--r-- | Documentation/networking/phy.txt | 38 | ||||
-rw-r--r-- | drivers/net/phy/mdio_bus.c | 3 | ||||
-rw-r--r-- | drivers/net/phy/phy.c | 4 | ||||
-rw-r--r-- | drivers/net/phy/phy_device.c | 129 | ||||
-rw-r--r-- | include/linux/phy.h | 24 |
5 files changed, 182 insertions, 16 deletions
diff --git a/Documentation/networking/phy.txt b/Documentation/networking/phy.txt index 0bc95eab1512..8df6a7b0e66c 100644 --- a/Documentation/networking/phy.txt +++ b/Documentation/networking/phy.txt | |||
@@ -1,7 +1,7 @@ | |||
1 | 1 | ||
2 | ------- | 2 | ------- |
3 | PHY Abstraction Layer | 3 | PHY Abstraction Layer |
4 | (Updated 2006-11-30) | 4 | (Updated 2008-04-08) |
5 | 5 | ||
6 | Purpose | 6 | Purpose |
7 | 7 | ||
@@ -291,3 +291,39 @@ Writing a PHY driver | |||
291 | Feel free to look at the Marvell, Cicada, and Davicom drivers in | 291 | Feel free to look at the Marvell, Cicada, and Davicom drivers in |
292 | drivers/net/phy/ for examples (the lxt and qsemi drivers have | 292 | drivers/net/phy/ for examples (the lxt and qsemi drivers have |
293 | not been tested as of this writing) | 293 | not been tested as of this writing) |
294 | |||
295 | Board Fixups | ||
296 | |||
297 | Sometimes the specific interaction between the platform and the PHY requires | ||
298 | special handling. For instance, to change where the PHY's clock input is, | ||
299 | or to add a delay to account for latency issues in the data path. In order | ||
300 | to support such contingencies, the PHY Layer allows platform code to register | ||
301 | fixups to be run when the PHY is brought up (or subsequently reset). | ||
302 | |||
303 | When the PHY Layer brings up a PHY it checks to see if there are any fixups | ||
304 | registered for it, matching based on UID (contained in the PHY device's phy_id | ||
305 | field) and the bus identifier (contained in phydev->dev.bus_id). Both must | ||
306 | match, however two constants, PHY_ANY_ID and PHY_ANY_UID, are provided as | ||
307 | wildcards for the bus ID and UID, respectively. | ||
308 | |||
309 | When a match is found, the PHY layer will invoke the run function associated | ||
310 | with the fixup. This function is passed a pointer to the phy_device of | ||
311 | interest. It should therefore only operate on that PHY. | ||
312 | |||
313 | The platform code can either register the fixup using phy_register_fixup(): | ||
314 | |||
315 | int phy_register_fixup(const char *phy_id, | ||
316 | u32 phy_uid, u32 phy_uid_mask, | ||
317 | int (*run)(struct phy_device *)); | ||
318 | |||
319 | Or using one of the two stubs, phy_register_fixup_for_uid() and | ||
320 | phy_register_fixup_for_id(): | ||
321 | |||
322 | int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, | ||
323 | int (*run)(struct phy_device *)); | ||
324 | int phy_register_fixup_for_id(const char *phy_id, | ||
325 | int (*run)(struct phy_device *)); | ||
326 | |||
327 | The stubs set one of the two matching criteria, and set the other one to | ||
328 | match anything. | ||
329 | |||
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 963630c65ca9..94e0b7ed76f1 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c | |||
@@ -89,6 +89,9 @@ int mdiobus_register(struct mii_bus *bus) | |||
89 | 89 | ||
90 | phydev->bus = bus; | 90 | phydev->bus = bus; |
91 | 91 | ||
92 | /* Run all of the fixups for this PHY */ | ||
93 | phy_scan_fixups(phydev); | ||
94 | |||
92 | err = device_register(&phydev->dev); | 95 | err = device_register(&phydev->dev); |
93 | 96 | ||
94 | if (err) { | 97 | if (err) { |
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 12fccb1c76dc..3c18bb594957 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c | |||
@@ -406,8 +406,10 @@ int phy_mii_ioctl(struct phy_device *phydev, | |||
406 | 406 | ||
407 | if (mii_data->reg_num == MII_BMCR | 407 | if (mii_data->reg_num == MII_BMCR |
408 | && val & BMCR_RESET | 408 | && val & BMCR_RESET |
409 | && phydev->drv->config_init) | 409 | && phydev->drv->config_init) { |
410 | phy_scan_fixups(phydev); | ||
410 | phydev->drv->config_init(phydev); | 411 | phydev->drv->config_init(phydev); |
412 | } | ||
411 | break; | 413 | break; |
412 | 414 | ||
413 | default: | 415 | default: |
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 8b1121b02f98..ddf8d51832a6 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c | |||
@@ -53,6 +53,96 @@ static void phy_device_release(struct device *dev) | |||
53 | phy_device_free(to_phy_device(dev)); | 53 | phy_device_free(to_phy_device(dev)); |
54 | } | 54 | } |
55 | 55 | ||
56 | static LIST_HEAD(phy_fixup_list); | ||
57 | static DEFINE_MUTEX(phy_fixup_lock); | ||
58 | |||
59 | /* | ||
60 | * Creates a new phy_fixup and adds it to the list | ||
61 | * @bus_id: A string which matches phydev->dev.bus_id (or PHY_ANY_ID) | ||
62 | * @phy_uid: Used to match against phydev->phy_id (the UID of the PHY) | ||
63 | * It can also be PHY_ANY_UID | ||
64 | * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before | ||
65 | * comparison | ||
66 | * @run: The actual code to be run when a matching PHY is found | ||
67 | */ | ||
68 | int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, | ||
69 | int (*run)(struct phy_device *)) | ||
70 | { | ||
71 | struct phy_fixup *fixup; | ||
72 | |||
73 | fixup = kzalloc(sizeof(struct phy_fixup), GFP_KERNEL); | ||
74 | if (!fixup) | ||
75 | return -ENOMEM; | ||
76 | |||
77 | strncpy(fixup->bus_id, bus_id, BUS_ID_SIZE); | ||
78 | fixup->phy_uid = phy_uid; | ||
79 | fixup->phy_uid_mask = phy_uid_mask; | ||
80 | fixup->run = run; | ||
81 | |||
82 | mutex_lock(&phy_fixup_lock); | ||
83 | list_add_tail(&fixup->list, &phy_fixup_list); | ||
84 | mutex_unlock(&phy_fixup_lock); | ||
85 | |||
86 | return 0; | ||
87 | } | ||
88 | EXPORT_SYMBOL(phy_register_fixup); | ||
89 | |||
90 | /* Registers a fixup to be run on any PHY with the UID in phy_uid */ | ||
91 | int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, | ||
92 | int (*run)(struct phy_device *)) | ||
93 | { | ||
94 | return phy_register_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask, run); | ||
95 | } | ||
96 | EXPORT_SYMBOL(phy_register_fixup_for_uid); | ||
97 | |||
98 | /* Registers a fixup to be run on the PHY with id string bus_id */ | ||
99 | int phy_register_fixup_for_id(const char *bus_id, | ||
100 | int (*run)(struct phy_device *)) | ||
101 | { | ||
102 | return phy_register_fixup(bus_id, PHY_ANY_UID, 0xffffffff, run); | ||
103 | } | ||
104 | EXPORT_SYMBOL(phy_register_fixup_for_id); | ||
105 | |||
106 | /* | ||
107 | * Returns 1 if fixup matches phydev in bus_id and phy_uid. | ||
108 | * Fixups can be set to match any in one or more fields. | ||
109 | */ | ||
110 | static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) | ||
111 | { | ||
112 | if (strcmp(fixup->bus_id, phydev->dev.bus_id) != 0) | ||
113 | if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0) | ||
114 | return 0; | ||
115 | |||
116 | if ((fixup->phy_uid & fixup->phy_uid_mask) != | ||
117 | (phydev->phy_id & fixup->phy_uid_mask)) | ||
118 | if (fixup->phy_uid != PHY_ANY_UID) | ||
119 | return 0; | ||
120 | |||
121 | return 1; | ||
122 | } | ||
123 | |||
124 | /* Runs any matching fixups for this phydev */ | ||
125 | int phy_scan_fixups(struct phy_device *phydev) | ||
126 | { | ||
127 | struct phy_fixup *fixup; | ||
128 | |||
129 | mutex_lock(&phy_fixup_lock); | ||
130 | list_for_each_entry(fixup, &phy_fixup_list, list) { | ||
131 | if (phy_needs_fixup(phydev, fixup)) { | ||
132 | int err; | ||
133 | |||
134 | err = fixup->run(phydev); | ||
135 | |||
136 | if (err < 0) | ||
137 | return err; | ||
138 | } | ||
139 | } | ||
140 | mutex_unlock(&phy_fixup_lock); | ||
141 | |||
142 | return 0; | ||
143 | } | ||
144 | EXPORT_SYMBOL(phy_scan_fixups); | ||
145 | |||
56 | struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id) | 146 | struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id) |
57 | { | 147 | { |
58 | struct phy_device *dev; | 148 | struct phy_device *dev; |
@@ -179,13 +269,13 @@ void phy_prepare_link(struct phy_device *phydev, | |||
179 | * choose to call only the subset of functions which provide | 269 | * choose to call only the subset of functions which provide |
180 | * the desired functionality. | 270 | * the desired functionality. |
181 | */ | 271 | */ |
182 | struct phy_device * phy_connect(struct net_device *dev, const char *phy_id, | 272 | struct phy_device * phy_connect(struct net_device *dev, const char *bus_id, |
183 | void (*handler)(struct net_device *), u32 flags, | 273 | void (*handler)(struct net_device *), u32 flags, |
184 | phy_interface_t interface) | 274 | phy_interface_t interface) |
185 | { | 275 | { |
186 | struct phy_device *phydev; | 276 | struct phy_device *phydev; |
187 | 277 | ||
188 | phydev = phy_attach(dev, phy_id, flags, interface); | 278 | phydev = phy_attach(dev, bus_id, flags, interface); |
189 | 279 | ||
190 | if (IS_ERR(phydev)) | 280 | if (IS_ERR(phydev)) |
191 | return phydev; | 281 | return phydev; |
@@ -226,7 +316,7 @@ static int phy_compare_id(struct device *dev, void *data) | |||
226 | /** | 316 | /** |
227 | * phy_attach - attach a network device to a particular PHY device | 317 | * phy_attach - attach a network device to a particular PHY device |
228 | * @dev: network device to attach | 318 | * @dev: network device to attach |
229 | * @phy_id: PHY device to attach | 319 | * @bus_id: PHY device to attach |
230 | * @flags: PHY device's dev_flags | 320 | * @flags: PHY device's dev_flags |
231 | * @interface: PHY device's interface | 321 | * @interface: PHY device's interface |
232 | * | 322 | * |
@@ -238,7 +328,7 @@ static int phy_compare_id(struct device *dev, void *data) | |||
238 | * change. The phy_device is returned to the attaching driver. | 328 | * change. The phy_device is returned to the attaching driver. |
239 | */ | 329 | */ |
240 | struct phy_device *phy_attach(struct net_device *dev, | 330 | struct phy_device *phy_attach(struct net_device *dev, |
241 | const char *phy_id, u32 flags, phy_interface_t interface) | 331 | const char *bus_id, u32 flags, phy_interface_t interface) |
242 | { | 332 | { |
243 | struct bus_type *bus = &mdio_bus_type; | 333 | struct bus_type *bus = &mdio_bus_type; |
244 | struct phy_device *phydev; | 334 | struct phy_device *phydev; |
@@ -246,12 +336,12 @@ struct phy_device *phy_attach(struct net_device *dev, | |||
246 | 336 | ||
247 | /* Search the list of PHY devices on the mdio bus for the | 337 | /* Search the list of PHY devices on the mdio bus for the |
248 | * PHY with the requested name */ | 338 | * PHY with the requested name */ |
249 | d = bus_find_device(bus, NULL, (void *)phy_id, phy_compare_id); | 339 | d = bus_find_device(bus, NULL, (void *)bus_id, phy_compare_id); |
250 | 340 | ||
251 | if (d) { | 341 | if (d) { |
252 | phydev = to_phy_device(d); | 342 | phydev = to_phy_device(d); |
253 | } else { | 343 | } else { |
254 | printk(KERN_ERR "%s not found\n", phy_id); | 344 | printk(KERN_ERR "%s not found\n", bus_id); |
255 | return ERR_PTR(-ENODEV); | 345 | return ERR_PTR(-ENODEV); |
256 | } | 346 | } |
257 | 347 | ||
@@ -271,7 +361,7 @@ struct phy_device *phy_attach(struct net_device *dev, | |||
271 | 361 | ||
272 | if (phydev->attached_dev) { | 362 | if (phydev->attached_dev) { |
273 | printk(KERN_ERR "%s: %s already attached\n", | 363 | printk(KERN_ERR "%s: %s already attached\n", |
274 | dev->name, phy_id); | 364 | dev->name, bus_id); |
275 | return ERR_PTR(-EBUSY); | 365 | return ERR_PTR(-EBUSY); |
276 | } | 366 | } |
277 | 367 | ||
@@ -287,6 +377,11 @@ struct phy_device *phy_attach(struct net_device *dev, | |||
287 | if (phydev->drv->config_init) { | 377 | if (phydev->drv->config_init) { |
288 | int err; | 378 | int err; |
289 | 379 | ||
380 | err = phy_scan_fixups(phydev); | ||
381 | |||
382 | if (err < 0) | ||
383 | return ERR_PTR(err); | ||
384 | |||
290 | err = phydev->drv->config_init(phydev); | 385 | err = phydev->drv->config_init(phydev); |
291 | 386 | ||
292 | if (err < 0) | 387 | if (err < 0) |
@@ -395,6 +490,7 @@ EXPORT_SYMBOL(genphy_config_advert); | |||
395 | */ | 490 | */ |
396 | int genphy_setup_forced(struct phy_device *phydev) | 491 | int genphy_setup_forced(struct phy_device *phydev) |
397 | { | 492 | { |
493 | int err; | ||
398 | int ctl = 0; | 494 | int ctl = 0; |
399 | 495 | ||
400 | phydev->pause = phydev->asym_pause = 0; | 496 | phydev->pause = phydev->asym_pause = 0; |
@@ -407,17 +503,26 @@ int genphy_setup_forced(struct phy_device *phydev) | |||
407 | if (DUPLEX_FULL == phydev->duplex) | 503 | if (DUPLEX_FULL == phydev->duplex) |
408 | ctl |= BMCR_FULLDPLX; | 504 | ctl |= BMCR_FULLDPLX; |
409 | 505 | ||
410 | ctl = phy_write(phydev, MII_BMCR, ctl); | 506 | err = phy_write(phydev, MII_BMCR, ctl); |
411 | 507 | ||
412 | if (ctl < 0) | 508 | if (err < 0) |
413 | return ctl; | 509 | return err; |
510 | |||
511 | /* | ||
512 | * Run the fixups on this PHY, just in case the | ||
513 | * board code needs to change something after a reset | ||
514 | */ | ||
515 | err = phy_scan_fixups(phydev); | ||
516 | |||
517 | if (err < 0) | ||
518 | return err; | ||
414 | 519 | ||
415 | /* We just reset the device, so we'd better configure any | 520 | /* We just reset the device, so we'd better configure any |
416 | * settings the PHY requires to operate */ | 521 | * settings the PHY requires to operate */ |
417 | if (phydev->drv->config_init) | 522 | if (phydev->drv->config_init) |
418 | ctl = phydev->drv->config_init(phydev); | 523 | err = phydev->drv->config_init(phydev); |
419 | 524 | ||
420 | return ctl; | 525 | return err; |
421 | } | 526 | } |
422 | 527 | ||
423 | 528 | ||
diff --git a/include/linux/phy.h b/include/linux/phy.h index 779cbcd65f62..02df20f085fe 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h | |||
@@ -379,6 +379,18 @@ struct phy_driver { | |||
379 | }; | 379 | }; |
380 | #define to_phy_driver(d) container_of(d, struct phy_driver, driver) | 380 | #define to_phy_driver(d) container_of(d, struct phy_driver, driver) |
381 | 381 | ||
382 | #define PHY_ANY_ID "MATCH ANY PHY" | ||
383 | #define PHY_ANY_UID 0xffffffff | ||
384 | |||
385 | /* A Structure for boards to register fixups with the PHY Lib */ | ||
386 | struct phy_fixup { | ||
387 | struct list_head list; | ||
388 | char bus_id[BUS_ID_SIZE]; | ||
389 | u32 phy_uid; | ||
390 | u32 phy_uid_mask; | ||
391 | int (*run)(struct phy_device *phydev); | ||
392 | }; | ||
393 | |||
382 | int phy_read(struct phy_device *phydev, u16 regnum); | 394 | int phy_read(struct phy_device *phydev, u16 regnum); |
383 | int phy_write(struct phy_device *phydev, u16 regnum, u16 val); | 395 | int phy_write(struct phy_device *phydev, u16 regnum, u16 val); |
384 | int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id); | 396 | int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id); |
@@ -386,8 +398,8 @@ struct phy_device* get_phy_device(struct mii_bus *bus, int addr); | |||
386 | int phy_clear_interrupt(struct phy_device *phydev); | 398 | int phy_clear_interrupt(struct phy_device *phydev); |
387 | int phy_config_interrupt(struct phy_device *phydev, u32 interrupts); | 399 | int phy_config_interrupt(struct phy_device *phydev, u32 interrupts); |
388 | struct phy_device * phy_attach(struct net_device *dev, | 400 | struct phy_device * phy_attach(struct net_device *dev, |
389 | const char *phy_id, u32 flags, phy_interface_t interface); | 401 | const char *bus_id, u32 flags, phy_interface_t interface); |
390 | struct phy_device * phy_connect(struct net_device *dev, const char *phy_id, | 402 | struct phy_device * phy_connect(struct net_device *dev, const char *bus_id, |
391 | void (*handler)(struct net_device *), u32 flags, | 403 | void (*handler)(struct net_device *), u32 flags, |
392 | phy_interface_t interface); | 404 | phy_interface_t interface); |
393 | void phy_disconnect(struct phy_device *phydev); | 405 | void phy_disconnect(struct phy_device *phydev); |
@@ -427,5 +439,13 @@ void phy_print_status(struct phy_device *phydev); | |||
427 | struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id); | 439 | struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id); |
428 | void phy_device_free(struct phy_device *phydev); | 440 | void phy_device_free(struct phy_device *phydev); |
429 | 441 | ||
442 | int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, | ||
443 | int (*run)(struct phy_device *)); | ||
444 | int phy_register_fixup_for_id(const char *bus_id, | ||
445 | int (*run)(struct phy_device *)); | ||
446 | int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, | ||
447 | int (*run)(struct phy_device *)); | ||
448 | int phy_scan_fixups(struct phy_device *phydev); | ||
449 | |||
430 | extern struct bus_type mdio_bus_type; | 450 | extern struct bus_type mdio_bus_type; |
431 | #endif /* __PHY_H */ | 451 | #endif /* __PHY_H */ |