From: Kenneth Kasilag <kenneth@kasilag.me>
Date: Tue, 30 Jun 2026 00:00:00 +0000
Subject: [PATCH] net: pcs: qcom-ipq9574: complete USXGMII in-band AN

The XPCS signals USXGMII in-band autonegotiation completion through an
interrupt that is never enabled, waited for or acknowledged, so rate
adaptation never starts and the MAC receives nothing even though the
SerDes achieves block lock. The advertised speed and duplex are also
left at their reset values while autonegotiation runs.

Follow the vendor SSDK sequence: enable the completion interrupt and
advertise 10G/full duplex when configuring the PCS, and on link-up
restart the in-band autonegotiation, wait for completion and
acknowledge it before programming the negotiated speed. The wait is
best-effort, as in the vendor driver.

Signed-off-by: Kenneth Kasilag <kenneth@kasilag.me>
---
 drivers/net/pcs/pcs-qcom-ipq9574.c | 34 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
+++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
@@ -95,6 +95,7 @@
 #define XPCS_MII_CTRL			0x1f0000
 #define XPCS_MII1_CTRL(x)		(0x1a0000 + 0x10000 * ((x) - 1))
 #define XPCS_MII_AN_EN			BIT(12)
+#define XPCS_MII_AN_RESTART		BIT(9)
 #define XPCS_DUPLEX_FULL		BIT(8)
 #define XPCS_SPEED_MASK			(BIT(13) | BIT(6) | BIT(5))
 #define XPCS_SPEED_10000		(BIT(13) | BIT(6))
@@ -107,10 +108,12 @@
 #define XPCS_MII_AN_CTRL		0x1f8001
 #define XPCS_MII1_AN_CTRL(x)		(0x1a8001 + 0x10000 * ((x) - 1))
 #define XPCS_MII_AN_8BIT		BIT(8)
+#define XPCS_MII_AN_INTR_EN		BIT(0)
 
 #define XPCS_MII_AN_INTR_STS		0x1f8002
 #define XPCS_MII1_AN_INTR_STS(x)	(0x1a8002 + 0x10000 * ((x) - 1))
 #define XPCS_USXG_AN_LINK_STS		BIT(14)
+#define XPCS_MII_AN_CL37_CMPLT		BIT(0)
 #define XPCS_USXG_AN_SPEED_MASK		GENMASK(12, 10)
 #define XPCS_USXG_AN_SPEED_10		0
 #define XPCS_USXG_AN_SPEED_100		1
@@ -472,12 +475,16 @@ static int ipq_pcs_config_usxgmii(struct
 	}
 
 	reg = (index == 0) ? XPCS_MII_AN_CTRL : XPCS_MII1_AN_CTRL(index);
-	ret = regmap_set_bits(qpcs->regmap, reg, XPCS_MII_AN_8BIT);
+	ret = regmap_set_bits(qpcs->regmap, reg,
+			      XPCS_MII_AN_8BIT | XPCS_MII_AN_INTR_EN);
 	if (ret)
 		return ret;
 
 	reg = (index == 0) ? XPCS_MII_CTRL : XPCS_MII1_CTRL(index);
-	return regmap_set_bits(qpcs->regmap, reg, XPCS_MII_AN_EN);
+	/* Advertise 10G/full while AN runs; link-up applies the result */
+	return regmap_update_bits(qpcs->regmap, reg,
+				  XPCS_MII_AN_EN | XPCS_SPEED_MASK | XPCS_DUPLEX_FULL,
+				  XPCS_MII_AN_EN | XPCS_SPEED_10000 | XPCS_DUPLEX_FULL);
 }
 
 static int ipq_pcs_config_10gbaser(struct ipq_pcs *qpcs)
@@ -553,6 +560,23 @@ static int ipq_pcs_link_up_config_usxgmi
 	unsigned int reg, val;
 	int ret;
 
+	/* Rate adaptation is held off until the AN completion is acked:
+	 * restart in-band AN, wait for it and ack it.
+	 */
+	reg = (index == 0) ? XPCS_MII_CTRL : XPCS_MII1_CTRL(index);
+	ret = regmap_set_bits(qpcs->regmap, reg, XPCS_MII_AN_RESTART);
+	if (ret)
+		return ret;
+
+	reg = (index == 0) ? XPCS_MII_AN_INTR_STS : XPCS_MII1_AN_INTR_STS(index);
+	ret = regmap_read_poll_timeout(qpcs->regmap, reg, val,
+				       val & XPCS_MII_AN_CL37_CMPLT,
+				       1000, 100000);
+	if (ret)
+		dev_warn(qpcs->dev, "USXGMII in-band AN did not complete\n");
+	else
+		regmap_clear_bits(qpcs->regmap, reg, XPCS_MII_AN_CL37_CMPLT);
+
 	switch (speed) {
 	case SPEED_10000:
 		val = XPCS_SPEED_10000;
