From d96ec0527b0f5618b3a0757b47606705555ee996 Mon Sep 17 00:00:00 2001
From: Lei Wei <quic_leiwei@quicinc.com>
Date: Mon, 15 Apr 2024 11:06:02 +0800
Subject: [PATCH 15/50] net:pcs: Add 10G_QXGMII interface mode support to IPQ
 UNIPHY PCS driver

10G_QXGMII is used when PCS connectes with QCA8084 four ports
2.5G PHYs.

Change-Id: If3dc92a07ac3e51f7c9473fb05fa0668617916fb
Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
---
 drivers/net/pcs/pcs-qcom-ipq-uniphy.c | 174 +++++++++++++++++++++-----
 1 file changed, 142 insertions(+), 32 deletions(-)

--- a/drivers/net/pcs/pcs-qcom-ipq-uniphy.c
+++ b/drivers/net/pcs/pcs-qcom-ipq-uniphy.c
@@ -50,6 +50,9 @@
 #define PCS_CHANNEL_STS_PAUSE_TX_EN	BIT(1)
 #define PCS_CHANNEL_STS_PAUSE_RX_EN	BIT(0)
 
+#define PCS_QP_USXG_OPTION		0x584
+#define PCS_QP_USXG_GMII_SRC_XPCS	BIT(0)
+
 #define PCS_PLL_RESET			0x780
 #define PCS_ANA_SW_RESET		BIT(6)
 
@@ -65,10 +68,22 @@
 #define XPCS_10GBASER_LINK_STS		BIT(12)
 
 #define XPCS_DIG_CTRL			0x38000
+#define XPCS_SOFT_RESET			BIT(15)
 #define XPCS_USXG_ADPT_RESET		BIT(10)
 #define XPCS_USXG_EN			BIT(9)
 
+#define XPCS_KR_CTRL			0x38007
+#define XPCS_USXG_MODE_MASK		GENMASK(12, 10)
+#define XPCS_10G_QXGMII_MODE		FIELD_PREP(XPCS_USXG_MODE_MASK, 0x5)
+
+#define XPCS_DIG_STS			0x3800a
+#define XPCS_DIG_STS_AM_COUNT		GENMASK(14, 0)
+
+#define XPCS_CHANNEL_DIG_CTRL(x)	(0x1a8000 + 0x10000 * ((x) - 1))
+#define XPCS_CHANNEL_USXG_ADPT_RESET	BIT(5)
+
 #define XPCS_MII_CTRL			0x1f0000
+#define XPCS_CHANNEL_MII_CTRL(x)	(0x1a0000 + 0x10000 * ((x) - 1))
 #define XPCS_MII_AN_EN			BIT(12)
 #define XPCS_DUPLEX_FULL		BIT(8)
 #define XPCS_SPEED_MASK			(BIT(13) | BIT(6) | BIT(5))
@@ -80,9 +95,11 @@
 #define XPCS_SPEED_10			0
 
 #define XPCS_MII_AN_CTRL		0x1f8001
+#define XPCS_CHANNEL_MII_AN_CTRL(x)	(0x1a8001 + 0x10000 * ((x) - 1))
 #define XPCS_MII_AN_8BIT		BIT(8)
 
 #define XPCS_MII_AN_INTR_STS		0x1f8002
+#define XPCS_CHANNEL_MII_AN_INTR_STS(x)	(0x1a8002 + 0x10000 * ((x) - 1))
 #define XPCS_USXG_AN_LINK_STS		BIT(14)
 #define XPCS_USXG_AN_DUPLEX_FULL	BIT(13)
 #define XPCS_USXG_AN_SPEED_MASK		GENMASK(12, 10)
@@ -93,6 +110,10 @@
 #define XPCS_USXG_AN_SPEED_5000		5
 #define XPCS_USXG_AN_SPEED_10000	3
 
