* [PATCH 1/2] net: phy: microchip: add downshift tunable support for LAN88xx
2026-03-29 22:41 [PATCH net-next 0/2] net: phy: microchip: add downshift support for LAN88xx Nicolai Buchwitz
@ 2026-03-29 22:41 ` Nicolai Buchwitz
2026-03-29 22:42 ` [PATCH 2/2] net: phy: microchip: enable downshift by default on LAN88xx Nicolai Buchwitz
1 sibling, 0 replies; 3+ messages in thread
From: Nicolai Buchwitz @ 2026-03-29 22:41 UTC (permalink / raw)
To: netdev
Cc: Phil Elwell, Nicolai Buchwitz, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-kernel
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>
---
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
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH 2/2] net: phy: microchip: enable downshift by default on LAN88xx
2026-03-29 22:41 [PATCH net-next 0/2] net: phy: microchip: add downshift support for LAN88xx Nicolai Buchwitz
2026-03-29 22:41 ` [PATCH 1/2] net: phy: microchip: add downshift tunable " Nicolai Buchwitz
@ 2026-03-29 22:42 ` Nicolai Buchwitz
1 sibling, 0 replies; 3+ messages in thread
From: Nicolai Buchwitz @ 2026-03-29 22:42 UTC (permalink / raw)
To: netdev
Cc: Phil Elwell, Nicolai Buchwitz, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-kernel
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.
Users can override or disable downshift at runtime:
ethtool --set-phy-tunable eth0 downshift off
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/phy/microchip.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c
index a044be1ade79..3455fe713088 100644
--- a/drivers/net/phy/microchip.c
+++ b/drivers/net/phy/microchip.c
@@ -371,7 +371,7 @@ static void lan88xx_set_mdix(struct phy_device *phydev)
static int lan88xx_config_init(struct phy_device *phydev)
{
- int val;
+ int val, err;
/*Zerodetect delay enable */
val = phy_read_mmd(phydev, MDIO_MMD_PCS,
@@ -384,6 +384,11 @@ static int lan88xx_config_init(struct phy_device *phydev)
/* Config DSP registers */
lan88xx_config_TR_regs(phydev);
+ /* Enable downshift after 2 failed attempts by default */
+ err = lan88xx_set_downshift(phydev, 2);
+ if (err < 0)
+ return err;
+
return 0;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 3+ messages in thread