summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/phy/phy.c128
1 files changed, 80 insertions, 48 deletions
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index b6c1bb45b661..1bbf5a168dbc 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -157,107 +157,138 @@ static inline int phy_aneg_done(struct phy_device *phydev)
struct phy_setting {
int speed;
int duplex;
- u32 setting;
+ int bit;
};
-/* A mapping of all SUPPORTED settings to speed/duplex */
+/* A mapping of all SUPPORTED settings to speed/duplex. This table
+ * must be sorted in descending match priority - iow, descending
+ * speed. */
static const struct phy_setting settings[] = {
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_10000baseKR_Full,
+ .bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
},
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_10000baseKX4_Full,
+ .bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
},
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_10000baseT_Full,
+ .bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
},
{
.speed = SPEED_2500,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_2500baseX_Full,
+ .bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_1000baseKX_Full,
+ .bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_1000baseT_Full,
+ .bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_HALF,
- .setting = SUPPORTED_1000baseT_Half,
+ .bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_100baseT_Full,
+ .bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_HALF,
- .setting = SUPPORTED_100baseT_Half,
+ .bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_FULL,
- .setting = SUPPORTED_10baseT_Full,
+ .bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_HALF,
- .setting = SUPPORTED_10baseT_Half,
+ .bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT,
},
};
-#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
+ * @mask: allowed link modes
+ * @maxbit: bit size of 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.
*
- * 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 unset, either an exact match or %NULL for no match will
+ * be returned.
+ *
+ * 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, const unsigned long *mask,
+ size_t maxbit, 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->bit < maxbit && test_bit(p->bit, mask)) {
+ 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;
- while (idx < ARRAY_SIZE(settings) &&
- (settings[idx].speed != speed || settings[idx].duplex != duplex))
- idx++;
+ if (p->speed < speed)
+ break;
+ }
+ }
+ }
+
+ 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++;
+ unsigned long mask = supported;
- return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
+ return phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, false);
}
/**
@@ -271,12 +302,9 @@ static inline unsigned int phy_find_valid(unsigned int idx, u32 features)
*/
static inline bool phy_check_valid(int speed, int duplex, u32 features)
{
- unsigned int idx;
+ unsigned long mask = features;
- 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, &mask, BITS_PER_LONG, true);
}
/**
@@ -289,18 +317,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;
+ }
}
/**