diff options
author | Andy Fleming <afleming@freescale.com> | 2008-04-19 02:29:54 +0400 |
---|---|---|
committer | Jeff Garzik <jgarzik@redhat.com> | 2008-04-25 10:08:52 +0400 |
commit | f62220d3a9ccb879c3f90f845ae57b724b7bbb62 (patch) | |
tree | 72697d5d0b7bfdebaf0fd74bea07212c9820a6df /drivers/net | |
parent | 8ec7226a93dcd4a314e2387d1033aef01145061b (diff) | |
download | linux-f62220d3a9ccb879c3f90f845ae57b724b7bbb62.tar.xz |
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>
Diffstat (limited to 'drivers/net')
-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 |
3 files changed, 123 insertions, 13 deletions
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) phydev->bus = bus; + /* Run all of the fixups for this PHY */ + phy_scan_fixups(phydev); + err = device_register(&phydev->dev); 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, if (mii_data->reg_num == MII_BMCR && val & BMCR_RESET - && phydev->drv->config_init) + && phydev->drv->config_init) { + phy_scan_fixups(phydev); phydev->drv->config_init(phydev); + } break; 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) phy_device_free(to_phy_device(dev)); } +static LIST_HEAD(phy_fixup_list); +static DEFINE_MUTEX(phy_fixup_lock); + +/* + * Creates a new phy_fixup and adds it to the list + * @bus_id: A string which matches phydev->dev.bus_id (or PHY_ANY_ID) + * @phy_uid: Used to match against phydev->phy_id (the UID of the PHY) + * It can also be PHY_ANY_UID + * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before + * comparison + * @run: The actual code to be run when a matching PHY is found + */ +int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, + int (*run)(struct phy_device *)) +{ + struct phy_fixup *fixup; + + fixup = kzalloc(sizeof(struct phy_fixup), GFP_KERNEL); + if (!fixup) + return -ENOMEM; + + strncpy(fixup->bus_id, bus_id, BUS_ID_SIZE); + fixup->phy_uid = phy_uid; + fixup->phy_uid_mask = phy_uid_mask; + fixup->run = run; + + mutex_lock(&phy_fixup_lock); + list_add_tail(&fixup->list, &phy_fixup_list); + mutex_unlock(&phy_fixup_lock); + + return 0; +} +EXPORT_SYMBOL(phy_register_fixup); + +/* Registers a fixup to be run on any PHY with the UID in phy_uid */ +int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, + int (*run)(struct phy_device *)) +{ + return phy_register_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask, run); +} +EXPORT_SYMBOL(phy_register_fixup_for_uid); + +/* Registers a fixup to be run on the PHY with id string bus_id */ +int phy_register_fixup_for_id(const char *bus_id, + int (*run)(struct phy_device *)) +{ + return phy_register_fixup(bus_id, PHY_ANY_UID, 0xffffffff, run); +} +EXPORT_SYMBOL(phy_register_fixup_for_id); + +/* + * Returns 1 if fixup matches phydev in bus_id and phy_uid. + * Fixups can be set to match any in one or more fields. + */ +static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) +{ + if (strcmp(fixup->bus_id, phydev->dev.bus_id) != 0) + if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0) + return 0; + + if ((fixup->phy_uid & fixup->phy_uid_mask) != + (phydev->phy_id & fixup->phy_uid_mask)) + if (fixup->phy_uid != PHY_ANY_UID) + return 0; + + return 1; +} + +/* Runs any matching fixups for this phydev */ +int phy_scan_fixups(struct phy_device *phydev) +{ + struct phy_fixup *fixup; + + mutex_lock(&phy_fixup_lock); + list_for_each_entry(fixup, &phy_fixup_list, list) { + if (phy_needs_fixup(phydev, fixup)) { + int err; + + err = fixup->run(phydev); + + if (err < 0) + return err; + } + } + mutex_unlock(&phy_fixup_lock); + + return 0; +} +EXPORT_SYMBOL(phy_scan_fixups); + struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id) { struct phy_device *dev; @@ -179,13 +269,13 @@ void phy_prepare_link(struct phy_device *phydev, * choose to call only the subset of functions which provide * the desired functionality. */ -struct phy_device * phy_connect(struct net_device *dev, const char *phy_id, +struct phy_device * phy_connect(struct net_device *dev, const char *bus_id, void (*handler)(struct net_device *), u32 flags, phy_interface_t interface) { struct phy_device *phydev; - phydev = phy_attach(dev, phy_id, flags, interface); + phydev = phy_attach(dev, bus_id, flags, interface); if (IS_ERR(phydev)) return phydev; @@ -226,7 +316,7 @@ static int phy_compare_id(struct device *dev, void *data) /** * phy_attach - attach a network device to a particular PHY device * @dev: network device to attach - * @phy_id: PHY device to attach + * @bus_id: PHY device to attach * @flags: PHY device's dev_flags * @interface: PHY device's interface * @@ -238,7 +328,7 @@ static int phy_compare_id(struct device *dev, void *data) * change. The phy_device is returned to the attaching driver. */ struct phy_device *phy_attach(struct net_device *dev, - const char *phy_id, u32 flags, phy_interface_t interface) + const char *bus_id, u32 flags, phy_interface_t interface) { struct bus_type *bus = &mdio_bus_type; struct phy_device *phydev; @@ -246,12 +336,12 @@ struct phy_device *phy_attach(struct net_device *dev, /* Search the list of PHY devices on the mdio bus for the * PHY with the requested name */ - d = bus_find_device(bus, NULL, (void *)phy_id, phy_compare_id); + d = bus_find_device(bus, NULL, (void *)bus_id, phy_compare_id); if (d) { phydev = to_phy_device(d); } else { - printk(KERN_ERR "%s not found\n", phy_id); + printk(KERN_ERR "%s not found\n", bus_id); return ERR_PTR(-ENODEV); } @@ -271,7 +361,7 @@ struct phy_device *phy_attach(struct net_device *dev, if (phydev->attached_dev) { printk(KERN_ERR "%s: %s already attached\n", - dev->name, phy_id); + dev->name, bus_id); return ERR_PTR(-EBUSY); } @@ -287,6 +377,11 @@ struct phy_device *phy_attach(struct net_device *dev, if (phydev->drv->config_init) { int err; + err = phy_scan_fixups(phydev); + + if (err < 0) + return ERR_PTR(err); + err = phydev->drv->config_init(phydev); if (err < 0) @@ -395,6 +490,7 @@ EXPORT_SYMBOL(genphy_config_advert); */ int genphy_setup_forced(struct phy_device *phydev) { + int err; int ctl = 0; phydev->pause = phydev->asym_pause = 0; @@ -407,17 +503,26 @@ int genphy_setup_forced(struct phy_device *phydev) if (DUPLEX_FULL == phydev->duplex) ctl |= BMCR_FULLDPLX; - ctl = phy_write(phydev, MII_BMCR, ctl); + err = phy_write(phydev, MII_BMCR, ctl); - if (ctl < 0) - return ctl; + if (err < 0) + return err; + + /* + * Run the fixups on this PHY, just in case the + * board code needs to change something after a reset + */ + err = phy_scan_fixups(phydev); + + if (err < 0) + return err; /* We just reset the device, so we'd better configure any * settings the PHY requires to operate */ if (phydev->drv->config_init) - ctl = phydev->drv->config_init(phydev); + err = phydev->drv->config_init(phydev); - return ctl; + return err; } |