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

From: mike . marciniszyn

Date: Mon May 11 2026 - 14:27:53 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 lane.

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
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 the DW_VR_MII_PCS_PCS_MODE register so there is no issue.

There is also a confict 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>
---
v2:
- 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 | 91 +++++++++++++++++++++++++++++++++++++-
drivers/net/pcs/pcs-xpcs.h | 25 +++++++++++
2 files changed, 115 insertions(+), 1 deletion(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 14f89e56958c..42d64f7ebf34 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1423,9 +1423,39 @@ 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 lanes;
u16 rsfec_ctrl;
+ u16 pcs_mode;
+ u16 vl_intvl;
};

static struct mdio_device *xpcs_find_first_mdev(struct dw_xpcs *xpcs)
@@ -1448,11 +1478,38 @@ xpcs_find_next_mdev(struct dw_xpcs *xpcs, struct mdio_device *mdev)
return &n->mdio;
}

+#define XPCS_VL_TO_REG(vl, lh) \
+ (((vl) * 2) + DW_VR_MII_PCS_VL0_##lh)
+
+static int
+xpcs_write_pcs_mdev(struct mdio_device *mdev, int reg, u16 val)
+{
+ return mdiodev_c45_write(mdev, MDIO_MMD_PCS, reg, val);
+}
+
+static int
+xpcs_config_vl_markers(struct dw_xpcs *xpcs, int vl, struct mdio_device *mdev,
+ 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_mdev(mdev, XPCS_VL_TO_REG(vl, L),
+ ((u16)m->m1 << 8) | m->m0);
+ if (ret < 0)
+ return ret;
+ return xpcs_write_pcs_mdev(mdev, 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, vl, pma_mmd;
struct mdio_device *mdev;
- int ret = 0, i, pma_mmd;

pma_mmd = xpcs_get_pma_mmd(xpcs);
if (pma_mmd < 1)
@@ -1464,6 +1521,23 @@ xpcs_config_rsfec_pma(struct dw_xpcs *xpcs, const struct pma_pcs_values *v)

for (i = 0; mdev && ret >= 0 && i < v->lanes;
i++, mdev = xpcs_find_next_mdev(xpcs, mdev)) {
+ /* code word markings */
+ for (vl = 0; mdev && ret >= 0 && vl < 4; vl++)
+ ret = xpcs_config_vl_markers(xpcs, vl, mdev,
+ !vl ? &v->vl0_markers[0] :
+ &v->vl123_markers[vl - 1]);
+ if (ret < 0)
+ break;
+ /* vendor registers */
+ ret = xpcs_write_pcs_mdev(mdev, DW_VR_MII_PCS_VL_INTVL,
+ v->vl_intvl);
+ if (ret < 0)
+ break;
+ ret = xpcs_write_pcs_mdev(mdev, DW_VR_MII_PCS_PCS_MODE,
+ v->pcs_mode);
+ if (ret < 0)
+ break;
+ /* rsfec register */
ret = mdiodev_c45_write(mdev, pma_mmd, MDIO_PMA_RSFEC_CTRL,
v->rsfec_ctrl);
}
@@ -1478,6 +1552,13 @@ static int xpcs_25gbaser_pma_config(struct dw_xpcs *xpcs)
const struct pma_pcs_values v = {
.rsfec_ctrl = 0,
.lanes = 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);
@@ -1488,6 +1569,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,
.lanes = 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);
@@ -1498,6 +1583,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,
.lanes = 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 187cdec30e70..8161d75370e6 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