net: pcs: xpcs: support to switch mode for Wangxun NICs
authorJiawen Wu <jiawenwu@trustnetic.com>
Wed, 23 Aug 2023 06:19:29 +0000 (14:19 +0800)
committerDavid S. Miller <davem@davemloft.net>
Fri, 25 Aug 2023 06:42:58 +0000 (07:42 +0100)
According to chapter 6 of DesignWare Cores Ethernet PCS (version 3.20a)
and custom design manual, add a configuration flow for switching interface
mode.

If the interface changes, the following setting is required:
1. wait VR_XS_PCS_DIG_STS bit(4, 2) [PSEQ_STATE] = 100b (Power-Good)
2. write SR_XS_PCS_CTRL2 to select various PCS type
3. write SR_PMA_CTRL1 and/or SR_XS_PCS_CTRL1 for link speed
4. program PMA registers
5. write VR_XS_PCS_DIG_CTRL1 bit(15) [VR_RST] = 1b (Vendor-Specific
   Soft Reset)
6. wait for VR_XS_PCS_DIG_CTRL1 bit(15) [VR_RST] to get cleared

Only 10GBASE-R/SGMII/1000BASE-X modes are planned for the current Wangxun
devices. And there is a quirk for Wangxun devices to switch mode although
the interface in phylink state has not changed, since PCS will change to
default 10GBASE-R when the ethernet driver(txgbe) do LAN reset.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
MAINTAINERS
drivers/net/pcs/Makefile
drivers/net/pcs/pcs-xpcs-wx.c [new file with mode: 0644]
drivers/net/pcs/pcs-xpcs.c
drivers/net/pcs/pcs-xpcs.h
include/linux/pcs/pcs-xpcs.h

