summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYongzhao Chen <yongzhao.derek@gmail.com>2026-10-13 21:03:39 +0200
committerPaolo Abeni <pabeni@redhat.com>2026-10-20 12:03:31 +0200
commit42a91cbfc7a3ab99d05cb2ed0de5a24b9652cfed (patch)
treeadd86d5e0fdcdfc255002c10393e79c2c7d61e26
parentd06ae66516e1aaf023808912af6292aef09d46c3 (diff)
net: phy: qca83xx: read resolved QCA8337 link status
After a QCA8337 SmartSpeed downshift, genphy_read_status() can report 1000 Mbit/s from the advertised modes when the PHY is running at 100 Mbit/s. On a Redmi AX5400 running downstream OpenWrt Linux 6.18.52, this caused qca8k to program the user-port MAC for 1000 Mbit/s and traffic failed. An earlier prototype that read the resolved speed from the PHY restored traffic. Wrap genphy_read_status() and use at803x_read_specific_status() to replace its speed and duplex result when an autonegotiated link comes up. Resolve pause for the resulting duplex. Until the PHY reports a resolved speed and duplex, report the link as down so that the next poll reads them again, as marvell_read_status_page_an() does. In that case also clear the duplex, master/slave state and link partner modes that genphy_read_status() read for an up link. QCA8337's register layout matches the helper (data sheet 80-Y0619-3 Rev. D, pages 327-328). Keep the generic master/slave read order and the early return for a steady autonegotiated link. This avoids additional reads of the clear-on-read idle error count in MII_STAT1000. Forced mode continues to use BMCR. Report the MDI-X state read at link-up and invalidate it when the link goes down or autonegotiation is disabled. Clear mdix_ctrl because this driver does not implement MDI-X configuration. The QCA8327 entries keep genphy_read_status(). The AR8327 data sheet (MKG-15791 Rev. 1.0) lists the QCA8327-A PHY ID and documents the same PHY-specific status layout, with SmartSpeed enabled after a hardware reset, so those PHYs may have the same problem. That is untested on QCA8327 hardware, so limit this change to QCA8337. Fixes: b3591c2a3661 ("net: dsa: qca8k: Switch to PHYLINK instead of PHYLIB") Suggested-by: Andrew Lunn <andrew@lunn.ch> Signed-off-by: Yongzhao Chen <yongzhao.derek@gmail.com> Assisted-by: LLM Link: https://patch.msgid.link/20261013190339.51909-1-yongzhao.derek@gmail.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
-rw-r--r--drivers/net/phy/qcom/qca83xx.c58
1 files changed, 58 insertions, 0 deletions
diff --git a/drivers/net/phy/qcom/qca83xx.c b/drivers/net/phy/qcom/qca83xx.c
index bc70ed8efd86..2c4d085bacdb 100644
--- a/drivers/net/phy/qcom/qca83xx.c
+++ b/drivers/net/phy/qcom/qca83xx.c
@@ -92,6 +92,63 @@ static int qca83xx_probe(struct phy_device *phydev)
return 0;
}
+static int qca8337_read_status(struct phy_device *phydev)
+{
+ struct at803x_ss_mask ss_mask = {
+ .speed_mask = AT803X_SS_SPEED_MASK,
+ .speed_shift = __bf_shf(AT803X_SS_SPEED_MASK),
+ };
+ int ret, old_link = phydev->link;
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ /* Keep the state resolved at link-up while the link stays up */
+ if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+ goto out;
+
+ phydev->mdix = ETH_TP_MDI_INVALID;
+
+ /* genphy_read_status() derives speed and duplex from the
+ * advertisements, which is wrong after a SmartSpeed downshift.
+ * Use what the PHY resolved instead.
+ */
+ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete &&
+ phydev->link) {
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = false;
+ phydev->asym_pause = false;
+
+ ret = at803x_read_specific_status(phydev, ss_mask);
+ if (ret)
+ return ret;
+
+ /* Report the link down until speed and duplex are resolved,
+ * so that the next poll reads them again. Also clear what
+ * genphy_read_status() read for an up link, as the
+ * PHY_HALTED path of the state machine does.
+ */
+ if (phydev->speed == SPEED_UNKNOWN ||
+ phydev->duplex == DUPLEX_UNKNOWN) {
+ phydev->link = 0;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->mdix = ETH_TP_MDI_INVALID;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+ linkmode_zero(phydev->lp_advertising);
+ } else {
+ phy_resolve_aneg_pause(phydev);
+ }
+ }
+
+out:
+ /* MDI-X configuration is not implemented */
+ phydev->mdix_ctrl = ETH_TP_MDI_INVALID;
+
+ return 0;
+}
+
static int qca83xx_config_init(struct phy_device *phydev)
{
u8 switch_revision;
@@ -220,6 +277,7 @@ static struct phy_driver qca83xx_driver[] = {
.flags = PHY_IS_INTERNAL,
.config_init = qca83xx_config_init,
.soft_reset = genphy_soft_reset,
+ .read_status = qca8337_read_status,
.get_sset_count = qca83xx_get_sset_count,
.get_strings = qca83xx_get_strings,
.get_stats = qca83xx_get_stats,