[net-next PATCH v4 4/4] net: pcs: xpcs: Add handling for 4 channel rsfec device

From: Alexander Duyck

Date: Wed Jul 29 2026 - 18:33:22 EST


From: Mike Marciniszyn (Meta) <mike.marciniszyn@xxxxxxxxx>

This patch introduces the configuration of vendor specific registers for
alignment encoding, PCS Mode, and VL_INTVL over the one or two instances as
required.

The DW PCS IP specification calls out the need to configure both lanes
identically when using 2 lane modes such as 50-R2 and 100-R2, so the
programming is repeated for each channel.

The encoding tables are derived from the IEEE 8023-2022 spec sections
82.2.7 and tables 82-2 and 82-3 for the alignment markers and their
insertion.

Note that there is a conflict between VRs DW_VR_XS_PCS_DIG_STS and the
DW_PCS_IP DW_VR_MII_PCS_PCS_MODE. The bit mask for
DW_VR_XS_PCS_DIG_STS/RX_FIFO_ERR fits within the reserved bits for the DW
PCS IP DW_VR_MII_PCS_PCS_MODE register so there is no issue.

There is also a conflict between DW_VR_MII_PCS_VL_INTVL and
DW_VR_MII_AN_INTR_STS but an_mode differs, so again there is no issue.

Signed-off-by: Mike Marciniszyn (Meta) <mike.marciniszyn@xxxxxxxxx>
Signed-off-by: Alexander Duyck <alexanderduyck@xxxxxx>
---
drivers/net/pcs/pcs-xpcs.c | 96 ++++++++++++++++++++++++++++++++++++++++++++
drivers/net/pcs/pcs-xpcs.h | 25 +++++++++++
2 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index c252f8ba6517..d9c444528f55 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1412,11 +1412,67 @@ static int xpcs_read_ids(struct dw_xpcs *xpcs)
return 0;
}

+/* m0 - m2 from Table 82-2/82-3
+ * m4 - m6 are skipped since they are inversions of m0 - m2.
+ * Inverted parity fields (IEEE 82.2.8) bip3 and bip7 are omitted.
+ */
+struct lane_markers {
+ u8 m0, m1, m2;
+};
+
+/* Alignment marker encodings, see table 82-2 in IEEE 802.3-2022 */
+static const struct lane_markers xpcs_100gbaser_markers[] = {
+ {0xc1, 0x68, 0x21},
+ {0x9d, 0x71, 0x8e},
+ {0x59, 0x4b, 0xe8},
+ {0x4d, 0x95, 0x7b},
+};
+
+/* Alignment marker encodings, see table 82-3 in IEEE 802.3-2022
+ * The content of the 50G markers is identical to 40G values (IEEE 133.2.2).
+ */
+static const struct lane_markers xpcs_50gbaser_markers[] = {
+ {0x90, 0x76, 0x47},
+ {0xf0, 0xc4, 0xe6},
+ {0xc5, 0x65, 0x9b},
+ {0xa2, 0x79, 0x3d},
+};
+
struct pma_pcs_values {
+ const struct lane_markers *vl0_markers;
+ const struct lane_markers *vl123_markers;
int channels;
u16 rsfec_ctrl;
+ u16 pcs_mode;
+ u16 vl_intvl;
};

