summaryrefslogtreecommitdiff
path: root/drivers/net/phy/phy_device.c
diff options
context:
space:
mode:
authorAndy Fleming <afleming@freescale.com>2008-04-19 02:29:54 +0400
committerJeff Garzik <jgarzik@redhat.com>2008-04-25 10:08:52 +0400
commitf62220d3a9ccb879c3f90f845ae57b724b7bbb62 (patch)
tree72697d5d0b7bfdebaf0fd74bea07212c9820a6df /drivers/net/phy/phy_device.c
parent8ec7226a93dcd4a314e2387d1033aef01145061b (diff)
downloadlinux-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/phy/phy_device.c')
-rw-r--r--drivers/net/phy/phy_device.c129
1 files changed, 117 insertions, 12 deletions
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;
}