Commit 3c78f91e authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-phy-smsc-robustness-fixes-for-lan87xx-lan9500'

Oleksij Rempel says:

====================
net: phy: smsc: robustness fixes for LAN87xx/LAN9500

The SMSC 10/100 PHYs (LAN87xx family) found in smsc95xx (lan95xx)
USB-Ethernet adapters show several quirks around the Auto-MDIX feature:

- A hardware strap (AUTOMDIX_EN) may boot the PHY in fixed-MDI mode, and
  the current driver cannot always override it.

- When Auto-MDIX is left enabled while autonegotiation is forced off,
  the PHY endlessly swaps the TX/RX pairs and never links up.

- The driver sets the enable bit for Auto-MDIX but forgets the override
  bit, so userspace requests are silently ignored.

- Rapid configuration changes can wedge the link if PHY IRQs are
  enabled.

The four patches below make the MDIX state fully predictable and prevent
link failures in every tested strap / autoneg / MDI-X permutation.

Tested on LAN9512 Eval board.
====================

Link: https://patch.msgid.link/20250703114941.3243890-1-o.rempel@pengutronix.de


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 78b7920a 9dfe110c
Loading
Loading
Loading
Loading
+52 −5
Original line number Diff line number Diff line
@@ -155,10 +155,29 @@ static int smsc_phy_reset(struct phy_device *phydev)

static int lan87xx_config_aneg(struct phy_device *phydev)
{
	int rc;
	u8 mdix_ctrl;
	int val;
	int rc;

	/* When auto-negotiation is disabled (forced mode), the PHY's
	 * Auto-MDIX will continue toggling the TX/RX pairs.
	 *
	 * To establish a stable link, we must select a fixed MDI mode.
	 * If the user has not specified a fixed MDI mode (i.e., mdix_ctrl is
	 * 'auto'), we default to ETH_TP_MDI. This choice of a ETH_TP_MDI mode
	 * mirrors the behavior the hardware would exhibit if the AUTOMDIX_EN
	 * strap were configured for a fixed MDI connection.
	 */
	if (phydev->autoneg == AUTONEG_DISABLE) {
		if (phydev->mdix_ctrl == ETH_TP_MDI_AUTO)
			mdix_ctrl = ETH_TP_MDI;
		else
			mdix_ctrl = phydev->mdix_ctrl;
	} else {
		mdix_ctrl = phydev->mdix_ctrl;
	}

	switch (phydev->mdix_ctrl) {
	switch (mdix_ctrl) {
	case ETH_TP_MDI:
		val = SPECIAL_CTRL_STS_OVRRD_AMDIX_;
		break;
@@ -167,7 +186,8 @@ static int lan87xx_config_aneg(struct phy_device *phydev)
			SPECIAL_CTRL_STS_AMDIX_STATE_;
		break;
	case ETH_TP_MDI_AUTO:
		val = SPECIAL_CTRL_STS_AMDIX_ENABLE_;
		val = SPECIAL_CTRL_STS_OVRRD_AMDIX_ |
			SPECIAL_CTRL_STS_AMDIX_ENABLE_;
		break;
	default:
		return genphy_config_aneg(phydev);
@@ -183,7 +203,7 @@ static int lan87xx_config_aneg(struct phy_device *phydev)
	rc |= val;
	phy_write(phydev, SPECIAL_CTRL_STS, rc);

	phydev->mdix = phydev->mdix_ctrl;
	phydev->mdix = mdix_ctrl;
	return genphy_config_aneg(phydev);
}

@@ -261,6 +281,33 @@ int lan87xx_read_status(struct phy_device *phydev)
}
EXPORT_SYMBOL_GPL(lan87xx_read_status);

static int lan87xx_phy_config_init(struct phy_device *phydev)
{
	int rc;

	/* The LAN87xx PHY's initial MDI-X mode is determined by the AUTOMDIX_EN
	 * hardware strap, but the driver cannot read the strap's status. This
	 * creates an unpredictable initial state.
	 *
	 * To ensure consistent and reliable behavior across all boards,
	 * override the strap configuration on initialization and force the PHY
	 * into a known state with Auto-MDIX enabled, which is the expected
	 * default for modern hardware.
	 */
	rc = phy_modify(phydev, SPECIAL_CTRL_STS,
			SPECIAL_CTRL_STS_OVRRD_AMDIX_ |
			SPECIAL_CTRL_STS_AMDIX_ENABLE_ |
			SPECIAL_CTRL_STS_AMDIX_STATE_,
			SPECIAL_CTRL_STS_OVRRD_AMDIX_ |
			SPECIAL_CTRL_STS_AMDIX_ENABLE_);
	if (rc < 0)
		return rc;

	phydev->mdix_ctrl = ETH_TP_MDI_AUTO;

	return smsc_phy_config_init(phydev);
}

static int lan874x_phy_config_init(struct phy_device *phydev)
{
	u16 val;
@@ -695,7 +742,7 @@ static struct phy_driver smsc_phy_driver[] = {

	/* basic functions */
	.read_status	= lan87xx_read_status,
	.config_init	= smsc_phy_config_init,
	.config_init	= lan87xx_phy_config_init,
	.soft_reset	= smsc_phy_reset,
	.config_aneg	= lan87xx_config_aneg,