]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: phy: microchip: add downshift tunable support for LAN88xx
authorNicolai Buchwitz <nb@tipi-net.de>
Wed, 1 Apr 2026 12:38:44 +0000 (14:38 +0200)
committerJakub Kicinski <kuba@kernel.org>
Fri, 3 Apr 2026 01:03:03 +0000 (18:03 -0700)
Implement the standard ETHTOOL_PHY_DOWNSHIFT tunable for the LAN88xx
PHY. This allows runtime configuration of the auto-downshift feature
via ethtool:

  ethtool --set-phy-tunable eth0 downshift on count 3

The LAN88xx PHY supports downshifting from 1000BASE-T to 100BASE-TX
after 2-5 failed auto-negotiation attempts. Valid count values are
2, 3, 4 and 5.

This is based on an earlier downstream implementation by Phil Elwell.

Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Link: https://patch.msgid.link/20260401123848.696766-2-nb@tipi-net.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/phy/microchip.c
include/linux/microchipphy.h

index dc8634e7bcbe71cee0b4d7da14a9b61abc640acb..bc293d2dd130d6775704ce66ef8d8238e6118aeb 100644 (file)
@@ -2,6 +2,7 @@
 /*
  * Copyright (C) 2015 Microchip Technology
  */
+#include <linux/bitfield.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mii.h>
@@ -193,6 +194,67 @@ static void lan88xx_config_TR_regs(struct phy_device *phydev)
                phydev_warn(phydev, "Failed to Set Register[0x1686]\n");
 }
 
+static int lan88xx_get_downshift(struct phy_device *phydev, u8 *data)
+{
+       int val;
+
+       val = phy_read_paged(phydev, 1, LAN78XX_PHY_CTRL3);
+       if (val < 0)
+               return val;
+
+       if (!(val & LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT)) {
+               *data = DOWNSHIFT_DEV_DISABLE;
+               return 0;
+       }
+
+       *data = FIELD_GET(LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK, val) + 2;
+
+       return 0;
+}
+
+static int lan88xx_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+       u32 mask = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK |
+                  LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT;
+
+       if (cnt == DOWNSHIFT_DEV_DISABLE)
+               return phy_modify_paged(phydev, 1, LAN78XX_PHY_CTRL3,
+                                       LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT, 0);
+
+       if (cnt == DOWNSHIFT_DEV_DEFAULT_COUNT)
+               cnt = 2;
+
+       if (cnt < 2 || cnt > 5)
+               return -EINVAL;
+
+       return phy_modify_paged(phydev, 1, LAN78XX_PHY_CTRL3, mask,
+                               FIELD_PREP(LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK,
+                                          cnt - 2) |
+                               LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT);
+}
+
+static int lan88xx_get_tunable(struct phy_device *phydev,
+                              struct ethtool_tunable *tuna, void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_DOWNSHIFT:
+               return lan88xx_get_downshift(phydev, data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int lan88xx_set_tunable(struct phy_device *phydev,
+                              struct ethtool_tunable *tuna, const void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_DOWNSHIFT:
+               return lan88xx_set_downshift(phydev, *(const u8 *)data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
 static int lan88xx_probe(struct phy_device *phydev)
 {
        struct device *dev = &phydev->mdio.dev;
@@ -499,6 +561,8 @@ static struct phy_driver microchip_phy_driver[] = {
        .set_wol        = lan88xx_set_wol,
        .read_page      = lan88xx_read_page,
        .write_page     = lan88xx_write_page,
+       .get_tunable    = lan88xx_get_tunable,
+       .set_tunable    = lan88xx_set_tunable,
 },
 {
        PHY_ID_MATCH_MODEL(PHY_ID_LAN937X_TX),
index 517288da19fd3d18a9009bf031150854641222c8..7da956c666a081fccae7182b6d10d85d5d311064 100644 (file)
 /* Registers specific to the LAN7800/LAN7850 embedded phy */
 #define LAN78XX_PHY_LED_MODE_SELECT            (0x1D)
 
+/* PHY Control 3 register (page 1) */
+#define LAN78XX_PHY_CTRL3                      (0x14)
+#define  LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT      BIT(4)
+#define  LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK GENMASK(3, 2)
+
 /* DSP registers */
 #define PHY_ARDENNES_MMD_DEV_3_PHY_CFG         (0x806A)
 #define PHY_ARDENNES_MMD_DEV_3_PHY_CFG_ZD_DLY_EN_      (0x2000)