]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/net/phy/phy.c
net: phy: improve phylib correctness for non-autoneg settings
[karo-tx-linux.git] / drivers / net / phy / phy.c
index 7b1c93b0233a21ef5a44d4edebd83f6001f82424..00280d4eeb563099ae1ab4d177f62c27347751b3 100644 (file)
@@ -50,8 +50,22 @@ static const char *phy_speed_to_str(int speed)
                return "1Gbps";
        case SPEED_2500:
                return "2.5Gbps";
+       case SPEED_5000:
+               return "5Gbps";
        case SPEED_10000:
                return "10Gbps";
+       case SPEED_20000:
+               return "20Gbps";
+       case SPEED_25000:
+               return "25Gbps";
+       case SPEED_40000:
+               return "40Gbps";
+       case SPEED_50000:
+               return "50Gbps";
+       case SPEED_56000:
+               return "56Gbps";
+       case SPEED_100000:
+               return "100Gbps";
        case SPEED_UNKNOWN:
                return "Unknown";
        default:
@@ -162,7 +176,9 @@ struct phy_setting {
        u32 setting;
 };
 
-/* A mapping of all SUPPORTED settings to speed/duplex */
+/* A mapping of all SUPPORTED settings to speed/duplex.  This table
+ * must be grouped by speed and sorted in descending match priority
+ * - iow, descending speed. */
 static const struct phy_setting settings[] = {
        {
                .speed = SPEED_10000,
@@ -221,45 +237,70 @@ static const struct phy_setting settings[] = {
        },
 };
 
-#define MAX_NUM_SETTINGS ARRAY_SIZE(settings)
-
 /**
- * phy_find_setting - find a PHY settings array entry that matches speed & duplex
+ * phy_lookup_setting - lookup a PHY setting
  * @speed: speed to match
  * @duplex: duplex to match
+ * @feature: allowed link modes
+ * @exact: an exact match is required
+ *
+ * Search the settings array for a setting that matches the speed and
+ * duplex, and which is supported.
+ *
+ * If @exact is unset, either an exact match or %NULL for no match will
+ * be returned.
  *
- * Description: Searches the settings array for the setting which
- *   matches the desired speed and duplex, and returns the index
- *   of that setting.  Returns the index of the last setting if
- *   none of the others match.
+ * If @exact is set, an exact match, the fastest supported setting at
+ * or below the specified speed, the slowest supported setting, or if
+ * they all fail, %NULL will be returned.
  */
-static inline unsigned int phy_find_setting(int speed, int duplex)
+static const struct phy_setting *
+phy_lookup_setting(int speed, int duplex, u32 features, bool exact)
 {
-       unsigned int idx = 0;
+       const struct phy_setting *p, *match = NULL, *last = NULL;
+       int i;
+
+       for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
+               if (p->setting & features) {
+                       last = p;
+                       if (p->speed == speed && p->duplex == duplex) {
+                               /* Exact match for speed and duplex */
+                               match = p;
+                               break;
+                       } else if (!exact) {
+                               if (!match && p->speed <= speed)
+                                       /* Candidate */
+                                       match = p;
+
+                               if (p->speed < speed)
+                                       break;
+                       }
+               }
+       }
 
-       while (idx < ARRAY_SIZE(settings) &&
-              (settings[idx].speed != speed || settings[idx].duplex != duplex))
-               idx++;
+       if (!match && !exact)
+               match = last;
 
-       return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
+       return match;
 }
 
 /**
- * phy_find_valid - find a PHY setting that matches the requested features mask
- * @idx: The first index in settings[] to search
- * @features: A mask of the valid settings
+ * phy_find_valid - find a PHY setting that matches the requested parameters
+ * @speed: desired speed
+ * @duplex: desired duplex
+ * @supported: mask of supported link modes
  *
- * Description: Returns the index of the first valid setting less
- *   than or equal to the one pointed to by idx, as determined by
- *   the mask in features.  Returns the index of the last setting
- *   if nothing else matches.
+ * Locate a supported phy setting that is, in priority order:
+ * - an exact match for the specified speed and duplex mode
+ * - a match for the specified speed, or slower speed
+ * - the slowest supported speed
+ * Returns the matched phy_setting entry, or %NULL if no supported phy
+ * settings were found.
  */
