From 0d3a93e3a5544daec59d8f10ac5ccab39849536e Mon Sep 17 00:00:00 2001
From: Lei Wei <quic_leiwei@quicinc.com>
Date: Tue, 2 Apr 2024 18:28:42 +0800
Subject: [PATCH] net: pcs: Add 2500BASEX interface mode support to IPQ UNIPHY
 PCS driver

2500BASEX mode is used when PCS connects with QCA8386 switch in a fixed
2500M link. It is also used when PCS connectes with QCA8081 PHY which
works at 2500M link speed. In addition, it can be also used when PCS
connects with a 2.5G SFP module.

Change-Id: I3fe61113c1b3685debc20659736a9488216a029d
Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
Alex G: use regmap to read/write registers
        's/ipq_unipcs/ipq_pcs/' in function names as suggested by Luo Jie
Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
---
 drivers/net/pcs/pcs-qcom-ipq9574.c | 67 ++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
+++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
@@ -26,6 +26,7 @@
 #define PCS_MODE_SEL_MASK		GENMASK(12, 8)
 #define PCS_MODE_SGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
 #define PCS_MODE_QSGMII			FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
+#define PCS_MODE_2500BASEX		FIELD_PREP(PCS_MODE_SEL_MASK, 0x8)
 #define PCS_MODE_XPCS			FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
 
 #define PCS_MII_CTRL(x)			(0x480 + 0x18 * (x))
@@ -155,6 +156,29 @@ static void ipq_pcs_get_state_sgmii(stru
 		state->duplex = DUPLEX_HALF;
 }
 
+static void ipq_pcs_get_state_2500basex(struct ipq_pcs *qpcs,
+					struct phylink_link_state *state)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(qpcs->regmap, PCS_MII_STS(0), &val);
+	if (ret) {
+		state->link = 0;
+		return;
+	}
+
+
+	state->link = !!(val & PCS_MII_LINK_STS);
+
+	if (!state->link)
+		return;
+
+	state->speed = SPEED_2500;
+	state->duplex = DUPLEX_FULL;
+	state->pause |= MLO_PAUSE_TXRX_MASK;
+}
+
 static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs,
 				      struct phylink_link_state *state)
 {
@@ -236,6 +260,10 @@ static int ipq_pcs_config_mode(struct ip
 	case PHY_INTERFACE_MODE_QSGMII:
 		val = PCS_MODE_QSGMII;
 		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		val = PCS_MODE_2500BASEX;
+		rate = 312500000;
+		break;
 	case PHY_INTERFACE_MODE_USXGMII:
 	case PHY_INTERFACE_MODE_10GBASER:
 		val = PCS_MODE_XPCS;
@@ -314,6 +342,15 @@ static int ipq_pcs_config_sgmii(struct i
 			       PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
 }
 
+static int ipq_pcs_config_2500basex(struct ipq_pcs *qpcs)
+{
+	/* Configure PCS for 2500BASEX mode if required */
+	if (qpcs->interface == PHY_INTERFACE_MODE_2500BASEX)
+		return 0;
+
+	return ipq_pcs_config_mode(qpcs, PHY_INTERFACE_MODE_2500BASEX);
+}
+
 static int ipq_pcs_config_usxgmii(struct ipq_pcs *qpcs)
 {
 	int ret;
@@ -388,6 +425,22 @@ static int ipq_pcs_link_up_config_sgmii(
 			       PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
 }
 
+static int ipq_pcs_link_up_config_2500basex(struct ipq_pcs *qpcs, int speed)
+{
+	int ret;
+
+	/* 2500BASEX does not support autoneg and does not need to
+	 * configure PCS speed. Only reset PCS adapter here.
+	 */
+	ret = regmap_clear_bits(qpcs->regmap,
+				PCS_MII_CTRL(0), PCS_MII_ADPT_RESET);
+	if (ret)
+		return ret;
+
+	return regmap_set_bits(qpcs->regmap,
+			       PCS_MII_CTRL(0), PCS_MII_ADPT_RESET);
+}
+
 static int ipq_pcs_link_up_config_usxgmii(struct ipq_pcs *qpcs, int speed)
 {
 	unsigned int val;
@@ -436,6 +489,10 @@ static int ipq_pcs_validate(struct phyli
 	case PHY_INTERFACE_MODE_QSGMII:
 	case PHY_INTERFACE_MODE_10GBASER:
 		return 0;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		/* In-band autoneg is not supported for 2500BASEX */
+		phylink_clear(supported, Autoneg);
+		return 0;
 	case PHY_INTERFACE_MODE_USXGMII:
 		/* USXGMII only supports full duplex mode */
 		phylink_clear(supported, 100baseT_Half);
@@ -454,6 +511,7 @@ static unsigned int ipq_pcs_inband_caps(
 	case PHY_INTERFACE_MODE_QSGMII:
 	case PHY_INTERFACE_MODE_USXGMII:
 		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+	case PHY_INTERFACE_MODE_2500BASEX:
 	case PHY_INTERFACE_MODE_10GBASER:
 		return LINK_INBAND_DISABLE;
 	default:
@@ -507,6 +565,9 @@ static void ipq_pcs_get_state(struct phy
 	case PHY_INTERFACE_MODE_QSGMII:
 		ipq_pcs_get_state_sgmii(qpcs, index, state);
 		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		ipq_pcs_get_state_2500basex(qpcs, state);
+		break;
 	case PHY_INTERFACE_MODE_USXGMII:
 		ipq_pcs_get_state_usxgmii(qpcs, state);
 		break;
@@ -539,6 +600,8 @@ static int ipq_pcs_config(struct phylink
 	case PHY_INTERFACE_MODE_SGMII:
 	case PHY_INTERFACE_MODE_QSGMII:
 		return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
+	case PHY_INTERFACE_MODE_2500BASEX:
+		return ipq_pcs_config_2500basex(qpcs);
 	case PHY_INTERFACE_MODE_USXGMII:
 		return ipq_pcs_config_usxgmii(qpcs);
 	case PHY_INTERFACE_MODE_10GBASER:
@@ -564,6 +627,9 @@ static void ipq_pcs_link_up(struct phyli
 		ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
 						   neg_mode, speed);
 		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		ret = ipq_pcs_link_up_config_2500basex(qpcs, speed);
+		break;
 	case PHY_INTERFACE_MODE_USXGMII:
 		ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
 		break;
@@ -648,6 +714,7 @@ static int ipq_pcs_create_miis(struct ip
 static unsigned long ipq_pcs_clk_rate_get(struct ipq_pcs *qpcs)
 {
 	switch (qpcs->interface) {
+	case PHY_INTERFACE_MODE_2500BASEX:
 	case PHY_INTERFACE_MODE_USXGMII:
 	case PHY_INTERFACE_MODE_10GBASER:
 		return 312500000;
