From d137b725f8f4a7d49a809dcd73c5b836495ec44d Mon Sep 17 00:00:00 2001
From: Luo Jie <quic_luoj@quicinc.com>
Date: Mon, 23 Sep 2024 20:59:40 +0800
Subject: [PATCH] net: phy: qca808x: Add QCA8084 SerDes init function

When QCA8084 works on 10G-QXGMII, the XPCS and PCS need to be
configured in the PHY package init function.

Change-Id: Iac48c44f0e80adf055fa9c2095e99a04ba24c4bb
Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
---
 drivers/net/phy/qcom/qca8084_serdes.c | 374 ++++++++++++++++++++++++++
 drivers/net/phy/qcom/qca8084_serdes.h |   2 +
 drivers/net/phy/qcom/qca808x.c        |  11 +
 3 files changed, 387 insertions(+)

--- a/drivers/net/phy/qcom/qca8084_serdes.c
+++ b/drivers/net/phy/qcom/qca8084_serdes.c
@@ -24,6 +24,92 @@
  */
 #define QCA8084_CHANNEL_MAX			4
 
+/* MII registers */
+#define PLL_POWER_ON_AND_RESET			0x0
+#define PCS_ANA_SW_RESET			BIT(6)
+
+#define PLL_CONTROL				6
+#define PLL_CONTROL_CMLDIV2_IBSEL_MASK		GENMASK(5, 4)
+
+/* MMD_PMAPMD registers */
+#define CDR_CONTRL				0x20
+#define SSC_FIX_MODE				BIT(3)
+
+#define CALIBRATION4				0x78
+#define CALIBRATION_DONE			BIT(7)
+
+#define MODE_CONTROL				0x11b
+#define MODE_CONTROL_SEL_MASK			GENMASK(12, 8)
+#define MODE_CONTROL_XPCS			0x10
+#define MODE_CONTROL_SGMII_PLUS			0x8
+#define MODE_CONTROL_SGMII			0x4
+#define MODE_CONTROL_SGMII_SEL_MASK		GENMASK(6, 4)
+#define MODE_CONTROL_SGMII_PHY			1
+#define MODE_CONTROL_SGMII_MAC			2
+
+#define QP_USXG_OPTION1				0x180
+#define QP_USXG_OPTION1_DATAPASS		BIT(0)
+#define QP_USXG_OPTION1_DATAPASS_SGMII		0
+#define QP_USXG_OPTION1_DATAPASS_USXGMII	1
+
+#define BYPASS_TUNNING_IPG			0x189
+#define BYPASS_TUNNING_IPG_MASK			GENMASK(11, 0)
+
+/* MDIO_MMD_PCS register */
+#define PCS_CONTROL2				0x7
+#define PCS_TYPE_MASK				GENMASK(3, 0)
+#define PCS_TYPE_BASER				0
+
+#define PCS_EEE_CONTROL				0x14
+#define EEE_CAPABILITY				BIT(6)
+
+#define PCS_STATUS1				0x20
+#define PCS_BASER_UP				BIT(12)
+
+#define DIG_CTRL1				0x8000
+#define DIG_CTRL1_USXGMII_EN			BIT(9)
+#define DIG_CTRL1_XPCS_RESET			BIT(15)
+#define FIFO_RESET_CH0				BIT(10)
+#define FIFO_RESET_CH1_CH2_CH3			BIT(5)
+
+#define EEE_MODE_CONTROL			0x8006
+#define EEE_LCT_RES				GENMASK(11, 8)
+#define EEE_SIGN				BIT(6)
+#define EEE_LRX_EN				BIT(1)
+#define EEE_LTX_EN				BIT(0)
+
+#define PCS_TPC					0x8007
+#define PCS_QXGMII_MODE_MASK			GENMASK(12, 10)
+#define PCS_QXGMII_EN				0x5
+
+#define EEE_RX_TIMER				0x8009
+#define EEE_RX_TIMER_100US_RES			GENMASK(7, 0)
+#define EEE_RX_TIMER_RWR_RES			GENMASK(12, 8)
+
+#define AM_LINK_TIMER				0x800a
+#define AM_LINK_TIMER_VAL			0x6018
+
+#define EEE_MODE_CONTROL1			0x800b
+#define TRANS_LPI_MODE				BIT(0)
+#define TRANS_RX_LPI_MODE			BIT(8)
+
+/* QXGMII channel MMD register */
+#define MII_CONTROL				0x0
+#define AUTO_NEGOTIATION_EN			BIT(12)
+#define AUTO_NEGOTIATION_RESTART		BIT(9)
+#define PCS_SPEED_2500				BIT(5)
+#define PCS_SPEED_1000				BIT(6)
+#define PCS_SPEED_100				BIT(13)
+#define PCS_SPEED_10				0
+
+#define DIG_CONTROL2				0x8001
+#define MII_BIT_CONTROL				BIT(8)
+#define TX_CONFIG				BIT(3)
+#define AUTO_NEGOTIATION_CMPLT_INTR		BIT(0)
+
+#define XAUI_CONTROL				0x8004
+#define TX_IPG_CHECK_DISABLE			BIT(0)
+
 enum pcs_clk_id {
 	PCS_CLK,
 	PCS_RX_ROOT_CLK,
@@ -76,6 +162,8 @@ static const char *const pcs_clock_names
 	[PCS_TX_ROOT_CLK] =	"pcs_tx_root",
 };
 
