Commit e417ac73 authored by Nicolai Buchwitz's avatar Nicolai Buchwitz Committed by Jakub Kicinski
Browse files

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: default avatarNicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Reviewed-by: default avatarRussell King (Oracle) <rmk+kernel@armlinux.org.uk>
Link: https://patch.msgid.link/20260401123848.696766-2-nb@tipi-net.de


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 86f5dd4e
Loading
Loading
Loading
Loading
+64 −0
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>
@@ -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),
+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)