+#define XPCS_XAUI_MODE_CTRL		0x1f8004
+#define XPCS_CHANNEL_XAUI_MODE_CTRL(x)	(0x1a8004 + 0x10000 * ((x) - 1))
+#define XPCS_TX_IPG_CHECK_DIS		BIT(0)
+
 /* UNIPHY PCS RAW clock ID */
 enum {
 	PCS_RAW_RX_CLK = 0,
@@ -153,6 +174,7 @@ struct ipq_uniphy_pcs {
 	struct device *dev;
 	phy_interface_t interface;
 	struct mutex shared_lock; /* Lock to protect shared config */
+	spinlock_t reg_lock; /* Lock for register access */
 	struct clk *clk[PCS_CLK_MAX];
 	struct reset_control *reset[PCS_RESET_MAX];
 	struct ipq_unipcs_raw_clk raw_clk[PCS_RAW_CLK_MAX];
@@ -215,39 +237,55 @@ static const struct clk_ops ipq_unipcs_r
 
 static u32 ipq_unipcs_reg_read32(struct ipq_uniphy_pcs *qunipcs, u32 reg)
 {
+	u32 val;
+
 	/* PCS use direct AHB access while XPCS use indirect AHB access */
 	if (reg >= XPCS_INDIRECT_ADDR) {
+		/* For XPCS, althrough the register is different for different
+		 * channels, but they use the same indirect AHB address to
+		 * access, so add protects here.
+		 */
+		spin_lock(&qunipcs->reg_lock);
+
 		writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
 		       qunipcs->base + XPCS_INDIRECT_AHB_ADDR);
-		return readl(qunipcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
+		val = readl(qunipcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
+
+		spin_unlock(&qunipcs->reg_lock);
+		return val;
 	} else {
 		return readl(qunipcs->base + reg);
 	}
 }
 
-static void ipq_unipcs_reg_write32(struct ipq_uniphy_pcs *qunipcs,
-				   u32 reg, u32 val)
+static void ipq_unipcs_reg_modify32(struct ipq_uniphy_pcs *qunipcs,
+				    u32 reg, u32 mask, u32 set)
 {
+	u32 val;
+
 	if (reg >= XPCS_INDIRECT_ADDR) {
+		spin_lock(&qunipcs->reg_lock);
+
+		/* XPCS read */
 		writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
 		       qunipcs->base + XPCS_INDIRECT_AHB_ADDR);
+		val = readl(qunipcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
+
+		val &= ~mask;
+		val |= set;
+
+		/* XPCS write */
 		writel(val, qunipcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
+
+		spin_unlock(&qunipcs->reg_lock);
 	} else {
+		val = readl(qunipcs->base + reg);
+		val &= ~mask;
+		val |= set;
 		writel(val, qunipcs->base + reg);
 	}
 }
 
-static void ipq_unipcs_reg_modify32(struct ipq_uniphy_pcs *qunipcs,
-				    u32 reg, u32 mask, u32 set)
-{
-	u32 val;
-
-	val = ipq_unipcs_reg_read32(qunipcs, reg);
-	val &= ~mask;
-	val |= set;
-	ipq_unipcs_reg_write32(qunipcs, reg, val);
-}
-
 static void ipq_unipcs_get_state_sgmii(struct ipq_uniphy_pcs *qunipcs,
 				       int channel,
 				       struct phylink_link_state *state)
@@ -305,11 +343,15 @@ static void ipq_unipcs_get_state_2500bas
 }
 
 static void ipq_unipcs_get_state_usxgmii(struct ipq_uniphy_pcs *qunipcs,
+					 int channel,
 					 struct phylink_link_state *state)
 {
-	u32 val;
+	u32 val, reg;
+
+	reg = (channel == 0) ? XPCS_MII_AN_INTR_STS :
+		XPCS_CHANNEL_MII_AN_INTR_STS(channel);
 
-	val = ipq_unipcs_reg_read32(qunipcs, XPCS_MII_AN_INTR_STS);
+	val = ipq_unipcs_reg_read32(qunipcs, reg);
 
 	state->link = !!(val & XPCS_USXG_AN_LINK_STS);
 
@@ -415,6 +457,15 @@ static int ipq_unipcs_config_mode(struct
 					PCS_MODE_SEL_MASK,
 					PCS_MODE_XPCS);
 		break;
+	case PHY_INTERFACE_MODE_10G_QXGMII:
+		rate = 312500000;
+		ipq_unipcs_reg_modify32(qunipcs, PCS_MODE_CTRL,
+					PCS_MODE_SEL_MASK,
+					PCS_MODE_XPCS);
+		ipq_unipcs_reg_modify32(qunipcs, PCS_QP_USXG_OPTION,
+					PCS_QP_USXG_GMII_SRC_XPCS,
+					PCS_QP_USXG_GMII_SRC_XPCS);
+		break;
 	default:
 		dev_err(qunipcs->dev,
 			"interface %s not supported\n", phy_modes(interface));
@@ -502,35 +553,82 @@ static int ipq_unipcs_config_2500basex(s
 }
 
 static int ipq_unipcs_config_usxgmii(struct ipq_uniphy_pcs *qunipcs,
+				     int channel,
 				     unsigned int neg_mode,
 				     phy_interface_t interface)
 {
 	int ret;
+	u32 reg;
+
+	/* Only in-band autoneg mode is supported currently */
+	if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED)
+		return -EOPNOTSUPP;
+
+	if (interface == PHY_INTERFACE_MODE_10G_QXGMII)
+		mutex_lock(&qunipcs->shared_lock);
 
 	if (qunipcs->interface != interface) {
 		ret = ipq_unipcs_config_mode(qunipcs, interface);
 		if (ret)
-			return ret;
+			goto err;
 
-		/* Deassert XPCS and configure XPCS USXGMII */
+		/* Deassert XPCS and configure XPCS USXGMII or 10G_QXGMII */
 		reset_control_deassert(qunipcs->reset[XPCS_RESET]);
 
 		ipq_unipcs_reg_modify32(qunipcs, XPCS_DIG_CTRL,
 					XPCS_USXG_EN, XPCS_USXG_EN);
 
-		if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
-			ipq_unipcs_reg_modify32(qunipcs, XPCS_MII_AN_CTRL,
-						XPCS_MII_AN_8BIT,
-						XPCS_MII_AN_8BIT);
-
-			ipq_unipcs_reg_modify32(qunipcs, XPCS_MII_CTRL,
-						XPCS_MII_AN_EN, XPCS_MII_AN_EN);
+		if (interface == PHY_INTERFACE_MODE_10G_QXGMII) {
+			ipq_unipcs_reg_modify32(qunipcs, XPCS_KR_CTRL,
+						XPCS_USXG_MODE_MASK,
+						XPCS_10G_QXGMII_MODE);
+
+			/* Set Alignment Marker Interval */
+			ipq_unipcs_reg_modify32(qunipcs, XPCS_DIG_STS,
+						XPCS_DIG_STS_AM_COUNT,
+						0x6018);
+
+			ipq_unipcs_reg_modify32(qunipcs, XPCS_DIG_CTRL,
+						XPCS_SOFT_RESET,
+						XPCS_SOFT_RESET);
 		}
 
 		qunipcs->interface = interface;
 	}
 
+	if (interface == PHY_INTERFACE_MODE_10G_QXGMII)
+		mutex_unlock(&qunipcs->shared_lock);
+
+	/* Disable Tx IPG check for 10G_QXGMII */
+	if (interface == PHY_INTERFACE_MODE_10G_QXGMII) {
+		reg = (channel == 0) ? XPCS_XAUI_MODE_CTRL :
+			XPCS_CHANNEL_XAUI_MODE_CTRL(channel);
+
+		ipq_unipcs_reg_modify32(qunipcs, reg,
+					XPCS_TX_IPG_CHECK_DIS,
+					XPCS_TX_IPG_CHECK_DIS);
+	}
+
+	/* Enable autoneg */
+	reg = (channel == 0) ? XPCS_MII_AN_CTRL :
+		XPCS_CHANNEL_MII_AN_CTRL(channel);
+
+	ipq_unipcs_reg_modify32(qunipcs, reg,
+				XPCS_MII_AN_8BIT, XPCS_MII_AN_8BIT);
+
+	reg = (channel == 0) ? XPCS_MII_CTRL :
+		XPCS_CHANNEL_MII_CTRL(channel);
+
+	ipq_unipcs_reg_modify32(qunipcs, reg,
+				XPCS_MII_AN_EN, XPCS_MII_AN_EN);
+
 	return 0;
+
+err:
+	if (interface == PHY_INTERFACE_MODE_10G_QXGMII)
+		mutex_unlock(&qunipcs->shared_lock);
+
+	return ret;
 }
 
 static int ipq_unipcs_config_10gbaser(struct ipq_uniphy_pcs *qunipcs,
@@ -638,6 +736,7 @@ ipq_unipcs_link_up_clock_rate_set(struct
 		break;
 	case PHY_INTERFACE_MODE_USXGMII:
 	case PHY_INTERFACE_MODE_10GBASER:
+	case PHY_INTERFACE_MODE_10G_QXGMII:
 		rate = ipq_unipcs_clock_rate_get_xgmii(speed);
 		break;
 	default:
@@ -713,9 +812,10 @@ static void ipq_unipcs_link_up_config_25
 }
 
 static void ipq_unipcs_link_up_config_usxgmii(struct ipq_uniphy_pcs *qunipcs,
+					      int channel,
 					      int speed)
 {
-	u32 val;
+	u32 val, reg;
 
 	switch (speed) {
 	case SPEED_10000:
@@ -744,14 +844,20 @@ static void ipq_unipcs_link_up_config_us
 	val |= XPCS_DUPLEX_FULL;
 
 	/* Config XPCS speed */
-	ipq_unipcs_reg_modify32(qunipcs, XPCS_MII_CTRL,
+	reg = (channel == 0) ? XPCS_MII_CTRL : XPCS_CHANNEL_MII_CTRL(channel);
+	ipq_unipcs_reg_modify32(qunipcs, reg,
 				XPCS_SPEED_MASK | XPCS_DUPLEX_FULL,
 				val);
 
 	/* XPCS adapter reset */
-	ipq_unipcs_reg_modify32(qunipcs, XPCS_DIG_CTRL,
-				XPCS_USXG_ADPT_RESET,
-				XPCS_USXG_ADPT_RESET);
+	if (channel == 0)
+		ipq_unipcs_reg_modify32(qunipcs, XPCS_DIG_CTRL,
+					XPCS_USXG_ADPT_RESET,
+					XPCS_USXG_ADPT_RESET);
+	else
+		ipq_unipcs_reg_modify32(qunipcs, XPCS_CHANNEL_DIG_CTRL(channel),
+					XPCS_CHANNEL_USXG_ADPT_RESET,
+					XPCS_CHANNEL_USXG_ADPT_RESET);
 }
 
 static int ipq_unipcs_validate(struct phylink_pcs *pcs,
@@ -786,7 +892,8 @@ static void ipq_unipcs_get_state(struct
 		ipq_unipcs_get_state_2500basex(qunipcs, channel, state);
 		break;
 	case PHY_INTERFACE_MODE_USXGMII:
-		ipq_unipcs_get_state_usxgmii(qunipcs, state);
+	case PHY_INTERFACE_MODE_10G_QXGMII:
+		ipq_unipcs_get_state_usxgmii(qunipcs, channel, state);
 		break;
 	case PHY_INTERFACE_MODE_10GBASER:
 		ipq_unipcs_get_state_10gbaser(qunipcs, state);
@@ -823,7 +930,8 @@ static int ipq_unipcs_config(struct phyl
 	case PHY_INTERFACE_MODE_2500BASEX:
 		return ipq_unipcs_config_2500basex(qunipcs, interface);
 	case PHY_INTERFACE_MODE_USXGMII:
-		return ipq_unipcs_config_usxgmii(qunipcs,
+	case PHY_INTERFACE_MODE_10G_QXGMII:
+		return ipq_unipcs_config_usxgmii(qunipcs, channel,
 						 neg_mode, interface);
 	case PHY_INTERFACE_MODE_10GBASER:
 		return ipq_unipcs_config_10gbaser(qunipcs, interface);
@@ -865,7 +973,8 @@ static void ipq_unipcs_link_up(struct ph
 						    channel, speed);
 		break;
 	case PHY_INTERFACE_MODE_USXGMII:
-		ipq_unipcs_link_up_config_usxgmii(qunipcs, speed);
+	case PHY_INTERFACE_MODE_10G_QXGMII:
+		ipq_unipcs_link_up_config_usxgmii(qunipcs, channel, speed);
 		break;
 	case PHY_INTERFACE_MODE_10GBASER:
 		break;
@@ -1082,6 +1191,7 @@ static int ipq_uniphy_probe(struct platf
 		return ret;
 
 	mutex_init(&priv->shared_lock);
+	spin_lock_init(&priv->reg_lock);
 
 	platform_set_drvdata(pdev, priv);
 