+static const int qca8084_xpcs_ch_mmd[QCA8084_CHANNEL_MAX] = { 31, 26, 27, 28 };
+
 struct mdio_device *qca8084_package_pcs_probe(struct device_node *pcs_np)
 {
 	struct qca8084_pcs_data *pcs_data;
@@ -247,3 +335,289 @@ void qca8084_package_xpcs_and_pcs_remove
 	mdio_device_put(xpcs_mdiodev);
 	mdio_device_put(pcs_mdiodev);
 }
+
+static int qca8084_pcs_set_interface_mode(struct mdio_device *mdio_dev,
+					  phy_interface_t ifmode)
+{
+	int ret, hw_ifmode, data;
+
+	switch (ifmode) {
+	case PHY_INTERFACE_MODE_SGMII:
+		hw_ifmode = MODE_CONTROL_SGMII;
+		data = QP_USXG_OPTION1_DATAPASS_SGMII;
+		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		hw_ifmode = MODE_CONTROL_SGMII_PLUS;
+		data = QP_USXG_OPTION1_DATAPASS_SGMII;
+		break;
+	case PHY_INTERFACE_MODE_10G_QXGMII:
+		hw_ifmode = MODE_CONTROL_XPCS;
+		data = QP_USXG_OPTION1_DATAPASS_USXGMII;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	/* For PLL stable under high temperature */
+	ret = mdiodev_modify(mdio_dev, PLL_CONTROL,
+			     PLL_CONTROL_CMLDIV2_IBSEL_MASK,
+			     FIELD_PREP(PLL_CONTROL_CMLDIV2_IBSEL_MASK, 3));
+	if (ret)
+		return ret;
+
+	/* Configure the interface mode of PCS */
+	ret = mdiodev_c45_modify(mdio_dev, MDIO_MMD_PMAPMD, MODE_CONTROL,
+				 MODE_CONTROL_SEL_MASK,
+				 FIELD_PREP(MODE_CONTROL_SEL_MASK, hw_ifmode));
+	if (ret)
+		return ret;
+
+	/* Data pass selects SGMII or USXGMII */
+	return mdiodev_c45_modify(mdio_dev, MDIO_MMD_PMAPMD, QP_USXG_OPTION1,
+				  QP_USXG_OPTION1_DATAPASS,
+				  FIELD_PREP(QP_USXG_OPTION1_DATAPASS, data));
+}
+
+static int qca8084_do_calibration(struct mdio_device *mdio_dev)
+{
+	int ret;
+
+	ret = mdiodev_modify(mdio_dev, PLL_POWER_ON_AND_RESET,
+			     PCS_ANA_SW_RESET, 0);
+	if (ret)
+		return ret;
+
+	usleep_range(10000, 11000);
+	ret = mdiodev_modify(mdio_dev, PLL_POWER_ON_AND_RESET,
+			     PCS_ANA_SW_RESET, PCS_ANA_SW_RESET);
+	if (ret)
+		return ret;
+
+	/* Wait calibration done */
+	return read_poll_timeout(mdiodev_c45_read, ret,
+				 (ret & CALIBRATION_DONE),
+				 100, 100000, true, mdio_dev,
+				 MDIO_MMD_PMAPMD, CALIBRATION4);
+}
+
+
+static int qca8084_xpcs_set_mode(struct mdio_device *xpcs_mdiodev)
+{
+	int ret, val, i;
+
+	/* Configure BaseR mode */
+	ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, PCS_CONTROL2,
+				 PCS_TYPE_MASK,
+				 FIELD_PREP(PCS_TYPE_MASK, PCS_TYPE_BASER));
+	if (ret)
+		return ret;
+
+	/* Wait BaseR link up */
+	ret = read_poll_timeout(mdiodev_c45_read, val,
+				(val & PCS_BASER_UP), 1000, 100000, true,
+				xpcs_mdiodev,
+				MDIO_MMD_PCS, PCS_STATUS1);
+	if (ret) {
+		dev_err(&xpcs_mdiodev->dev, "BaseR link failed!\n");
+		return ret;
+	}
+
+	/* Enable USXGMII mode */
+	ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, DIG_CTRL1,
+				 DIG_CTRL1_USXGMII_EN,
+				 DIG_CTRL1_USXGMII_EN);
+	if (ret)
+		return ret;
+
+	/* Configure QXGMII mode */
+	ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, PCS_TPC,
+				 PCS_QXGMII_MODE_MASK,
+				 FIELD_PREP(PCS_QXGMII_MODE_MASK,
+					    PCS_QXGMII_EN));
+	if (ret)
+		return ret;
+
+	/* Configure AM interval */
+	ret = mdiodev_c45_write(xpcs_mdiodev, MDIO_MMD_PCS, AM_LINK_TIMER,
+				AM_LINK_TIMER_VAL);
+	if (ret)
+		return ret;
+
+	/* Reset XPCS */
+	ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, DIG_CTRL1,
+				 DIG_CTRL1_XPCS_RESET,
+				 DIG_CTRL1_XPCS_RESET);
+	if (ret)
+		return ret;
+
+	/* Wait XPCS reset done */
+	ret = read_poll_timeout(mdiodev_c45_read, val,
+				!(val & DIG_CTRL1_XPCS_RESET),
+				1000, 100000, true, xpcs_mdiodev,
+				MDIO_MMD_PCS, DIG_CTRL1);
+	if (ret) {
+		dev_err(&xpcs_mdiodev->dev, "XPCS reset failed!\n");
+		return ret;
+	}
+
+	/* Enable auto-negotiation complete interrupt, using mii-4bit
+	 * and TX configureation of PHY side on all XPCS channels.
+	 */
+	for (i = 0; i < QCA8084_CHANNEL_MAX; i++) {
+		ret = mdiodev_c45_modify(xpcs_mdiodev, qca8084_xpcs_ch_mmd[i],
+					 DIG_CONTROL2,
+					 (MII_BIT_CONTROL | TX_CONFIG |
+					 AUTO_NEGOTIATION_CMPLT_INTR),
+					 (TX_CONFIG | AUTO_NEGOTIATION_CMPLT_INTR));
+		if (ret)
+			return ret;
+
+		/* Enable auto-negotiation capability */
+		ret = mdiodev_c45_modify(xpcs_mdiodev, qca8084_xpcs_ch_mmd[i],
+					 MII_CONTROL,
+					 AUTO_NEGOTIATION_EN,
+					 AUTO_NEGOTIATION_EN);
+		if (ret)
+			return ret;
+
+		/* Disable TX IPG check */
+		ret = mdiodev_c45_modify(xpcs_mdiodev, qca8084_xpcs_ch_mmd[i],
+					 XAUI_CONTROL,
+					 TX_IPG_CHECK_DISABLE,
+					 TX_IPG_CHECK_DISABLE);
+		if (ret)
+			return ret;
+	}
+
+	/* Check EEE capability supported or not */
+	ret = mdiodev_c45_read(xpcs_mdiodev, MDIO_MMD_PCS, PCS_EEE_CONTROL);
+	if (ret < 0)
+		return ret;
+
+	if (ret & EEE_CAPABILITY) {
+		ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
+					 EEE_MODE_CONTROL,
+					 EEE_LCT_RES | EEE_SIGN,
+					 FIELD_PREP(EEE_LCT_RES, 1) | EEE_SIGN);
+		if (ret)
+			return ret;
+
+		ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
+					 EEE_RX_TIMER,
+					 EEE_RX_TIMER_100US_RES | EEE_RX_TIMER_RWR_RES,
+					 FIELD_PREP(EEE_RX_TIMER_100US_RES, 0xc8) |
+					 FIELD_PREP(EEE_RX_TIMER_RWR_RES, 0x1c));
+		if (ret)
+			return ret;
+
+		/* Enable EEE LPI */
+		ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
+					 EEE_MODE_CONTROL1,
+					 TRANS_LPI_MODE | TRANS_RX_LPI_MODE,
+					 TRANS_LPI_MODE | TRANS_RX_LPI_MODE);
+		if (ret)
+			return ret;
+
+		/* Enable TX/RX LPI pattern */
+		ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
+					 EEE_MODE_CONTROL,
+					 EEE_LRX_EN | EEE_LTX_EN,
+					 EEE_LRX_EN | EEE_LTX_EN);
+	}
+
+	return ret;
+}
+
+static int qca8084_pcs_set_mode(struct mdio_device *xpcs_mdiodev,
+				       struct mdio_device *pcs_mdiodev)
+{
+	struct qca8084_xpcs_data *xpcs_data = mdiodev_get_drvdata(xpcs_mdiodev);
+	struct qca8084_pcs_data *pcs_data = mdiodev_get_drvdata(pcs_mdiodev);
+	struct qca8084_xpcs_channel_priv xpcs_ch;
+	int ret, channel;
+
+	/* Enable clock and de-assert for PCS. */
+	ret = clk_prepare_enable(pcs_data->clks[PCS_CLK]);
+	if (ret)
+		return ret;
+
+	ret = reset_control_deassert(pcs_data->rstc);
+	if (ret)
+		return ret;
+
+	/* IPG tunning selection for RX, TX and XGMII of all channels. */
+	ret = mdiodev_c45_modify(pcs_mdiodev, MDIO_MMD_PMAPMD,
+				 BYPASS_TUNNING_IPG,
+				 BYPASS_TUNNING_IPG_MASK, 0);
+	if (ret)
+		return ret;
+
+	reset_control_assert(xpcs_data->rstc);
+
+	ret = qca8084_pcs_set_interface_mode(pcs_mdiodev,
+					     PHY_INTERFACE_MODE_10G_QXGMII);
+	if (ret)
+		return ret;
+
+	/* Reset of 4 channels */
+	for (channel = 0; channel < QCA8084_CHANNEL_MAX; channel++) {
+		xpcs_ch = xpcs_data->xpcs_ch[channel];
+		ret = reset_control_reset(xpcs_ch.rstcs);
+		if (ret)
+			return ret;
+	}
+
+	ret = qca8084_do_calibration(pcs_mdiodev);
+	if (ret) {
+		dev_err(&pcs_mdiodev->dev, "PCS calibration timeout!\n");
+		return ret;
+	}
+
+	/* Enable PCS SSC to fix mode */
+	ret = mdiodev_c45_modify(pcs_mdiodev, MDIO_MMD_PMAPMD,
+				 CDR_CONTRL, SSC_FIX_MODE, SSC_FIX_MODE);
+	if (ret)
+		return ret;
+
+	return reset_control_deassert(xpcs_data->rstc);
+}
+
+static int qca8084_xpcs_clock_parent_set(struct mdio_device *xpcs_mdiodev,
+					 struct mdio_device *pcs_mdiodev)
+{
+	struct qca8084_xpcs_data *xpcs_data = mdiodev_get_drvdata(xpcs_mdiodev);
+	struct qca8084_pcs_data *pcs_data = mdiodev_get_drvdata(pcs_mdiodev);
+	struct qca8084_xpcs_channel_priv xpcs_ch;
+	int ret, channel;
+
+	for (channel = 0; channel < QCA8084_CHANNEL_MAX; channel++) {
+		xpcs_ch = xpcs_data->xpcs_ch[channel];
+		ret = clk_set_parent(xpcs_ch.clks[XPCS_RX_SRC_CLK],
+				     pcs_data->clks[PCS_RX_ROOT_CLK]);
+		if (ret)
+			return ret;
+
+		ret = clk_set_parent(xpcs_ch.clks[XPCS_TX_SRC_CLK],
+				     pcs_data->clks[PCS_TX_ROOT_CLK]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int qca8084_qxgmii_set_mode(struct mdio_device *xpcs_mdiodev,
+			    struct mdio_device *pcs_mdiodev)
+{
+	int ret;
+
+	ret = qca8084_xpcs_clock_parent_set(xpcs_mdiodev, pcs_mdiodev);
+	if (ret)
+		return ret;
+
+	ret = qca8084_pcs_set_mode(xpcs_mdiodev, pcs_mdiodev);
+	if (ret)
+		return ret;
+
+	return qca8084_xpcs_set_mode(xpcs_mdiodev);
+}
--- a/drivers/net/phy/qcom/qca8084_serdes.h
+++ b/drivers/net/phy/qcom/qca8084_serdes.h
@@ -15,4 +15,6 @@ struct mdio_device *qca8084_package_pcs_
 struct mdio_device *qca8084_package_xpcs_probe(struct device_node *xpcs_np);
 void qca8084_package_xpcs_and_pcs_remove(struct mdio_device *xpcs_mdiodev,
 					 struct mdio_device *pcs_mdiodev);
+int qca8084_qxgmii_set_mode(struct mdio_device *xpcs_mdiodev,
+			    struct mdio_device *pcs_mdiodev);
 #endif /* _QCA8084_SERDES_H_ */
--- a/drivers/net/phy/qcom/qca808x.c
+++ b/drivers/net/phy/qcom/qca808x.c
@@ -926,6 +926,14 @@ static int qca8084_phy_package_config_in
 
 	usleep_range(10000, 11000);
 
+	/* Configure PCS working on 10G-QXGMII mode */
+	if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII) {
+		ret = qca8084_qxgmii_set_mode(shared_priv->mdiodev[1],
+					      shared_priv->mdiodev[0]);
+		if (ret)
+			return ret;
+	}
+
 	/* Initialize the PHY package clock and reset, which is the
 	 * necessary config sequence after GPIO reset on the PHY package.
 	 */
@@ -1164,6 +1172,9 @@ static int qca8084_probe(struct phy_devi
 
 static void qca8084_remove(struct phy_device *phydev)
 {
+	if (phydev->interface != PHY_INTERFACE_MODE_10G_QXGMII)
+		return;
+
 	if (phy_package_remove_once(phydev))
 		qca8084_phy_package_remove_once(phydev);
 }
