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

From: mike . marciniszyn

Date: Tue May 26 2026 - 20:13:12 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.

Reviewed-by: Alexander Duyck <alexanderduyck@xxxxxx>
Signed-off-by: Mike Marciniszyn (Meta) <mike.marciniszyn@xxxxxxxxx>
---
v3:
- rename xpcs_config_vl_markers() to xpcs_config_ch_vl_markers()
- add xpcs_write_pcs_ch()
- lanes -> channels
v2: https://lore.kernel.org/all/20260511182604.1338-3-mike.marciniszyn@xxxxxxxxx/
- Replace xpcs_write_pcs_prtad() wiht xpcs_write_pcs_mdev() to
avoid hardcoded prtaddr value
v1: https://lore.kernel.org/all/20260506190904.4029-3-mike.marciniszyn@xxxxxxxxx/

drivers/net/pcs/pcs-xpcs.c | 95 +++++++++++++++++++++++++++++++++++++-
drivers/net/pcs/pcs-xpcs.h | 25 ++++++++++
2 files changed, 119 insertions(+), 1 deletion(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 342d1cc2adbc..515c0615f382 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1430,21 +1430,95 @@ static int xpcs_get_pma_mmd(struct dw_xpcs *xpcs)
return -ENODEV;
}

+/* 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)
{
- int ret = 0, i, pma_mmd;
+ int ret = 0, i, vl, pma_mmd;

pma_mmd = xpcs_get_pma_mmd(xpcs);
if (pma_mmd < 1)
return pma_mmd;

for (i = 0; ret >= 0 && i < v->channels; i++) {
+ /* code word markings */
+ for (vl = 0; ret >= 0 && vl < 4; vl++)
+ ret = xpcs_config_ch_vl_markers(xpcs, i,
+ vl, !vl ?
+ &v->vl0_markers[0] :
+ &v->vl123_markers[vl - 1]);
+ 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);
}
@@ -1457,6 +1531,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);
@@ -1467,6 +1548,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);
@@ -1477,6 +1562,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);
@@ -1487,6 +1576,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 }

--
2.43.0