summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Semin <fancer.lancer@gmail.com>2023-07-05 14:00:02 +0300
committerSerge Semin <fancer.lancer@gmail.com>2026-08-13 20:37:50 +0300
commit30cc4db3e9d3e081b604f9d1d66cfde128dfa6ca (patch)
tree32bf8ab4f4d89f82361e3386f8c67df72c975630
parenta4de026e4156d2a3c9e90db717dd0537fdd48e45 (diff)
downloadlinux-30cc4db3e9d3e081b604f9d1d66cfde128dfa6ca.tar.gz
linux-30cc4db3e9d3e081b604f9d1d66cfde128dfa6ca.zip
net: pcs: xpcs: Add 10GBASE-R interface support
DW XPCS can be synthesized with 10GBASE-R link support. In that case xGMAC is still connected to the XPCS device by means of normally multi-lane interface meanwhile an external interface is defined as 10GBASE-R. From network subsystem point of view it's as if DW XPCS is fully integrated into the MAC with no need in setting any intermediate interface up. So based on the IEEE 802.3 standard 10GBASE-R doesn't intent any auto-negotiations. Instead it works with the 10Gbps speed and in the Full-duplex mode. Thus a new AN-less mode is defined DW_10GBASER in the driver. It implies PCS and PMA setups required for the device to work in the denoted mode. The respective actions are executed in the xpcs_do_config() method. At the same time xpcs_link_up() makes sure that the PCS/PMA<->PHY link actually works and it has compatible speed and duplex (though having the pcs_link_up() method returning the operation status would look better or at least having pcs_get_state() working for the non-inband setups). Note 1. 10GBASE-R PMA config implies reference clock switch in case if the respective flag doesn't correspond to the specified clock sources. Note 2. XPCS 10GBASE-R interface features list contains all speed-compatible link modes in order to have the pcs_validate() permitting these modes for the externally attached PHYs. Note 3. Soft-reset and poll-mode are activated on the XPCS-descriptor create procedure since the interface initialization is now comprehensive enough to configure device from scratch. Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_main.c3
-rw-r--r--drivers/net/pcs/Makefile2
-rw-r--r--drivers/net/pcs/pcs-xpcs-pma.c89
-rw-r--r--drivers/net/pcs/pcs-xpcs.c79
-rw-r--r--drivers/net/pcs/pcs-xpcs.h22
5 files changed, 193 insertions, 2 deletions
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 2fb13d0823ca7..b0826b833e4a7 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -1089,7 +1089,8 @@ static void stmmac_mac_link_up(struct phylink_config *config,
old_ctrl = readl(priv->ioaddr + MAC_CTRL_REG);
ctrl = old_ctrl & ~priv->hw->link.speed_mask;
- if (interface == PHY_INTERFACE_MODE_XGMII) {
+ if (interface == PHY_INTERFACE_MODE_XGMII ||
+ interface == PHY_INTERFACE_MODE_10GBASER) {
switch (speed) {
case SPEED_10000:
ctrl |= priv->hw->link.xgmii.speed10000;
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 4f7920618b900..95b91cebd8518 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -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-plat.o \
+pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o pcs-xpcs-pma.o \
pcs-xpcs-nxp.o pcs-xpcs-wx.o
obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
diff --git a/drivers/net/pcs/pcs-xpcs-pma.c b/drivers/net/pcs/pcs-xpcs-pma.c
new file mode 100644
index 0000000000000..128ac789cd251
--- /dev/null
+++ b/drivers/net/pcs/pcs-xpcs-pma.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synopsys DesignWare XPCS PMA driver
+ *
+ * Copyright (C) 2024 Serge Semin
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/mdio.h>
+#include <linux/pcs/pcs-xpcs.h>
+
+#include "pcs-xpcs.h"
+
+/* DesignWare Gen5 10G PHY can be clocked by an external clock source.
+ * It will be enabled instead of the internal one in case if it's specified.
+ */
+static int xpcs_gen5_10g_ref_clock_select(struct dw_xpcs *xpcs, bool reset)
+{
+ int ret;
+
+ ret = xpcs_read_vendor(xpcs, MDIO_MMD_PMAPMD,
+ DW_VR_XS_PMA_GEN5_10G_MPLL_CTRL);
+ if (ret < 0)
+ return ret;
+
+ if (xpcs->clks[DW_XPCS_PAD_CLK].clk)
+ ret &= ~DW_VR_XS_PMA_REF_CLK_SEL_CORE;
+ else if (xpcs->clks[DW_XPCS_CORE_CLK].clk)
+ ret |= DW_VR_XS_PMA_REF_CLK_SEL_CORE;
+ else if (reset)
+ goto out_vendor_reset;
+ else
+ return 0;
+
+ ret = xpcs_write_vendor(xpcs, MDIO_MMD_PMAPMD,
+ DW_VR_XS_PMA_GEN5_10G_MPLL_CTRL, ret);
+ if (ret < 0)
+ return ret;
+
+ /* Vendor reset must be immediately performed. Note it will workout
+ * only if Tx/Rx are stable (Power_Good state).
+ */
+out_vendor_reset:
+ return xpcs_vendor_reset(xpcs);
+}
+
+static int xpcs_10gbaser_gen5_10g_pma_config(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ ret = xpcs_read_vendor(xpcs, MDIO_MMD_PMAPMD,
+ DW_VR_XS_PMA_GEN5_10G_GEN_CTRL);
+ if (ret < 0)
+ return ret;
+
+ /* Select KR-mode of the PHY lanes */
+ ret &= ~DW_VR_XS_PMA_LANE_MODE;
+ ret |= FIELD_PREP(DW_VR_XS_PMA_LANE_MODE, DW_VR_XS_PMA_LANE_MODE_KR);
+
+ /* Activate 1 lane per link */
+ ret &= ~DW_VR_XS_PMA_LINK_WIDTH;
+ ret |= FIELD_PREP(DW_VR_XS_PMA_LINK_WIDTH, DW_VR_XS_PMA_LINK_WIDTH_1);
+
+ /* Disable unused 1-3 lanes */
+ ret &= ~DW_VR_XS_PMA_LANE_PWR_OFF;
+ ret |= FIELD_PREP(DW_VR_XS_PMA_LANE_PWR_OFF, 0xe);
+
+ ret = xpcs_write_vendor(xpcs, MDIO_MMD_PMAPMD,
+ DW_VR_XS_PMA_GEN5_10G_GEN_CTRL, ret);
+ if (ret < 0)
+ return ret;
+
+ /* Select PCS/PMA refclk source: Pad or Core. Vendor-reset the device
+ * to make sure the updates are perceived by the core
+ */
+ return xpcs_gen5_10g_ref_clock_select(xpcs, true);
+}
+
+int xpcs_10gbaser_pma_config(struct dw_xpcs *xpcs)
+{
+ switch (xpcs->info.pma) {
+ case DW_XPCS_PMA_GEN5_10G_ID:
+ return xpcs_10gbaser_gen5_10g_pma_config(xpcs);
+ default:
+ return 0;
+ }
+}
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index b035ba1ac48c2..e746caae64abf 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -124,10 +124,16 @@ static const int xpcs_100gbasep_features[] = {
static const int xpcs_10gbaser_features[] = {
ETHTOOL_LINK_MODE_Pause_BIT,
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
+ /* Speed-compatible modes for the link setup in the external PHYs */
+ ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+ /* ETHTOOL_LINK_MODE_10000baseCX4_Full_BIT, */
+ /* ETHTOOL_LINK_MODE_10000baseLX4_Full_BIT, */
__ETHTOOL_LINK_MODE_MASK_NBITS,
};
@@ -902,6 +908,41 @@ static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs,
return changed;
}
+static int xpcs_config_10gbaser(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ /* Disable Clause 73 AN in anyway */
+ ret = xpcs_modify(xpcs, MDIO_MMD_AN, MDIO_CTRL1,
+ MDIO_AN_CTRL1_ENABLE, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Disable RXAUI if it's enabled by default */
+ ret = xpcs_read_vpcs(xpcs, DW_VR_XS_PCS_XAUI_MODE_CTRL);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~DW_VR_XS_PCS_RXAUI_MODE;
+ ret = xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_XAUI_MODE_CTRL, ret);
+ if (ret < 0)
+ return ret;
+
+ /* Set PCS to 10G speed and 10GBASE-R PCS */
+ ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL1,
+ MDIO_CTRL1_SPEEDSEL, MDIO_CTRL1_SPEED10G);
+ if (ret < 0)
+ return ret;
+
+ ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL2,
+ MDIO_PCS_CTRL2_TYPE, MDIO_PCS_CTRL2_10GBR);
+ if (ret < 0)
+ return ret;
+
+ /* Make sure the specified PCS type is perceived by the core */
+ return xpcs_vendor_reset(xpcs);
+}
+
static int xpcs_config_2500basex(struct dw_xpcs *xpcs)
{
int ret;
@@ -941,6 +982,9 @@ static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
switch (compat->an_mode) {
case DW_10GBASER:
+ ret = xpcs_config_10gbaser(xpcs);
+ if (ret)
+ return ret;
break;
case DW_AN_C73:
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
@@ -1229,6 +1273,36 @@ static void xpcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
}
}
+static void xpcs_link_up_check(struct dw_xpcs *xpcs, int speed, int duplex)
+{
+ int ret;
+
+ ret = xpcs_poll_val(xpcs, MDIO_MMD_PCS, MDIO_STAT1,
+ MDIO_STAT1_LSTATUS, MDIO_STAT1_LSTATUS);
+ if (ret < 0) {
+ dev_err(&xpcs->mdiodev->dev, "link is malfunction\n");
+ return;
+ }
+
+ if (speed != SPEED_10000)
+ dev_warn(&xpcs->mdiodev->dev, "Incompatible speed\n");
+
+ if (duplex != DUPLEX_FULL)
+ dev_warn(&xpcs->mdiodev->dev, "Incompatible duplex\n");
+
+ ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2);
+ if (ret < 0) {
+ dev_err(&xpcs->mdiodev->dev, "Failed to read PCS status\n");
+ return;
+ }
+
+ if (ret & MDIO_STAT2_RXFAULT)
+ dev_dbg(&xpcs->mdiodev->dev, "Receiver fault detected\n");
+
+ if (ret & MDIO_STAT2_TXFAULT)
+ dev_dbg(&xpcs->mdiodev->dev, "Transmitter fault detected\n");
+}
+
static void xpcs_link_up_sgmii_1000basex(struct dw_xpcs *xpcs,
unsigned int neg_mode,
phy_interface_t interface,
@@ -1274,6 +1348,10 @@ static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
xpcs_link_up_usxgmii(xpcs, speed);
break;
+ case PHY_INTERFACE_MODE_10GBASER:
+ xpcs_link_up_check(xpcs, speed, duplex);
+ break;
+
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_1000BASEX:
xpcs_link_up_sgmii_1000basex(xpcs, neg_mode, interface, speed,
@@ -1452,6 +1530,7 @@ static const struct dw_xpcs_compat synopsys_xpcs_compat[] = {
.interface = PHY_INTERFACE_MODE_10GBASER,
.supported = xpcs_10gbaser_features,
.an_mode = DW_10GBASER,
+ .pma_config = xpcs_10gbaser_pma_config,
}, {
.interface = PHY_INTERFACE_MODE_SGMII,
.supported = xpcs_sgmii_features,
diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h
index f80de96fcf8c6..40741e158e163 100644
--- a/drivers/net/pcs/pcs-xpcs.h
+++ b/drivers/net/pcs/pcs-xpcs.h
@@ -12,6 +12,22 @@
/* Vendor regs access */
#define DW_VENDOR BIT(15)
+/* VR_XS_PMA */
+#define DW_VR_XS_PMA_GEN5_10G_MPLL_CTRL 0x007a
+#define DW_VR_XS_PMA_REF_CLK_SEL_CORE BIT(13)
+#define DW_VR_XS_PMA_GEN5_10G_GEN_CTRL 0x009c
+#define DW_VR_XS_PMA_LANE_MODE GENMASK(3, 0)
+#define DW_VR_XS_PMA_LANE_MODE_KX 0x3
+#define DW_VR_XS_PMA_LANE_MODE_KX4 0x4
+#define DW_VR_XS_PMA_LANE_MODE_KR 0x5
+#define DW_VR_XS_PMA_LANE_MODE_SGMII 0x6
+#define DW_VR_XS_PMA_LANE_MODE_RXAUI 0x8
+#define DW_VR_XS_PMA_LINK_WIDTH GENMASK(10, 8)
+#define DW_VR_XS_PMA_LINK_WIDTH_1 0x0
+#define DW_VR_XS_PMA_LINK_WIDTH_2 0x1
+#define DW_VR_XS_PMA_LINK_WIDTH_4 0x2
+#define DW_VR_XS_PMA_LANE_PWR_OFF GENMASK(15, 12)
+
/* VR_XS_PCS */
#define DW_USXGMII_RST BIT(10)
#define DW_USXGMII_EN BIT(9)
@@ -19,7 +35,12 @@
#define DW_VR_RST BIT(15)
#define DW_EN_VSMMD1 BIT(13)
#define DW_CL37_BP BIT(12)
+#define DW_VR_XS_PCS_XAUI_MODE_CTRL 0x0004
+#define DW_VR_XS_PCS_RXAUI_MODE BIT(0)
+#define DW_VR_XS_PCS_MRVL_RXAUI BIT(1)
#define DW_VR_XS_PCS_DIG_STS 0x0010
+#define DW_PSEQ_STATE GENMASK(4, 2)
+#define DW_PSEQ_TXRX_STABLE 0x100
#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)
@@ -136,6 +157,7 @@ int xpcs_poll_val(struct dw_xpcs *xpcs, int dev, int reg, u16 mask, u16 goal);
int xpcs_soft_reset(struct dw_xpcs *xpcs);
int xpcs_vendor_reset(struct dw_xpcs *xpcs);
+int xpcs_10gbaser_pma_config(struct dw_xpcs *xpcs);
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);