__phy_speed_down provides most of the functionality for phy_speed_down.
It makes use of new helper phy_resolve_min_speed that is based on the
sorting of the settings[] array.
In certain cases it may be helpful to be able to exclude legacy half
duplex modes, therefore prepare phy_resolve_min_speed() for it.

Signed-off-by: Heiner Kallweit <hkallwe...@gmail.com>
---
 drivers/net/phy/phy-core.c | 29 +++++++++++++++++++++++++++++
 include/linux/phy.h        |  1 +
 2 files changed, 30 insertions(+)

diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c
index de085f255..d7331875e 100644
--- a/drivers/net/phy/phy-core.c
+++ b/drivers/net/phy/phy-core.c
@@ -316,6 +316,35 @@ void phy_resolve_aneg_linkmode(struct phy_device *phydev)
 }
 EXPORT_SYMBOL_GPL(phy_resolve_aneg_linkmode);
 
+static int phy_resolve_min_speed(struct phy_device *phydev, bool fdx_only)
+{
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(common);
+       int i = ARRAY_SIZE(settings);
+
+       linkmode_and(common, phydev->lp_advertising, phydev->advertising);
+
+       while (i--) {
+               if (test_bit(settings[i].bit, common)) {
+                       if (fdx_only && settings[i].duplex != DUPLEX_FULL)
+                               continue;
+                       return settings[i].speed;
+               }
+       }
+
+       return SPEED_UNKNOWN;
+}
+
+int __phy_speed_down(struct phy_device *phydev)
+{
+       int min_common_speed = phy_resolve_min_speed(phydev, true);
+
+       if (min_common_speed == SPEED_UNKNOWN)
+               return -EINVAL;
+
+       return __set_linkmode_max_speed(phydev, min_common_speed,
+                                       phydev->advertising);
+}
+
 static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
                             u16 regnum)
 {
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 781f4810c..4be6d3b47 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -665,6 +665,7 @@ size_t phy_speeds(unsigned int *speeds, size_t size,
                  unsigned long *mask);
 void of_set_phy_supported(struct phy_device *phydev);
 void of_set_phy_eee_broken(struct phy_device *phydev);
+int __phy_speed_down(struct phy_device *phydev);
 
 /**
  * phy_is_started - Convenience function to check whether PHY is started
-- 
2.22.0


Reply via email to