-static inline unsigned int phy_find_valid(unsigned int idx, u32 features)
+static const struct phy_setting *
+phy_find_valid(int speed, int duplex, u32 supported)
 {
-       while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features))
-               idx++;
-
-       return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
+       return phy_lookup_setting(speed, duplex, supported, false);
 }
 
 /**
@@ -279,11 +320,9 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
        unsigned int count = 0;
        unsigned int idx = 0;
 
-       while (idx < MAX_NUM_SETTINGS && count < size) {
-               idx = phy_find_valid(idx, phy->supported);
-
+       for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++) {
                if (!(settings[idx].setting & phy->supported))
-                       break;
+                       continue;
 
                /* Assumes settings are grouped by speed */
                if ((count == 0) ||
@@ -291,7 +330,6 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
                        speeds[count] = settings[idx].speed;
                        count++;
                }
-               idx++;
        }
 
        return count;
@@ -308,12 +346,7 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
  */
 static inline bool phy_check_valid(int speed, int duplex, u32 features)
 {
-       unsigned int idx;
-
-       idx = phy_find_valid(phy_find_setting(speed, duplex), features);
-
-       return settings[idx].speed == speed && settings[idx].duplex == duplex &&
-               (settings[idx].setting & features);
+       return !!phy_lookup_setting(speed, duplex, features, true);
 }
 
 /**
@@ -326,18 +359,22 @@ static inline bool phy_check_valid(int speed, int duplex, u32 features)
  */
 static void phy_sanitize_settings(struct phy_device *phydev)
 {
+       const struct phy_setting *setting;
        u32 features = phydev->supported;
-       unsigned int idx;
 
        /* Sanitize settings based on PHY capabilities */
        if ((features & SUPPORTED_Autoneg) == 0)
                phydev->autoneg = AUTONEG_DISABLE;
 
-       idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex),
-                       features);
-
-       phydev->speed = settings[idx].speed;
-       phydev->duplex = settings[idx].duplex;
+       setting = phy_find_valid(phydev->speed, phydev->duplex, features);
+       if (setting) {
+               phydev->speed = setting->speed;
+               phydev->duplex = setting->duplex;
+       } else {
+               /* We failed to find anything (no supported speeds?) */
+               phydev->speed = SPEED_UNKNOWN;
+               phydev->duplex = DUPLEX_UNKNOWN;
+       }
 }
 
 /**
@@ -681,7 +718,7 @@ void phy_stop_machine(struct phy_device *phydev)
        cancel_delayed_work_sync(&phydev->state_queue);
 
        mutex_lock(&phydev->lock);
-       if (phydev->state > PHY_UP)
+       if (phydev->state > PHY_UP && phydev->state != PHY_HALTED)
                phydev->state = PHY_UP;
        mutex_unlock(&phydev->lock);
 }
@@ -1208,15 +1245,8 @@ int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable)
                return -EIO;
 
        /* According to 802.3az,the EEE is supported only in full duplex-mode.
-        * Also EEE feature is active when core is operating with MII, GMII
-        * or RGMII (all kinds). Internal PHYs are also allowed to proceed and
-        * should return an error if they do not support EEE.
         */
-       if ((phydev->duplex == DUPLEX_FULL) &&
-           ((phydev->interface == PHY_INTERFACE_MODE_MII) ||
-           (phydev->interface == PHY_INTERFACE_MODE_GMII) ||
-            phy_interface_is_rgmii(phydev) ||
-            phy_is_internal(phydev))) {
+       if (phydev->duplex == DUPLEX_FULL) {
                int eee_lp, eee_cap, eee_adv;
                u32 lp, cap, adv;
                int status;
@@ -1332,7 +1362,7 @@ EXPORT_SYMBOL(phy_ethtool_get_eee);
  */
 int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data)
 {
-       int cap, adv;
+       int cap, old_adv, adv, ret;
 
        if (!phydev->drv)
                return -EIO;
@@ -1342,12 +1372,29 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data)
        if (cap < 0)
                return cap;
 
+       old_adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
+       if (old_adv < 0)
+               return old_adv;
+
        adv = ethtool_adv_to_mmd_eee_adv_t(data->advertised) & cap;
 
        /* Mask prohibited EEE modes */
        adv &= ~phydev->eee_broken_modes;
 
-       return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv);
+       if (old_adv != adv) {
+               ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv);
+               if (ret < 0)
+                       return ret;
+
+               /* Restart autonegotiation so the new modes get sent to the
+                * link partner.
+                */
+               ret = genphy_restart_aneg(phydev);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
 }
 EXPORT_SYMBOL(phy_ethtool_set_eee);