index dfc2ba0..5f527a9 100644 (file)
@@ -22925,6 +22925,7 @@ S:      Maintained
 W:     https://www.net-swift.com
 F:     Documentation/networking/device_drivers/ethernet/wangxun/*
 F:     drivers/net/ethernet/wangxun/
+F:     drivers/net/pcs/pcs-xpcs-wx.c
 
 WATCHDOG DEVICE DRIVERS
 M:     Wim Van Sebroeck <wim@linux-watchdog.org>
index ea662a7..fb16941 100644 (file)
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 # Makefile for Linux PCS drivers
 
-pcs_xpcs-$(CONFIG_PCS_XPCS)    := pcs-xpcs.o pcs-xpcs-nxp.o
+pcs_xpcs-$(CONFIG_PCS_XPCS)    := pcs-xpcs.o pcs-xpcs-nxp.o pcs-xpcs-wx.o
 
 obj-$(CONFIG_PCS_XPCS)         += pcs_xpcs.o
 obj-$(CONFIG_PCS_LYNX)         += pcs-lynx.o
diff --git a/drivers/net/pcs/pcs-xpcs-wx.c b/drivers/net/pcs/pcs-xpcs-wx.c
new file mode 100644 (file)
index 0000000..1f228d5
--- /dev/null
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
+
+#include <linux/pcs/pcs-xpcs.h>
+#include <linux/mdio.h>
+#include "pcs-xpcs.h"
+
+/* VR_XS_PMA_MMD */
+#define TXGBE_PMA_MMD                  0x8020
+#define TXGBE_TX_GENCTL1               0x11
+#define TXGBE_TX_GENCTL1_VBOOST_LVL    GENMASK(10, 8)
+#define TXGBE_TX_GENCTL1_VBOOST_EN0    BIT(4)
+#define TXGBE_TX_GEN_CTL2              0x12
+#define TXGBE_TX_GEN_CTL2_TX0_WIDTH(v) FIELD_PREP(GENMASK(9, 8), v)
+#define TXGBE_TX_RATE_CTL              0x14
+#define TXGBE_TX_RATE_CTL_TX0_RATE(v)  FIELD_PREP(GENMASK(2, 0), v)
+#define TXGBE_RX_GEN_CTL2              0x32
+#define TXGBE_RX_GEN_CTL2_RX0_WIDTH(v) FIELD_PREP(GENMASK(9, 8), v)
+#define TXGBE_RX_GEN_CTL3              0x33
+#define TXGBE_RX_GEN_CTL3_LOS_TRSHLD0  GENMASK(2, 0)
+#define TXGBE_RX_RATE_CTL              0x34
+#define TXGBE_RX_RATE_CTL_RX0_RATE(v)  FIELD_PREP(GENMASK(1, 0), v)
+#define TXGBE_RX_EQ_ATTN_CTL           0x37
+#define TXGBE_RX_EQ_ATTN_LVL0          GENMASK(2, 0)
+#define TXGBE_RX_EQ_CTL0               0x38
+#define TXGBE_RX_EQ_CTL0_VGA1_GAIN(v)  FIELD_PREP(GENMASK(15, 12), v)
+#define TXGBE_RX_EQ_CTL0_VGA2_GAIN(v)  FIELD_PREP(GENMASK(11, 8), v)
+#define TXGBE_RX_EQ_CTL0_CTLE_POLE(v)  FIELD_PREP(GENMASK(7, 5), v)
+#define TXGBE_RX_EQ_CTL0_CTLE_BOOST(v) FIELD_PREP(GENMASK(4, 0), v)
+#define TXGBE_RX_EQ_CTL4               0x3C
+#define TXGBE_RX_EQ_CTL4_CONT_OFF_CAN0 BIT(4)
+#define TXGBE_RX_EQ_CTL4_CONT_ADAPT0   BIT(0)
+#define TXGBE_AFE_DFE_ENABLE           0x3D
+#define TXGBE_DFE_EN_0                 BIT(4)
+#define TXGBE_AFE_EN_0                 BIT(0)
+#define TXGBE_DFE_TAP_CTL0             0x3E
+#define TXGBE_MPLLA_CTL0               0x51
+#define TXGBE_MPLLA_CTL2               0x53
+#define TXGBE_MPLLA_CTL2_DIV16P5_CLK_EN        BIT(10)
+#define TXGBE_MPLLA_CTL2_DIV10_CLK_EN  BIT(9)
+#define TXGBE_MPLLA_CTL3               0x57
+#define TXGBE_MISC_CTL0                        0x70
+#define TXGBE_MISC_CTL0_PLL            BIT(15)
+#define TXGBE_MISC_CTL0_CR_PARA_SEL    BIT(14)
+#define TXGBE_MISC_CTL0_RX_VREF(v)     FIELD_PREP(GENMASK(12, 8), v)
+#define TXGBE_VCO_CAL_LD0              0x72
+#define TXGBE_VCO_CAL_REF0             0x76
+
+static int txgbe_read_pma(struct dw_xpcs *xpcs, int reg)
+{
+       return xpcs_read(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg);
+}
+
+static int txgbe_write_pma(struct dw_xpcs *xpcs, int reg, u16 val)
+{
+       return xpcs_write(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg, val);
+}
+
+static void txgbe_pma_config_10gbaser(struct dw_xpcs *xpcs)
+{
+       int val;
+
+       txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL0, 0x21);
+       txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL3, 0);
+       val = txgbe_read_pma(xpcs, TXGBE_TX_GENCTL1);
+       val = u16_replace_bits(val, 0x5, TXGBE_TX_GENCTL1_VBOOST_LVL);
+       txgbe_write_pma(xpcs, TXGBE_TX_GENCTL1, val);
+       txgbe_write_pma(xpcs, TXGBE_MISC_CTL0, TXGBE_MISC_CTL0_PLL |
+                       TXGBE_MISC_CTL0_CR_PARA_SEL | TXGBE_MISC_CTL0_RX_VREF(0xF));
+       txgbe_write_pma(xpcs, TXGBE_VCO_CAL_LD0, 0x549);
+       txgbe_write_pma(xpcs, TXGBE_VCO_CAL_REF0, 0x29);
+       txgbe_write_pma(xpcs, TXGBE_TX_RATE_CTL, 0);
+       txgbe_write_pma(xpcs, TXGBE_RX_RATE_CTL, 0);
+       txgbe_write_pma(xpcs, TXGBE_TX_GEN_CTL2, TXGBE_TX_GEN_CTL2_TX0_WIDTH(3));
+       txgbe_write_pma(xpcs, TXGBE_RX_GEN_CTL2, TXGBE_RX_GEN_CTL2_RX0_WIDTH(3));
+       txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL2, TXGBE_MPLLA_CTL2_DIV16P5_CLK_EN |
+                       TXGBE_MPLLA_CTL2_DIV10_CLK_EN);
+
+       txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL0, TXGBE_RX_EQ_CTL0_CTLE_POLE(2) |
+                       TXGBE_RX_EQ_CTL0_CTLE_BOOST(5));
+       val = txgbe_read_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL);
+       val &= ~TXGBE_RX_EQ_ATTN_LVL0;
+       txgbe_write_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, val);
+       txgbe_write_pma(xpcs, TXGBE_DFE_TAP_CTL0, 0xBE);
+       val = txgbe_read_pma(xpcs, TXGBE_AFE_DFE_ENABLE);
+       val &= ~(TXGBE_DFE_EN_0 | TXGBE_AFE_EN_0);
+       txgbe_write_pma(xpcs, TXGBE_AFE_DFE_ENABLE, val);
+       val = txgbe_read_pma(xpcs, TXGBE_RX_EQ_CTL4);
+       val &= ~TXGBE_RX_EQ_CTL4_CONT_ADAPT0;
+       txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL4, val);
+}
+
+static void txgbe_pma_config_1g(struct dw_xpcs *xpcs)
+{
+       int val;
+
+       val = txgbe_read_pma(xpcs, TXGBE_TX_GENCTL1);
+       val = u16_replace_bits(val, 0x5, TXGBE_TX_GENCTL1_VBOOST_LVL);
+       val &= ~TXGBE_TX_GENCTL1_VBOOST_EN0;
+       txgbe_write_pma(xpcs, TXGBE_TX_GENCTL1, val);
+       txgbe_write_pma(xpcs, TXGBE_MISC_CTL0, TXGBE_MISC_CTL0_PLL |
+                       TXGBE_MISC_CTL0_CR_PARA_SEL | TXGBE_MISC_CTL0_RX_VREF(0xF));
+
+       txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL0, TXGBE_RX_EQ_CTL0_VGA1_GAIN(7) |
+                       TXGBE_RX_EQ_CTL0_VGA2_GAIN(7) | TXGBE_RX_EQ_CTL0_CTLE_BOOST(6));
+       val = txgbe_read_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL);
+       val &= ~TXGBE_RX_EQ_ATTN_LVL0;
+       txgbe_write_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, val);
+       txgbe_write_pma(xpcs, TXGBE_DFE_TAP_CTL0, 0);
+       val = txgbe_read_pma(xpcs, TXGBE_RX_GEN_CTL3);
+       val = u16_replace_bits(val, 0x4, TXGBE_RX_GEN_CTL3_LOS_TRSHLD0);
+       txgbe_write_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, val);
+
+       txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL0, 0x20);
+       txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL3, 0x46);
+       txgbe_write_pma(xpcs, TXGBE_VCO_CAL_LD0, 0x540);
+       txgbe_write_pma(xpcs, TXGBE_VCO_CAL_REF0, 0x2A);
+       txgbe_write_pma(xpcs, TXGBE_AFE_DFE_ENABLE, 0);
+       txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL4, TXGBE_RX_EQ_CTL4_CONT_OFF_CAN0);
+       txgbe_write_pma(xpcs, TXGBE_TX_RATE_CTL, TXGBE_TX_RATE_CTL_TX0_RATE(3));
+       txgbe_write_pma(xpcs, TXGBE_RX_RATE_CTL, TXGBE_RX_RATE_CTL_RX0_RATE(3));
+       txgbe_write_pma(xpcs, TXGBE_TX_GEN_CTL2, TXGBE_TX_GEN_CTL2_TX0_WIDTH(1));
+       txgbe_write_pma(xpcs, TXGBE_RX_GEN_CTL2, TXGBE_RX_GEN_CTL2_RX0_WIDTH(1));
+       txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL2, TXGBE_MPLLA_CTL2_DIV10_CLK_EN);
+}
+
+static int txgbe_pcs_poll_power_up(struct dw_xpcs *xpcs)
+{
+       int val, ret;
+
+       /* Wait xpcs power-up good */
+       ret = read_poll_timeout(xpcs_read_vpcs, val,
+                               (val & DW_PSEQ_ST) == DW_PSEQ_ST_GOOD,
+                               10000, 1000000, false,
+                               xpcs, DW_VR_XS_PCS_DIG_STS);
+       if (ret < 0)
+               dev_err(&xpcs->mdiodev->dev, "xpcs power-up timeout\n");
+
+       return ret;
+}
+
+static int txgbe_pma_init_done(struct dw_xpcs *xpcs)
+{
+       int val, ret;
+
+       xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_VR_RST | DW_EN_VSMMD1);
+
+       /* wait pma initialization done */
+       ret = read_poll_timeout(xpcs_read_vpcs, val, !(val & DW_VR_RST),
+                               100000, 10000000, false,
+                               xpcs, DW_VR_XS_PCS_DIG_CTRL1);
+       if (ret < 0)
+               dev_err(&xpcs->mdiodev->dev, "xpcs pma initialization timeout\n");
+
+       return ret;
+}
+
+static bool txgbe_xpcs_mode_quirk(struct dw_xpcs *xpcs)
+{
+       int ret;
+
+       /* When txgbe do LAN reset, PCS will change to default 10GBASE-R mode */
+       ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_CTRL2);
+       ret &= MDIO_PCS_CTRL2_TYPE;
+       if (ret == MDIO_PCS_CTRL2_10GBR &&
+           xpcs->interface != PHY_INTERFACE_MODE_10GBASER)
+               return true;
+
+       return false;
+}
+
+int txgbe_xpcs_switch_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
+{
+       int val, ret;
+
+       switch (interface) {
+       case PHY_INTERFACE_MODE_10GBASER:
+       case PHY_INTERFACE_MODE_SGMII:
+       case PHY_INTERFACE_MODE_1000BASEX:
+               break;
+       default:
+               return 0;
+       }
+
+       if (xpcs->interface == interface && !txgbe_xpcs_mode_quirk(xpcs))
+               return 0;
+
+       xpcs->interface = interface;
+
+       ret = txgbe_pcs_poll_power_up(xpcs);
+       if (ret < 0)
+               return ret;
+
+       if (interface == PHY_INTERFACE_MODE_10GBASER) {
+               xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, MDIO_PCS_CTRL2_10GBR);
+               val = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+               val |= MDIO_CTRL1_SPEED10G;
+               xpcs_write(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1, val);
+               txgbe_pma_config_10gbaser(xpcs);
+       } else {
+               xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, MDIO_PCS_CTRL2_10GBX);
+               xpcs_write(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1, 0);
+               xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL1, 0);
+               txgbe_pma_config_1g(xpcs);
+       }
+
+       return txgbe_pma_init_done(xpcs);
+}
index 8b56b2f..4cd0114 100644 (file)
@@ -228,12 +228,12 @@ static int xpcs_write_vendor(struct dw_xpcs *xpcs, int dev, int reg,
        return xpcs_write(xpcs, dev, DW_VENDOR | reg, val);
 }
 
