]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/net/phy/phy_device.c
Merge branches 'pm-core', 'pm-qos', 'pm-domains' and 'pm-opp'
[linux.git] / drivers / net / phy / phy_device.c
index c4ceb082e970e5d2a7d3b8c2c2f75813d52fbcff..8c8e15b8739dec0ae96d72b514045a99eb1ed7e1 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/mii.h>
 #include <linux/ethtool.h>
 #include <linux/phy.h>
+#include <linux/phy_led_triggers.h>
 #include <linux/mdio.h>
 #include <linux/io.h>
 #include <linux/uaccess.h>
@@ -234,6 +235,53 @@ int phy_register_fixup_for_id(const char *bus_id,
 }
 EXPORT_SYMBOL(phy_register_fixup_for_id);
 
+/**
+ * phy_unregister_fixup - remove a phy_fixup from the list
+ * @bus_id: A string matches fixup->bus_id (or PHY_ANY_ID) in phy_fixup_list
+ * @phy_uid: A phy id matches fixup->phy_id (or PHY_ANY_UID) in phy_fixup_list
+ * @phy_uid_mask: Applied to phy_uid and fixup->phy_uid before comparison
+ */
+int phy_unregister_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask)
+{
+       struct list_head *pos, *n;
+       struct phy_fixup *fixup;
+       int ret;
+
+       ret = -ENODEV;
+
+       mutex_lock(&phy_fixup_lock);
+       list_for_each_safe(pos, n, &phy_fixup_list) {
+               fixup = list_entry(pos, struct phy_fixup, list);
+
+               if ((!strcmp(fixup->bus_id, bus_id)) &&
+                   ((fixup->phy_uid & phy_uid_mask) ==
+                    (phy_uid & phy_uid_mask))) {
+                       list_del(&fixup->list);
+                       kfree(fixup);
+                       ret = 0;
+                       break;
+               }
+       }
+       mutex_unlock(&phy_fixup_lock);
+
+       return ret;
+}
+EXPORT_SYMBOL(phy_unregister_fixup);
+
+/* Unregisters a fixup of any PHY with the UID in phy_uid */
+int phy_unregister_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask)
+{
+       return phy_unregister_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask);
+}
+EXPORT_SYMBOL(phy_unregister_fixup_for_uid);
+
+/* Unregisters a fixup of the PHY with id string bus_id */
+int phy_unregister_fixup_for_id(const char *bus_id)
+{
+       return phy_unregister_fixup(bus_id, PHY_ANY_UID, 0xffffffff);
+}
+EXPORT_SYMBOL(phy_unregister_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.
  */
@@ -347,7 +395,7 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id,
 
        mutex_init(&dev->lock);
        INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
-       INIT_WORK(&dev->phy_queue, phy_change);
+       INIT_WORK(&dev->phy_queue, phy_change_work);
 
        /* Request the appropriate module unconditionally; don't
         * bother trying to do so only if it isn't already loaded,
@@ -860,6 +908,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
        struct module *ndev_owner = dev->dev.parent->driver->owner;
        struct mii_bus *bus = phydev->mdio.bus;
        struct device *d = &phydev->mdio.dev;
+       bool using_genphy = false;
        int err;
 
        /* For Ethernet device drivers that register their own MDIO bus, we
@@ -885,12 +934,22 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
                        d->driver =
                                &genphy_driver[GENPHY_DRV_1G].mdiodrv.driver;
 
+               using_genphy = true;
+       }
+
+       if (!try_module_get(d->driver->owner)) {
+               dev_err(&dev->dev, "failed to get the device driver module\n");
+               err = -EIO;
+               goto error_put_device;
+       }
+
+       if (using_genphy) {
                err = d->driver->probe(d);
                if (err >= 0)
                        err = device_bind_driver(d);
 
                if (err)
-                       goto error;
+                       goto error_module_put;
        }
 
        if (phydev->attached_dev) {
@@ -919,13 +978,21 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
         */
        err = phy_init_hw(phydev);
        if (err)
-               phy_detach(phydev);
-       else
-               phy_resume(phydev);
+               goto error;
+
+       phy_resume(phydev);
+       phy_led_triggers_register(phydev);
 
        return err;
 
 error:
+       /* phy_detach() does all of the cleanup below */
+       phy_detach(phydev);
+       return err;
+
+error_module_put:
+       module_put(d->driver->owner);
+error_put_device:
        put_device(d);
        if (ndev_owner != bus->owner)
                module_put(bus->owner);
@@ -987,6 +1054,10 @@ void phy_detach(struct phy_device *phydev)
        phydev->attached_dev = NULL;
        phy_suspend(phydev);
 
+       phy_led_triggers_unregister(phydev);
+
+       module_put(phydev->mdio.dev.driver->owner);
+
        /* If the device had no specific driver before (i.e. - it
         * was using the generic driver), we unbind the device
         * from the generic driver so that there's a chance a
@@ -1125,6 +1196,43 @@ static int genphy_config_advert(struct phy_device *phydev)
        return changed;
 }
 
+/**
+ * genphy_config_eee_advert - disable unwanted eee mode advertisement
+ * @phydev: target phy_device struct
+ *
+ * Description: Writes MDIO_AN_EEE_ADV after disabling unsupported energy
+ *   efficent ethernet modes. Returns 0 if the PHY's advertisement hasn't
+ *   changed, and 1 if it has changed.
+ */
+static int genphy_config_eee_advert(struct phy_device *phydev)
+{
+       int broken = phydev->eee_broken_modes;
+       int old_adv, adv;
+
+       /* Nothing to disable */
+       if (!broken)
+               return 0;
+
+       /* If the following call fails, we assume that EEE is not
+        * supported by the phy. If we read 0, EEE is not advertised
+        * In both case, we don't need to continue
+        */
+       adv = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV, MDIO_MMD_AN);
+       if (adv <= 0)
+               return 0;
+
+       old_adv = adv;
+       adv &= ~broken;
+
+       /* Advertising remains unchanged with the broken mask */
+       if (old_adv == adv)
+               return 0;
+
+       phy_write_mmd_indirect(phydev, MDIO_AN_EEE_ADV, MDIO_MMD_AN, adv);
+
+       return 1;
+}
+
 /**
  * genphy_setup_forced - configures/forces speed/duplex from @phydev
  * @phydev: target phy_device struct
@@ -1183,15 +1291,20 @@ EXPORT_SYMBOL(genphy_restart_aneg);
  */
 int genphy_config_aneg(struct phy_device *phydev)
 {
-       int result;
+       int err, changed;
+
+       changed = genphy_config_eee_advert(phydev);
 
        if (AUTONEG_ENABLE != phydev->autoneg)
                return genphy_setup_forced(phydev);
 
-       result = genphy_config_advert(phydev);
-       if (result < 0) /* error */
-               return result;
-       if (result == 0) {
+       err = genphy_config_advert(phydev);
+       if (err < 0) /* error */
+               return err;
+
+       changed |= err;
+
+       if (changed == 0) {
                /* Advertisement hasn't changed, but maybe aneg was never on to
                 * begin with?  Or maybe phy was isolated?
                 */
@@ -1201,16 +1314,16 @@ int genphy_config_aneg(struct phy_device *phydev)
                        return ctl;
 
                if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE))
-                       result = 1; /* do restart aneg */
+                       changed = 1; /* do restart aneg */
        }
 
        /* Only restart aneg if we are advertising something different
         * than we were before.
         */
