From: Jonas Jelonek Date: Tue, 28 Jul 2026 22:19:49 +0000 (+0000) Subject: realtek: pcs: rtl931x: wire up RX calibration for 10G fiber X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3c63abd60931e62e54e77eff2f1ce4e51f2ff8be;p=thirdparty%2Fopenwrt.git realtek: pcs: rtl931x: wire up RX calibration for 10G fiber Port the vendor SDK's _phy_rtl9310_fiber_adapt() as rtpcs_931x_sds_rxcal_fiber_adapt(), dispatched from post_config for RTPCS_SDS_ATTACH_FIBER when hw_mode is 10GBase-R (1G/100M fiber isn't calibrated by the vendor SDK either). Add the rxeq_tap_*/rxeq_vth_* setters (set_value, set_adapt) needed to drive it: fiber calibration runs VTH/TAP0 auto-adapt for 200ms, then samples the auto-adapted VTH and locks it in, forces TAP0 to a fixed value (31, per the vendor SDK), and re-locks manual mode, before letting DFE TAP1-4 auto-adapt freely and verifying via a symbol-error recheck loop. Sampling VTH needs the SerDes' debug-readback routing. Add rtpcs_931x_sds_set_debug() and rxeq_vth_get() for it, mirroring _phy_rtl9310_dbg_set()/_phy_rtl9310_dfe_get(). The final symbol-error recheck deliberately deviates from the vendor SDK's shape (adapt once, recheck 3x at 150ms, require exactly 0): symErr's field is only 8 bits wide and reads as a saturated 0xff right after rx_reset(), which looks like "link hasn't relocked yet" rather than a genuine error count - 10G optical relock can plausibly take longer than 150ms. Instead, recheck more times with more patience per check and no reset in between (so a settling link isn't interrupted), and tolerate a small nonzero symbol-error count rather than requiring exactly 0. rtpcs_931x_sds_fiber_get_symerr() already existed but was unused; wire it up alongside the existing rtpcs_931x_sds_clear_symerr(). Link: https://github.com/openwrt/openwrt/pull/24542 Signed-off-by: Jonas Jelonek --- diff --git a/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c b/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c index e26836b27dc..a82117c544b 100644 --- a/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c +++ b/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c @@ -3066,7 +3066,6 @@ static int rtpcs_931x_sds_op_xsg_write(struct rtpcs_serdes *sds, enum rtpcs_page value); } -__maybe_unused static int rtpcs_931x_sds_fiber_get_symerr(struct rtpcs_serdes *sds, enum rtpcs_sds_mode hw_mode) { @@ -3120,6 +3119,33 @@ static void rtpcs_931x_sds_clear_symerr(struct rtpcs_serdes *sds, } } +/* + * rtpcs_931x_sds_set_debug() - Route a coefficient's debug readback. + * + * Vendor SDK: _phy_rtl9310_dbg_set(). Selects which lane of the even/odd + * pair feeds the shared WDIG debug readback register, then selects + * dbg_sel within that lane. Must run before reading any rxeq_*_get(). + * + * Note: This needs to be locked to avoid adjacent SerDes interfering with + * those settings and produce inconsistent results. Currently, this is + * achieved by the global PCS lock. + */ +static int rtpcs_931x_sds_set_debug(struct rtpcs_serdes *sds, unsigned int dbg_sel) +{ + struct rtpcs_serdes *even_sds = rtpcs_sds_get_even(sds); + int ret; + + ret = rtpcs_sds_write(even_sds, PAGE_WDIG, 0x2, (sds == even_sds) ? 0x4b : 0x4c); + if (ret < 0) + return ret; + + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_COM, 0x0, 2, 2, 0x1); + if (ret < 0) + return ret; + + return rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0x15, 11, 10, dbg_sel); +} + static int rtpcs_931x_sds_rxeq_leq_set_adapt(struct rtpcs_serdes *sds, bool enable) { return rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0xd, 7, 7, enable ? 0x0 : 0x1); @@ -3130,6 +3156,96 @@ static int rtpcs_931x_sds_rxeq_leq_set_coef(struct rtpcs_serdes *sds, unsigned i return rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0xd, 6, 2, gain); } +static int rtpcs_931x_sds_rxeq_tap_set_value(struct rtpcs_serdes *sds, unsigned int tap_id, + int tap_even, int tap_odd) +{ + int ret; + + switch (tap_id) { + case 0: + return rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0x1c, 5, 0, + rtpcs_sign_mag_encode(tap_even, 5)); + case 1: + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0x1d, 5, 0, + rtpcs_sign_mag_encode(tap_even, 5)); + if (!ret) + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0x1d, 11, 6, + rtpcs_sign_mag_encode(tap_odd, 5)); + return ret; + case 2: + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0x1f, 5, 0, + rtpcs_sign_mag_encode(tap_even, 5)); + if (!ret) + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0x1f, 11, 6, + rtpcs_sign_mag_encode(tap_odd, 5)); + return ret; + case 3: + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G_EXT, 0x0, 5, 0, + rtpcs_sign_mag_encode(tap_even, 5)); + if (!ret) + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G_EXT, 0x0, 11, 6, + rtpcs_sign_mag_encode(tap_odd, 5)); + return ret; + case 4: + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G_EXT, 0x1, 5, 0, + rtpcs_sign_mag_encode(tap_even, 5)); + if (!ret) + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G_EXT, 0x1, 11, 6, + rtpcs_sign_mag_encode(tap_odd, 5)); + return ret; + default: + return -EINVAL; + } +} + +static int rtpcs_931x_sds_rxeq_tap_set_adapt(struct rtpcs_serdes *sds, unsigned int tap_id, + bool enable) +{ + if (tap_id > 4) + return -EINVAL; + + /* manual-mode enable bits, [10:6] = TAP0-TAP4 */ + return rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0xf, tap_id + 6, tap_id + 6, + enable ? 0x0 : 0x1); +} + +static int rtpcs_931x_sds_rxeq_vth_set_value(struct rtpcs_serdes *sds, unsigned int vth_p, + unsigned int vth_n) +{ + return rtpcs_sds_write_bits(sds, PAGE_ANA_10G_EXT, 0x12, 11, 4, + FIELD_PREP(GENMASK(3, 0), vth_p) | + FIELD_PREP(GENMASK(7, 4), vth_n)); +} + +static int rtpcs_931x_sds_rxeq_vth_set_adapt(struct rtpcs_serdes *sds, bool enable) +{ + return rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0xf, 12, 12, enable ? 0x0 : 0x1); +} + +static int rtpcs_931x_sds_rxeq_vth_get(struct rtpcs_serdes *sds, unsigned int *vth_p, + unsigned int *vth_n) +{ + int ret, val; + + ret = rtpcs_931x_sds_set_debug(sds, 0x2); + if (ret < 0) + return ret; + + ret = rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0x14, 10, 5, 0x0c); /* COEF_SEL = VTH */ + if (ret < 0) + return ret; + usleep_range(1000, 2000); + + val = rtpcs_sds_read_bits(sds, PAGE_WDIG, 0x14, 7, 0); + if (val < 0) + return val; + + *vth_p = FIELD_GET(GENMASK(3, 0), val); + *vth_n = FIELD_GET(GENMASK(7, 4), val); + + return 0; +} + /** * rtpcs_931x_sds_reset_leq_dfe() - Reset LEQ + DFE to a baseline. * @@ -3271,6 +3387,64 @@ static void rtpcs_931x_sds_rxcal_leq_adapt(struct rtpcs_serdes *sds) msleep(100); } +/* + * rtpcs_931x_sds_rxcal_fiber_adapt() - RX calibration for 10G fiber. + * + * Only used for 10GBase-R fiber; calibration not needed for fiber running + * on slower speeds. + * + * Deviates from the vendor SDK's retry shape which is considerably tighter + * (3 symbol error rechecks, 150ms delay, exact-0 target). symErr's field + * (8 bits wide) has been observed reading a saturated 0xff right after + * rx_reset(), which looks like "link hasn't relocked yet" rather than a + * genuine error count. Thus, recheck more times with more patience per + * check instead (no reset in between, so a settling link isn't interrupted), + * and tolerate a small nonzero symbol-error count rather than requiring + * exactly 0. + */ +static void rtpcs_931x_sds_rxcal_fiber_adapt(struct rtpcs_serdes *sds) +{ + struct device *dev = sds->ctrl->dev; + unsigned int vth_p = 0, vth_n = 0; + int i, symerr = -1; + + /* per-port calibration offset in the SDK, kept 0 here */ + rtpcs_sds_write_bits(sds, PAGE_ANA_10G, 0xc, 14, 10, 0x0); + + rtpcs_931x_sds_reset_leq_dfe(sds); + + /* let VTH + TAP0 auto-adapt run and settle before sampling/forcing values */ + rtpcs_931x_sds_rxeq_tap_set_adapt(sds, 0, true); + rtpcs_931x_sds_rxeq_vth_set_adapt(sds, true); + msleep(200); + + /* VTH is sampled from auto-adapt and locked in; TAP0 is always forced to 31 */ + if (rtpcs_931x_sds_rxeq_vth_get(sds, &vth_p, &vth_n) < 0) + dev_warn(dev, "SerDes %u failed to read auto-adapted VTH\n", sds->id); + + rtpcs_931x_sds_rxeq_tap_set_value(sds, 0, 31, 0); + rtpcs_931x_sds_rxeq_tap_set_adapt(sds, 0, false); + rtpcs_931x_sds_rxeq_vth_set_value(sds, vth_p, vth_n); + rtpcs_931x_sds_rxeq_vth_set_adapt(sds, false); + + rtpcs_931x_sds_rx_reset(sds); + + /* let DFE TAP1-4 auto-adapt continuously */ + for (i = 1; i <= 4; i++) + rtpcs_931x_sds_rxeq_tap_set_adapt(sds, i, true); + + for (i = 0; i < 8; i++) { + rtpcs_931x_sds_clear_symerr(sds, RTPCS_SDS_MODE_10GBASER); + msleep(300); + symerr = rtpcs_931x_sds_fiber_get_symerr(sds, RTPCS_SDS_MODE_10GBASER); + if (symerr >= 0 && symerr <= 5) + return; + } + + dev_warn(dev, "SerDes %u fiber RX calibration failed after %d symErr checks\n", + sds->id, i); +} + static int rtpcs_931x_sds_get_pll_select(struct rtpcs_serdes *sds, enum rtpcs_sds_pll_type *pll) { int cmu_page, pll_sel; @@ -3638,8 +3812,13 @@ static int rtpcs_931x_sds_post_config(struct rtpcs_serdes *sds, enum rtpcs_sds_m rtpcs_931x_sds_rxcal_leq_adapt(sds); break; + case RTPCS_SDS_ATTACH_FIBER: + if (hw_mode == RTPCS_SDS_MODE_10GBASER) + rtpcs_931x_sds_rxcal_fiber_adapt(sds); + break; + default: - /* TODO: fiber/DAC RX calibration */ + /* TODO: DAC RX calibration */ break; }