-static int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg)
+int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg)
 {
        return xpcs_read_vendor(xpcs, MDIO_MMD_PCS, reg);
 }
 
-static int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val)
+int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val)
 {
        return xpcs_write_vendor(xpcs, MDIO_MMD_PCS, reg, val);
 }
@@ -841,6 +841,12 @@ int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
        if (!compat)
                return -ENODEV;
 
+       if (xpcs->dev_flag == DW_DEV_TXGBE) {
+               ret = txgbe_xpcs_switch_mode(xpcs, interface);
+               if (ret)
+                       return ret;
+       }
+
        switch (compat->an_mode) {
        case DW_10GBASER:
                break;
@@ -1313,9 +1319,6 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
 
                xpcs->pcs.ops = &xpcs_phylink_ops;
                xpcs->pcs.neg_mode = true;
-               if (compat->an_mode == DW_10GBASER)
-                       return xpcs;
-
                xpcs->pcs.poll = true;
 
                if (xpcs->dev_flag != DW_DEV_TXGBE) {
index 68c6b5a..da61ad3 100644 (file)
 /* VR_XS_PCS */
 #define DW_USXGMII_RST                 BIT(10)
 #define DW_USXGMII_EN                  BIT(9)
+#define DW_VR_XS_PCS_DIG_CTRL1         0x0000
+#define DW_VR_RST                      BIT(15)
+#define DW_EN_VSMMD1                   BIT(13)
 #define DW_VR_XS_PCS_DIG_STS           0x0010
 #define DW_RXFIFO_ERR                  GENMASK(6, 5)
+#define DW_PSEQ_ST                     GENMASK(4, 2)
+#define DW_PSEQ_ST_GOOD                        FIELD_PREP(GENMASK(4, 2), 0x4)
 
 /* SR_MII */
 #define DW_USXGMII_FULL                        BIT(8)
 
 int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg);
 int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val);
+int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg);
+int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val);
 int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs);
 int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs);
 int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs);
+int txgbe_xpcs_switch_mode(struct dw_xpcs *xpcs, phy_interface_t interface);
index f37e8ac..da3a6c3 100644 (file)
@@ -32,6 +32,7 @@ struct dw_xpcs {
        struct mdio_device *mdiodev;
        const struct xpcs_id *id;
        struct phylink_pcs pcs;
+       phy_interface_t interface;
        int dev_flag;
 };