From e417ac73d24ae68b8dd6a1b02f9db03a7a5c184b Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Wed, 1 Apr 2026 14:38:44 +0200 Subject: [PATCH 1/2] net: phy: microchip: add downshift tunable support for LAN88xx 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 Reviewed-by: Andrew Lunn Reviewed-by: Russell King (Oracle) Link: https://patch.msgid.link/20260401123848.696766-2-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/microchip.c | 64 ++++++++++++++++++++++++++++++++++++ include/linux/microchipphy.h | 5 +++ 2 files changed, 69 insertions(+) diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c index dc8634e7bcbe..bc293d2dd130 100644 --- a/drivers/net/phy/microchip.c +++ b/drivers/net/phy/microchip.c @@ -2,6 +2,7 @@ /* * Copyright (C) 2015 Microchip Technology */ +#include #include #include #include @@ -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), diff --git a/include/linux/microchipphy.h b/include/linux/microchipphy.h index 517288da19fd..7da956c666a0 100644 --- a/include/linux/microchipphy.h +++ b/include/linux/microchipphy.h @@ -61,6 +61,11 @@ /* 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) From 70180f72d91144f4c7b308ffa87bbf3592cd8d65 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Wed, 1 Apr 2026 14:38:45 +0200 Subject: [PATCH 2/2] net: phy: microchip: enable downshift by default on LAN88xx Enable auto-downshift from 1000BASE-T to 100BASE-TX after 2 failed auto-negotiation attempts by default. This ensures that links with faulty or missing cable pairs (C and D) fall back to 100Mbps without requiring userspace configuration. The downshift count is stored in the driver's private data and applied in config_init, so user changes via ethtool are preserved across suspend/resume cycles. Users can override or disable downshift at runtime: ethtool --set-phy-tunable eth0 downshift off Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260401123848.696766-3-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/microchip.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c index bc293d2dd130..33bc633bdba0 100644 --- a/drivers/net/phy/microchip.c +++ b/drivers/net/phy/microchip.c @@ -26,6 +26,7 @@ struct lan88xx_priv { int chip_id; int chip_rev; __u32 wolopts; + u8 downshift_cnt; }; static int lan88xx_read_page(struct phy_device *phydev) @@ -247,9 +248,15 @@ static int lan88xx_get_tunable(struct phy_device *phydev, static int lan88xx_set_tunable(struct phy_device *phydev, struct ethtool_tunable *tuna, const void *data) { + struct lan88xx_priv *priv = phydev->priv; + int ret; + switch (tuna->id) { case ETHTOOL_PHY_DOWNSHIFT: - return lan88xx_set_downshift(phydev, *(const u8 *)data); + ret = lan88xx_set_downshift(phydev, *(const u8 *)data); + if (!ret) + priv->downshift_cnt = *(const u8 *)data; + return ret; default: return -EOPNOTSUPP; } @@ -267,6 +274,7 @@ static int lan88xx_probe(struct phy_device *phydev) return -ENOMEM; priv->wolopts = 0; + priv->downshift_cnt = 2; len = of_property_read_variable_u32_array(dev->of_node, "microchip,led-modes", @@ -346,7 +354,8 @@ static void lan88xx_set_mdix(struct phy_device *phydev) static int lan88xx_config_init(struct phy_device *phydev) { - int val; + struct lan88xx_priv *priv = phydev->priv; + int val, err; /*Zerodetect delay enable */ val = phy_read_mmd(phydev, MDIO_MMD_PCS, @@ -359,6 +368,10 @@ static int lan88xx_config_init(struct phy_device *phydev) /* Config DSP registers */ lan88xx_config_TR_regs(phydev); + err = lan88xx_set_downshift(phydev, priv->downshift_cnt); + if (err < 0) + return err; + return 0; }