-       if (result > 0)
-               result = genphy_restart_aneg(phydev);
+       if (changed > 0)
+               return genphy_restart_aneg(phydev);
 
-       return result;
+       return 0;
 }
 EXPORT_SYMBOL(genphy_config_aneg);
 
@@ -1568,6 +1681,33 @@ static void of_set_phy_supported(struct phy_device *phydev)
                __set_phy_supported(phydev, max_speed);
 }
 
+static void of_set_phy_eee_broken(struct phy_device *phydev)
+{
+       struct device_node *node = phydev->mdio.dev.of_node;
+       u32 broken = 0;
+
+       if (!IS_ENABLED(CONFIG_OF_MDIO))
+               return;
+
+       if (!node)
+               return;
+
+       if (of_property_read_bool(node, "eee-broken-100tx"))
+               broken |= MDIO_EEE_100TX;
+       if (of_property_read_bool(node, "eee-broken-1000t"))
+               broken |= MDIO_EEE_1000T;
+       if (of_property_read_bool(node, "eee-broken-10gt"))
+               broken |= MDIO_EEE_10GT;
+       if (of_property_read_bool(node, "eee-broken-1000kx"))
+               broken |= MDIO_EEE_1000KX;
+       if (of_property_read_bool(node, "eee-broken-10gkx4"))
+               broken |= MDIO_EEE_10GKX4;
+       if (of_property_read_bool(node, "eee-broken-10gkr"))
+               broken |= MDIO_EEE_10GKR;
+
+       phydev->eee_broken_modes = broken;
+}
+
 /**
  * phy_probe - probe and init a PHY device
  * @dev: device to probe and init
@@ -1605,6 +1745,30 @@ static int phy_probe(struct device *dev)
        of_set_phy_supported(phydev);
        phydev->advertising = phydev->supported;
 
+       /* Get the EEE modes we want to prohibit. We will ask
+        * the PHY stop advertising these mode later on
+        */
+       of_set_phy_eee_broken(phydev);
+
+       /* The Pause Frame bits indicate that the PHY can support passing
+        * pause frames. During autonegotiation, the PHYs will determine if
+        * they should allow pause frames to pass.  The MAC driver should then
+        * use that result to determine whether to enable flow control via
+        * pause frames.
+        *
+        * Normally, PHY drivers should not set the Pause bits, and instead
+        * allow phylib to do that.  However, there may be some situations
+        * (e.g. hardware erratum) where the driver wants to set only one
+        * of these bits.
+        */
+       if (phydrv->features & (SUPPORTED_Pause | SUPPORTED_Asym_Pause)) {
+               phydev->supported &= ~(SUPPORTED_Pause | SUPPORTED_Asym_Pause);
+               phydev->supported |= phydrv->features &
+                                    (SUPPORTED_Pause | SUPPORTED_Asym_Pause);
+       } else {
+               phydev->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;
+       }
+
        /* Set the state to READY by default */
        phydev->state = PHY_READY;