Commit 0ea7e61f authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

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: default avatarJakub Kicinski <kuba@kernel.org>
parents 86f5dd4e 70180f72
Loading
Loading
Loading
Loading
+78 −1
Original line number Diff line number Diff line
@@ -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),
+5 −0
Original line number Diff line number Diff line
@@ -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)