Merge branch 'net-phy-microchip-add-downshift-support-for-lan88xx'

Nicolai Buchwitz says:

====================
net: phy: microchip: add downshift support for LAN88xx

Add standard ETHTOOL_PHY_DOWNSHIFT tunable support for the Microchip
LAN88xx PHY, following the same pattern used by Marvell and other PHY
drivers.

Ethernet cables with faulty or missing pairs (specifically C and D)
can successfully auto-negotiate 1000BASE-T but fail to establish a
stable link. The LAN88xx PHY supports automatic downshift to
100BASE-TX after a configurable number of failed attempts (2-5).

Patch 1 adds the get/set tunable implementation.
Patch 2 enables downshift by default with a count of 2. The setting is
stored in the driver's private data so that user changes via ethtool are
preserved across suspend/resume cycles.

Based on an earlier downstream implementation by Phil Elwell.

Tested on Raspberry Pi 3B+ (LAN7515/LAN88xx).
====================

Link: https://patch.msgid.link/20260401123848.696766-1-nb@tipi-net.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-04-02 18:03:07 -07:00
commit 0ea7e61f65
2 changed files with 83 additions and 1 deletions

View 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>
@ -25,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)
@ -193,6 +195,73 @@ 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)
{
struct lan88xx_priv *priv = phydev->priv;
int ret;
switch (tuna->id) {
case ETHTOOL_PHY_DOWNSHIFT:
ret = lan88xx_set_downshift(phydev, *(const u8 *)data);
if (!ret)
priv->downshift_cnt = *(const u8 *)data;
return ret;
default:
return -EOPNOTSUPP;
}
}
static int lan88xx_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
@ -205,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",
@ -284,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,
@ -297,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;
}
@ -499,6 +574,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),

View File

@ -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)