+#define XPCS_VL_TO_REG(vl, lh) \
+ (((vl) * 2) + DW_VR_MII_PCS_VL0_##lh)
+
+static int
+xpcs_write_pcs_ch(struct dw_xpcs *xpcs, int ch, int reg, u16 val)
+{
+ return xpcs_mdev_write_ch(xpcs, ch, MDIO_MMD_PCS, reg, val);
+}
+
+static int xpcs_config_ch_vl_markers(struct dw_xpcs *xpcs, int ch, int vl,
+ const struct lane_markers *m)
+{
+ int ret;
+
+ /* m0, m1, m2 written to _L and _H registers
+ *
+ * _L = (m1 << 8) | m0
+ * _H = m2
+ */
+ ret = xpcs_write_pcs_ch(xpcs, ch, XPCS_VL_TO_REG(vl, L),
+ ((u16)m->m1 << 8) | m->m0);
+ if (ret < 0)
+ return ret;
+ return xpcs_write_pcs_ch(xpcs, ch, XPCS_VL_TO_REG(vl, H), m->m2);
+}
+
static int
xpcs_config_rsfec_pma(struct dw_xpcs *xpcs, const struct pma_pcs_values *v)
{
@@ -1425,9 +1481,28 @@ xpcs_config_rsfec_pma(struct dw_xpcs *xpcs, const struct pma_pcs_values *v)
* work for enabling a non-fbnic device on this PCS.
*/
int pma_mmd = MDIO_MMD_SEP_PMA1;
- int ret = 0, i;
+ int ret = 0, i, vl;

for (i = 0; ret >= 0 && i < v->channels; i++) {
+ /* code word markings */
+ for (vl = 0; ret >= 0 && vl < 4; vl++) {
+ const struct lane_markers *m;
+
+ m = vl ? v->vl123_markers + vl - 1 : v->vl0_markers;
+ ret = xpcs_config_ch_vl_markers(xpcs, i, vl, m);
+ }
+ if (ret < 0)
+ break;
+ /* vendor registers */
+ ret = xpcs_write_pcs_ch(xpcs, i,
+ DW_VR_MII_PCS_VL_INTVL, v->vl_intvl);
+ if (ret < 0)
+ break;
+ ret = xpcs_write_pcs_ch(xpcs, i,
+ DW_VR_MII_PCS_PCS_MODE, v->pcs_mode);
+ if (ret < 0)
+ break;
+ /* rsfec register */
ret = xpcs_mdev_write_ch(xpcs, i, pma_mmd,
MDIO_PMA_RSFEC_CTRL, v->rsfec_ctrl);
}
@@ -1440,6 +1515,13 @@ static int xpcs_25gbaser_pma_config(struct dw_xpcs *xpcs)
const struct pma_pcs_values v = {
.rsfec_ctrl = 0,
.channels = 1,
+ /* 25g markers from 100g and 50g tables per 802.3-2022
+ * 108.5.2.4
+ */
+ .vl0_markers = &xpcs_100gbaser_markers[0],
+ .vl123_markers = &xpcs_50gbaser_markers[1],
+ .vl_intvl = 20479,
+ .pcs_mode = DW_VR_MII_PCS_MODE_CLAUSE107,
};

return xpcs_config_rsfec_pma(xpcs, &v);
@@ -1450,6 +1532,10 @@ static int xpcs_50gbaser_pma_config(struct dw_xpcs *xpcs)
const struct pma_pcs_values v = {
.rsfec_ctrl = DW_VR_RSFEC_CTRL_TC_PAD_ALTER,
.channels = 1,
+ .vl0_markers = &xpcs_50gbaser_markers[0],
+ .vl123_markers = &xpcs_50gbaser_markers[1],
+ .pcs_mode = 0,
+ .vl_intvl = 20479,
};

return xpcs_config_rsfec_pma(xpcs, &v);
@@ -1460,6 +1546,10 @@ static int xpcs_50gbaser2_pma_config(struct dw_xpcs *xpcs)
const struct pma_pcs_values v = {
.rsfec_ctrl = DW_VR_RSFEC_CTRL_TC_PAD_ALTER,
.channels = 2,
+ .vl0_markers = &xpcs_50gbaser_markers[0],
+ .vl123_markers = &xpcs_50gbaser_markers[1],
+ .pcs_mode = 0,
+ .vl_intvl = 20479,
};

return xpcs_config_rsfec_pma(xpcs, &v);
@@ -1470,6 +1560,10 @@ static int xpcs_100gbasep_pma_config(struct dw_xpcs *xpcs)
const struct pma_pcs_values v = {
.rsfec_ctrl = MDIO_PMA_RSFEC_CTRL_4LANE_PMD,
.channels = 2,
+ .vl0_markers = &xpcs_100gbaser_markers[0],
+ .vl123_markers = &xpcs_100gbaser_markers[1],
+ .pcs_mode = DW_VR_MII_PCS_MODE_DISABLE_MLD,
+ .vl_intvl = 16383,
};

return xpcs_config_rsfec_pma(xpcs, &v);
diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h
index ab47d6dd050c..adb7b1c13c2d 100644
--- a/drivers/net/pcs/pcs-xpcs.h
+++ b/drivers/net/pcs/pcs-xpcs.h
@@ -100,6 +100,31 @@
*/
#define DW_VR_RSFEC_CTRL_TC_PAD_ALTER BIT(10)

+/* Vendor specific 4 channel PCS registers */
+
+/* DW_VR_MII_PCS_VL_INTVL and DW_VR_MII_AN_INTR_STS conflict
+ * but code paths are different
+ */
+#define DW_VR_MII_PCS_VL_INTVL 0x8002
+/* 0x8008 - 0x800f */
+#define DW_VR_MII_PCS_VL0_L 0x8008
+#define DW_VR_MII_PCS_VL0_H 0x8009
+#define DW_VR_MII_PCS_PCS_MODE 0x8010
+
+/* DW_VR_MII_PCS_PCS_MODE bits */
+#define DW_VR_MII_PCS_MODE_HI_BER25 BIT(2)
+#define DW_VR_MII_PCS_MODE_DISABLE_MLD BIT(1)
+#define DW_VR_MII_PCS_MODE_CLAUSE49 BIT(0)
+
+/* 25G requires these two bits are set.
+ *
+ * The CLAUSE49 bit changes the interface with the MAC
+ * to 64 bit and the BER25 bit changes the measurement
+ * interval to 2ms.
+ */
+#define DW_VR_MII_PCS_MODE_CLAUSE107 \
+ (DW_VR_MII_PCS_MODE_HI_BER25 | DW_VR_MII_PCS_MODE_CLAUSE49)
+
#define DW_XPCS_INFO_DECLARE(_name, _pcs, _pma) \
static const struct dw_xpcs_info _name = { .pcs = _pcs, .pma = _pma }