[PATCH 1/2] net: phy: microchip: add downshift tunable support for LAN88xx

From: Nicolai Buchwitz

Date: Sun Mar 29 2026 - 18:44:45 EST


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@xxxxxxxxxxx>
---
drivers/net/phy/microchip.c | 89 ++++++++++++++++++++++++++++++++++++
include/linux/microchipphy.h | 9 ++++
2 files changed, 98 insertions(+)

diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c
index dc8634e7bcbe..a044be1ade79 100644
--- a/drivers/net/phy/microchip.c
+++ b/drivers/net/phy/microchip.c
@@ -193,6 +193,93 @@ 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;
+ }
+
+ switch (val & LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK) {
+ case LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_2:
+ *data = 2;
+ break;
+ case LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_3:
+ *data = 3;
+ break;
+ case LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_4:
+ *data = 4;
+ break;
+ case LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_5:
+ *data = 5;
+ break;
+ }
+
+ 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;
+ u32 val;
+
+ 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;
+
+ switch (cnt) {
+ case 2:
+ val = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_2;
+ break;
+ case 3:
+ val = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_3;
+ break;
+ case 4:
+ val = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_4;
+ break;
+ case 5:
+ val = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_5;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return phy_modify_paged(phydev, 1, LAN78XX_PHY_CTRL3, mask,
+ val | 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 +586,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),
diff --git a/include/linux/microchipphy.h b/include/linux/microchipphy.h
index 517288da19fd..a8deae3977e9 100644
--- a/include/linux/microchipphy.h
+++ b/include/linux/microchipphy.h
@@ -61,6 +61,15 @@
/* 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)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_2 (0 << 2)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_3 (1 << 2)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_4 (2 << 2)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_5 (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)
--
2.51.0