From: Mieczyslaw Nalewaj Date: Wed, 22 Jul 2026 18:20:04 +0000 (+0200) Subject: generic: backport rtl8365mb SGMII/HSGMII support for RTL8367S X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d0cf4c8b3ebd730ae41fea430000c8dd182ed188;p=thirdparty%2Fopenwrt.git generic: backport rtl8365mb SGMII/HSGMII support for RTL8367S Backport two upstream patches adding SGMII and HSGMII support to the net/dsa/realtek rtl8365mb driver for the RTL8367S switch: - 943-01-v7.3-net-dsa-realtek-rtl8365mb-add-SGMII-support.patch - 943-02-v7.3-net-dsa-realtek-rtl8365mb-add-HSGMII-support.patch The RTL8367S SerDes can be muxed to external interface 1 (typically the CPU port). SGMII/HSGMII were already listed as supported modes in the chip info table, but only RGMII was implemented. This adds a phylink PCS for the SerDes, with the configuration derived from the GPL-licensed Realtek rtl8367c vendor driver. Tested on a Mercusys MR80X v2.20 (RTL8367S over HSGMII). Signed-off-by: Mieczyslaw Nalewaj Link: https://github.com/openwrt/openwrt/pull/24373 Signed-off-by: Jonas Jelonek --- diff --git a/target/linux/generic/backport-6.12/943-01-v7.3-net-dsa-realtek-rtl8365mb-add-SGMII-support.patch b/target/linux/generic/backport-6.12/943-01-v7.3-net-dsa-realtek-rtl8365mb-add-SGMII-support.patch new file mode 100644 index 00000000000..586f145c0a1 --- /dev/null +++ b/target/linux/generic/backport-6.12/943-01-v7.3-net-dsa-realtek-rtl8365mb-add-SGMII-support.patch @@ -0,0 +1,728 @@ +From 0b577e2fe06c023ab996c3d7684538dbbf6e99bc Mon Sep 17 00:00:00 2001 +From: Johan Alvarado +Date: Sat, 11 Jul 2026 23:31:58 -0500 +Subject: net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S + +The RTL8367S can mux its embedded SerDes to external interface 1, +which is typically used to connect the switch to a CPU port. The chip +info table already declares SGMII as a supported interface mode for +this chip, but the driver only implements RGMII so far. + +Implement SGMII support as a phylink PCS, with the configuration +sequence derived from the GPL-licensed Realtek rtl8367c vendor driver +as distributed in the Mercusys MR80X GPL code drop: + + - Add accessors for the SerDes indirect access registers (SDS_INDACS), + through which the SerDes internal registers are reached. + + - Register a phylink_pcs for the SerDes, selected from mac_select_pcs + for the SGMII interface, so the SerDes handling lives in the PCS + operations rather than in the MAC operations. + + - Probe the SerDes tuning variant from the chip option register once + at setup. The vendor driver keeps two sets of SerDes tuning + parameters and selects between them based on this option; only the + variant for a non-zero option (which all RTL8367S parts seen so far + report) has been validated on hardware, so the SerDes interface + modes are only advertised in that case. An unsupported variant thus + fails at phylink validation time instead of at link configuration + time. + + - Keep the embedded DW8051 microcontroller in reset and disabled. The + vendor driver loads firmware into it to manage the SerDes link, but + analysis of that firmware shows it only duplicates the link + management phylink already performs: it polls the port status and + writes the external interface force registers behind the driver's + back. + + - Clear the line rate bypass bit for the external interface, tune the + SerDes with the vendor-prescribed parameters, mux the SerDes to MAC8 + in SGMII mode and only then take the SerDes out of reset, as the + vendor driver does. + + - After deasserting the SerDes reset, reset the SerDes data path via + the SerDes BMCR register to flush the FIFOs and resync the PLL. + This mirrors what the vendor firmware does right after deasserting + the SerDes reset, and ensures a clean link state from cold boot. + + - Force the SGMII link parameters (link, speed, duplex) in the SDS_MISC + register from pcs_link_up(). SGMII in-band autonegotiation is not + implemented, so only fixed-link and conventional PHY setups are + supported, just like RGMII. This is reported to phylink through + pcs_inband_caps() returning LINK_INBAND_DISABLE, so phylink never + selects an in-band-enabled negotiation mode for this PCS. + + - Program the SerDes pause enables in SDS_MISC from the resolved + pause modes when forcing the MAC external interface in mac_link_up, + as the vendor driver does, rather than leaving whatever state the + boot firmware left there. Flow control testing shows these bits, + not the MAC force pause bits, gate pause on the SerDes external + interface. This is done in the MAC layer because pcs_link_up() + carries no pause information. + + - Implement pcs_get_state() by reading the link status from the + SerDes, with the forced speed and duplex read back from SDS_MISC. + Although the supported fixed-link and conventional PHY setups do not + use it, the PCS owns the SerDes link state, and phylink consults + pcs_get_state() to track the physical link when operating in in-band + mode with autonegotiation disabled. The SerDes has no link interrupt + wired up, so the PCS sets its poll flag. + +Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to +the SoC over SGMII. + +Suggested-by: Luiz Angelo Daros de Luca +Suggested-by: Maxime Chevallier +Suggested-by: Mieczyslaw Nalewaj +Signed-off-by: Johan Alvarado +Reviewed-by: Maxime Chevallier +Reviewed-by: Luiz Angelo Daros de Luca +Reviewed-by: Mieczyslaw Nalewaj +Tested-by: Stanislaw Pal +Link: https://patch.msgid.link/20260711-rtl8367s-sgmii-v6-1-88f7944ddca7@c127.dev +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb_main.c | 515 ++++++++++++++++++++++++++++++- + 1 file changed, 511 insertions(+), 4 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -40,7 +40,8 @@ + * driver has only been tested with a fixed-link, but in principle it should not + * matter. + * +- * NOTE: Currently, only the RGMII interface is implemented in this driver. ++ * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this ++ * driver. + * + * The interrupt line is asserted on link UP/DOWN events. The driver creates a + * custom irqchip to handle this interrupt and demultiplex the events by reading +@@ -94,11 +95,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + + #include "realtek.h" + #include "realtek-smi.h" +@@ -129,6 +132,7 @@ + + /* Chip reset register */ + #define RTL8365MB_CHIP_RESET_REG 0x1322 ++#define RTL8365MB_CHIP_RESET_DW8051_MASK 0x0010 + #define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 + #define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 + +@@ -238,6 +242,76 @@ + #define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 + #define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 + ++/* External interface line rate bypass register - one bit per external ++ * interface, indexed by the external port number with port 5 (the first ++ * external port) as the base. Other RTL8367 families index this register ++ * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only ++ * holds for the RTL8367C-style parts this driver supports. ++ */ ++#define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 ++#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) ++ ++/* SerDes indirect access registers */ ++#define RTL8365MB_SDS_INDACS_CMD_REG 0x6600 ++#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100 ++#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK 0x0080 ++#define RTL8365MB_SDS_INDACS_CMD_WR_MASK 0x0040 ++#define RTL8365MB_SDS_INDACS_ADR_REG 0x6601 ++#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602 ++ ++/* SerDes miscellaneous configuration register */ ++#define RTL8365MB_SDS_MISC_REG 0x1D11 ++#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK 0x4000 ++#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK 0x2000 ++#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK 0x0800 ++#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK 0x0400 ++#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK 0x0200 ++#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180 ++#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040 ++ ++/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR ++ * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the ++ * vendor-specific low bits from phase 1 to phase 2, which triggers a data path ++ * reset and PLL resync. ++ */ ++#define RTL8365MB_SDS_REG_BMCR 0x0000 ++#define RTL8365MB_SDS_BMCR_DPRST_PHASE1 (BMCR_ANENABLE | BMCR_ISOLATE | 0x1) ++#define RTL8365MB_SDS_BMCR_DPRST_PHASE2 (BMCR_ANENABLE | BMCR_ISOLATE | 0x3) ++#define RTL8365MB_SDS_REG_NWAY 0x0002 ++#define RTL8365MB_SDS_NWAY_EN_MASK 0x0200 ++#define RTL8365MB_SDS_NWAY_RESTART_MASK 0x0100 ++#define RTL8365MB_SDS_REG_RESET 0x0003 ++#define RTL8365MB_SDS_RESET_DEASSERT 0x7106 ++#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d ++#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK 0x0010 ++ ++/* The embedded SerDes can only be muxed to external interface 1 (MAC8), ++ * which is port 6. ++ */ ++#define RTL8365MB_SDS_EXT_INTERFACE_ID 1 ++#define RTL8365MB_SDS_EXT_INTERFACE_PORT 6 ++ ++/* Line rate bypass bit for the SerDes external interface */ ++#define RTL8365MB_SDS_BYPASS_LINE_RATE_MASK \ ++ RTL8365MB_BYPASS_LINE_RATE_MASK(RTL8365MB_SDS_EXT_INTERFACE_PORT) ++ ++/* SerDes tuning parameter variant selector. The vendor driver picks between ++ * two sets of SerDes tuning parameters based on this chip option. Reading it ++ * requires first arming the read by writing a magic key to the arm register, ++ * then disarming it afterwards. ++ */ ++#define RTL8365MB_SDS_OPTION_ARM_REG 0x13C0 ++#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249 ++#define RTL8365MB_SDS_OPTION_REG 0x13C1 ++ ++/* Embedded DW8051 microcontroller control registers. The microcontroller ++ * can run firmware to manage the SerDes link, but this driver keeps it in ++ * reset and disabled: phylink already performs the link management that ++ * the firmware would otherwise do. ++ */ ++#define RTL8365MB_MISC_CFG0_REG 0x130C ++#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK 0x0020 ++ + /* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ + #define RTL8365MB_PORT_SPEED_10M 0 + #define RTL8365MB_PORT_SPEED_100M 1 +@@ -551,6 +625,18 @@ static const struct rtl8365mb_jam_tbl_en + { 0x1D32, 0x0002 }, + }; + ++/* SGMII SerDes tuning parameters, lifted from the vendor driver sources. The ++ * vendor driver keeps two variants of this table and selects between them ++ * based on the chip option register; these are the values for a non-zero ++ * option, which is what RTL8367S parts seen so far report. See ++ * rtl8365mb_sds_probe_option(). ++ */ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = { ++ { 0x0480, 0x04D7 }, { 0x0481, 0xF994 }, { 0x0482, 0x2420 }, ++ { 0x0483, 0x6960 }, { 0x0484, 0x9728 }, { 0x0423, 0x9D85 }, ++ { 0x0424, 0xD810 }, { 0x002E, 0x83F2 }, ++}; ++ + enum rtl8365mb_phy_interface_mode { + RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, + RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), +@@ -730,6 +816,9 @@ struct rtl8365mb_port { + * @cpu: CPU tagging and CPU port configuration for this chip + * @mib_lock: prevent concurrent reads of MIB counters + * @ports: per-port data ++ * @pcs: PCS for the SerDes external interface ++ * @sds_supported: SerDes tuning parameters match the chip option, so the ++ * SerDes interface modes can be advertised + * + * Private data for this driver. + */ +@@ -740,8 +829,12 @@ struct rtl8365mb { + struct rtl8365mb_cpu cpu; + struct mutex mib_lock; + struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; ++ struct phylink_pcs pcs; ++ bool sds_supported; + }; + ++#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs) ++ + static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) + { + u32 val; +@@ -1042,6 +1135,333 @@ static int rtl8365mb_ext_config_rgmii(st + return 0; + } + ++static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data) ++{ ++ int ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); ++ if (ret) ++ return ret; ++ ++ /* The SerDes indirect access engine completes the command within the ++ * register write transaction, so there is no need to wait or poll for ++ * completion before the next access, matching the vendor driver. ++ */ ++ return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, ++ RTL8365MB_SDS_INDACS_CMD_RUN_MASK | ++ RTL8365MB_SDS_INDACS_CMD_WR_MASK); ++} ++ ++static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data) ++{ ++ u32 val; ++ int ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, ++ RTL8365MB_SDS_INDACS_CMD_RUN_MASK); ++ if (ret) ++ return ret; ++ ++ /* Wait for the indirect read to complete: the engine clears the BUSY ++ * bit once the data register holds the result. ++ */ ++ ret = regmap_read_poll_timeout(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, ++ val, ++ !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK), ++ 10, 1000); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val); ++ if (ret) ++ return ret; ++ ++ *data = val; ++ ++ return 0; ++} ++ ++/* The vendor driver selects between two sets of SerDes tuning parameters based ++ * on the chip option register. Only the variant for a non-zero option has been ++ * tested on real hardware - the RTL8367S parts seen so far all report 1. The ++ * variant for option 0 uses different tuning values that cannot be verified, ++ * so probe the option once at setup and only advertise the SerDes interface ++ * modes when the tuning parameters are known to match, so that an unsupported ++ * variant fails at phylink validation time rather than when configuring the ++ * link. ++ */ ++static int rtl8365mb_sds_probe_option(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ const struct rtl8365mb_extint *extint; ++ u32 option; ++ int ret; ++ int i; ++ ++ /* Nothing to probe if no external interface is wired to the SerDes */ ++ for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { ++ extint = &mb->chip_info->extints[i]; ++ ++ if (extint->supported_interfaces & ++ (RTL8365MB_PHY_INTERFACE_MODE_SGMII | ++ RTL8365MB_PHY_INTERFACE_MODE_HSGMII)) ++ break; ++ } ++ if (i == RTL8365MB_MAX_NUM_EXTINTS) ++ return 0; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, ++ RTL8365MB_SDS_OPTION_ARM_KEY); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(priv->map, RTL8365MB_SDS_OPTION_REG, &option); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, 0); ++ if (ret) ++ return ret; ++ ++ if (option == 0) { ++ dev_warn(priv->dev, ++ "unsupported SerDes tuning variant (chip option 0), disabling SerDes interface modes\n"); ++ return 0; ++ } ++ ++ mb->sds_supported = true; ++ ++ return 0; ++} ++ ++static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, ++ phy_interface_t interface, ++ const unsigned long *advertising, ++ bool permit_pause_to_mac) ++{ ++ const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; ++ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); ++ struct realtek_priv *priv; ++ u16 val; ++ int ret; ++ int i; ++ ++ priv = mb->priv; ++ ++ /* Hold the embedded DW8051 microcontroller in reset and keep it ++ * disabled. The vendor driver loads firmware into it to manage the ++ * SerDes link, but the firmware only duplicates work that phylink ++ * already does: it polls the port status and forces the external ++ * interface configuration in the very registers this driver manages. ++ * Letting it run would race with phylink. ++ */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_CHIP_RESET_REG, ++ RTL8365MB_CHIP_RESET_DW8051_MASK, ++ RTL8365MB_CHIP_RESET_DW8051_MASK); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(priv->map, RTL8365MB_MISC_CFG0_REG, ++ RTL8365MB_MISC_CFG0_DW8051_EN_MASK, 0); ++ if (ret) ++ return ret; ++ ++ /* The vendor driver clears the line rate bypass for all interface ++ * modes except TMII. ++ */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_BYPASS_LINE_RATE_REG, ++ RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, 0); ++ if (ret) ++ return ret; ++ ++ /* Tune the SerDes with vendor-prescribed parameters */ ++ for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) { ++ ret = rtl8365mb_sds_write(priv, ++ rtl8365mb_sds_jam_sgmii[i].reg, ++ rtl8365mb_sds_jam_sgmii[i].val); ++ if (ret) ++ return ret; ++ } ++ ++ /* Mux the SerDes to MAC8 in SGMII mode */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, ++ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | ++ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, ++ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK); ++ if (ret) ++ return ret; ++ ++ val = RTL8365MB_EXT_PORT_MODE_SGMII ++ << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), ++ RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), ++ val); ++ if (ret) ++ return ret; ++ ++ /* Take the SerDes out of reset. The vendor driver does this only ++ * after the SerDes mux and the interface mode are configured. ++ */ ++ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET, ++ RTL8365MB_SDS_RESET_DEASSERT); ++ if (ret) ++ return ret; ++ ++ /* Reset the SerDes data path and resync its PLL, mirroring what the ++ * vendor firmware does right after deasserting the SerDes reset. ++ * This flushes the FIFOs and ensures a clean state for the link, ++ * preventing silent drops and CRC errors. ++ */ ++ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, ++ RTL8365MB_SDS_BMCR_DPRST_PHASE1); ++ if (ret) ++ return ret; ++ ++ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, ++ RTL8365MB_SDS_BMCR_DPRST_PHASE2); ++ if (ret) ++ return ret; ++ ++ /* Keep SGMII in-band autonegotiation disabled: the link parameters are ++ * forced from rtl8365mb_pcs_link_up() instead. ++ */ ++ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val); ++ if (ret) ++ return ret; ++ ++ val &= ~RTL8365MB_SDS_NWAY_EN_MASK; ++ val |= RTL8365MB_SDS_NWAY_RESTART_MASK; ++ ++ return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val); ++} ++ ++static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) ++{ ++ return interface == PHY_INTERFACE_MODE_SGMII; ++} ++ ++static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs, ++ phy_interface_t interface) ++{ ++ /* In-band autonegotiation is not implemented; the link is always ++ * forced. Report that to phylink so that it never selects an ++ * in-band-enabled negotiation mode for this PCS. ++ */ ++ return LINK_INBAND_DISABLE; ++} ++ ++static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs, ++ struct phylink_link_state *state) ++{ ++ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); ++ struct realtek_priv *priv = mb->priv; ++ u16 status; ++ u32 val; ++ int ret; ++ ++ /* In-band autonegotiation is not implemented, so the link parameters are ++ * forced from rtl8365mb_pcs_link_up(). The real link state must still be ++ * read from the SerDes itself: the embedded DW8051 microcontroller that ++ * the vendor firmware uses to poll the SerDes is kept disabled (see ++ * rtl8365mb_pcs_config()), so the link status register can be read ++ * directly through the SDS_INDACS window without racing the auto-poll. ++ */ ++ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status); ++ if (ret) { ++ state->link = false; ++ return; ++ } ++ ++ state->link = !!(status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK); ++ state->an_complete = state->link; ++ if (!state->link) ++ return; ++ ++ /* The speed and duplex are forced; read them back from the values ++ * programmed into the SerDes MISC register. ++ */ ++ ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val); ++ if (ret) { ++ state->link = false; ++ return; ++ } ++ ++ state->duplex = (val & RTL8365MB_SDS_MISC_SGMII_FDUP_MASK) ? ++ DUPLEX_FULL : DUPLEX_HALF; ++ ++ switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) { ++ case RTL8365MB_PORT_SPEED_1000M: ++ state->speed = SPEED_1000; ++ break; ++ case RTL8365MB_PORT_SPEED_100M: ++ state->speed = SPEED_100; ++ break; ++ case RTL8365MB_PORT_SPEED_10M: ++ state->speed = SPEED_10; ++ break; ++ } ++} ++ ++static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs, ++ unsigned int neg_mode, ++ phy_interface_t interface, int speed, ++ int duplex) ++{ ++ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); ++ struct realtek_priv *priv = mb->priv; ++ u32 mask = RTL8365MB_SDS_MISC_SGMII_FDUP_MASK | ++ RTL8365MB_SDS_MISC_SGMII_LINK_MASK | ++ RTL8365MB_SDS_MISC_SGMII_SPD_MASK; ++ u32 val = RTL8365MB_SDS_MISC_SGMII_LINK_MASK; ++ u32 r_speed; ++ int ret; ++ ++ if (speed == SPEED_1000) { ++ r_speed = RTL8365MB_PORT_SPEED_1000M; ++ } else if (speed == SPEED_100) { ++ r_speed = RTL8365MB_PORT_SPEED_100M; ++ } else if (speed == SPEED_10) { ++ r_speed = RTL8365MB_PORT_SPEED_10M; ++ } else { ++ dev_err(priv->dev, "unsupported SerDes speed %s\n", ++ phy_speed_to_str(speed)); ++ return; ++ } ++ ++ val |= FIELD_PREP(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, r_speed); ++ ++ if (duplex == DUPLEX_FULL) ++ val |= RTL8365MB_SDS_MISC_SGMII_FDUP_MASK; ++ ++ /* pcs_link_up() carries no pause information, so the SerDes flow ++ * control bits are programmed together with the MAC external interface ++ * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause ++ * modes are known. ++ */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val); ++ if (ret) { ++ dev_err(priv->dev, "failed to force SerDes link: %pe\n", ++ ERR_PTR(ret)); ++ return; ++ } ++} ++ ++static const struct phylink_pcs_ops rtl8365mb_pcs_ops = { ++ .pcs_inband_caps = rtl8365mb_pcs_inband_caps, ++ .pcs_config = rtl8365mb_pcs_config, ++ .pcs_get_state = rtl8365mb_pcs_get_state, ++ .pcs_link_up = rtl8365mb_pcs_link_up, ++}; ++ + static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, + bool link, int speed, int duplex, + bool tx_pause, bool rx_pause) +@@ -1118,6 +1538,8 @@ static void rtl8365mb_phylink_get_caps(s + { + const struct rtl8365mb_extint *extint = + rtl8365mb_get_port_extint(ds->priv, port); ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb *mb = priv->chip_data; + + config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | + MAC_10 | MAC_100 | MAC_1000FD; +@@ -1141,6 +1563,25 @@ static void rtl8365mb_phylink_get_caps(s + + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) + phy_interface_set_rgmii(config->supported_interfaces); ++ ++ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII && ++ mb->sds_supported) ++ __set_bit(PHY_INTERFACE_MODE_SGMII, ++ config->supported_interfaces); ++} ++ ++static struct phylink_pcs * ++rtl8365mb_phylink_mac_select_pcs(struct phylink_config *config, ++ phy_interface_t interface) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ struct rtl8365mb *mb = priv->chip_data; ++ ++ if (rtl8365mb_interface_is_serdes(interface)) ++ return &mb->pcs; ++ ++ return NULL; + } + + static void rtl8365mb_phylink_mac_config(struct phylink_config *config, +@@ -1168,6 +1609,12 @@ static void rtl8365mb_phylink_mac_config + return; + } + ++ /* SGMII is handled by the SerDes PCS, configured through the ++ * phylink_pcs ops, so there is nothing to do here for it. ++ */ ++ if (rtl8365mb_interface_is_serdes(state->interface)) ++ return; ++ + /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also + * supports + */ +@@ -1188,7 +1635,13 @@ static void rtl8365mb_phylink_mac_link_d + p = &mb->ports[port]; + cancel_delayed_work_sync(&p->mib_work); + +- if (phy_interface_mode_is_rgmii(interface)) { ++ /* phylink has no pcs_link_down callback, so on the SerDes path only the ++ * MAC external interface force is reset here. Clearing the MAC force is ++ * enough to bring the link down; the SerDes keeps presenting its last ++ * forced state until the next pcs_link_up() reprograms it. ++ */ ++ if (phy_interface_mode_is_rgmii(interface) || ++ rtl8365mb_interface_is_serdes(interface)) { + ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, + false, false); + if (ret) +@@ -1218,14 +1671,51 @@ static void rtl8365mb_phylink_mac_link_u + p = &mb->ports[port]; + schedule_delayed_work(&p->mib_work, 0); + +- if (phy_interface_mode_is_rgmii(interface)) { ++ /* The SerDes forced link state is programmed by the PCS in ++ * rtl8365mb_pcs_link_up(); here only the MAC external interface force ++ * is configured, for both RGMII and SerDes. ++ */ ++ if (phy_interface_mode_is_rgmii(interface) || ++ rtl8365mb_interface_is_serdes(interface)) { + ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, + duplex, tx_pause, + rx_pause); +- if (ret) ++ if (ret) { + dev_err(priv->dev, + "failed to force mode on port %d: %pe\n", port, + ERR_PTR(ret)); ++ return; ++ } ++ ++ /* The SerDes has its own pause enables; program them from ++ * the resolved pause modes, as the vendor driver does when ++ * forcing the link on a SerDes external interface. These ++ * bits, not the MAC force pause bits, gate pause on the ++ * SerDes external interface: flow control testing shows ++ * that pause frames are only emitted with the SerDes TXFC ++ * bit set, while the MAC force pause bits alone have no ++ * effect on this port. This is done here rather than in ++ * rtl8365mb_pcs_link_up() because pcs_link_up() carries no ++ * pause information. ++ */ ++ if (rtl8365mb_interface_is_serdes(interface)) { ++ u32 val = 0; ++ ++ if (tx_pause) ++ val |= RTL8365MB_SDS_MISC_SGMII_TXFC_MASK; ++ if (rx_pause) ++ val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK; ++ ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_SDS_MISC_REG, ++ RTL8365MB_SDS_MISC_SGMII_TXFC_MASK | ++ RTL8365MB_SDS_MISC_SGMII_RXFC_MASK, ++ val); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to force SerDes pause modes on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ } + + return; + } +@@ -2419,6 +2909,14 @@ static int rtl8365mb_setup(struct dsa_sw + mb = priv->chip_data; + cpu = &mb->cpu; + ++ mb->pcs.ops = &rtl8365mb_pcs_ops; ++ ++ /* The SerDes has no link interrupt wired up, so phylink must poll the ++ * PCS for link changes when it tracks the link through pcs_get_state() ++ * (in-band mode with autonegotiation disabled). ++ */ ++ mb->pcs.poll = true; ++ + ret = rtl8365mb_reset_chip(priv); + if (ret) { + dev_err(priv->dev, "failed to reset chip: %pe\n", +@@ -2426,6 +2924,13 @@ static int rtl8365mb_setup(struct dsa_sw + goto out_error; + } + ++ ret = rtl8365mb_sds_probe_option(priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to probe SerDes chip option: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ + /* Configure switch to vendor-defined initial state */ + ret = rtl8365mb_switch_init(priv); + if (ret) { +@@ -2658,6 +3163,7 @@ static int rtl8365mb_detect(struct realt + } + + static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { ++ .mac_select_pcs = rtl8365mb_phylink_mac_select_pcs, + .mac_config = rtl8365mb_phylink_mac_config, + .mac_link_down = rtl8365mb_phylink_mac_link_down, + .mac_link_up = rtl8365mb_phylink_mac_link_up, diff --git a/target/linux/generic/backport-6.12/943-02-v7.3-net-dsa-realtek-rtl8365mb-add-HSGMII-support.patch b/target/linux/generic/backport-6.12/943-02-v7.3-net-dsa-realtek-rtl8365mb-add-HSGMII-support.patch new file mode 100644 index 00000000000..5aeb8c20761 --- /dev/null +++ b/target/linux/generic/backport-6.12/943-02-v7.3-net-dsa-realtek-rtl8365mb-add-HSGMII-support.patch @@ -0,0 +1,305 @@ +From 987137345f3312fd68cc9c11bd46b754bfb0046f Mon Sep 17 00:00:00 2001 +From: Johan Alvarado +Date: Sat, 11 Jul 2026 23:31:59 -0500 +Subject: net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +In addition to SGMII, the RTL8367S SerDes also supports HSGMII, which +carries 2.5 Gbps with the same signaling as SGMII at 2.5x clock rate. +The chip info table already declares HSGMII as a supported interface +mode for external interface 1. + +Extend the SerDes PCS to handle HSGMII, which phylink represents as +2500base-x: + + - Select the HSGMII SerDes tuning parameters and external interface + mode, and mux the SerDes to MAC8 in HSGMII mode, from pcs_config() + according to the interface. The parameters are again lifted from the + GPL-licensed Realtek rtl8367c vendor driver, and again only cover + the tuning variant for a non-zero chip option, so the mode is gated + on the option probed at setup. + + - Advertise 2500base-x and MAC_2500FD on ports whose external + interface supports HSGMII. + + - Accept SPEED_2500 in the forced link configuration. The MAC speed + field has no 2.5 Gbps value: the rate is determined by the HSGMII + SerDes configuration, and the vendor driver programs the 1 Gbps + value here, so do the same. + + - Raise the port 6 ingress and egress rate limiters to their maximum + at setup time, as the vendor switch init does unconditionally for + the whole chip family. The chip resets them to 0x1FFFF (~1.048 Gbps + in units of 8 Kbps), which caps the aggregate HSGMII throughput at + roughly 1 Gbps. The vendor documentation describes the reset + default as disabling the limiter, but the cap is real: on an + RTL8367S-based Mercusys MR85X running an OpenWrt backport of this + series, several clients on 1 Gbps user ports were limited to about + 1.02 Gbps combined across the HSGMII CPU port until these limiters + were raised, after which throughput reached about 2 Gbps [1]. The + related HSGMII scheduler line rate (LINE_RATE_HSG_H) is already set + to its maximum by the common init jam table. + +Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to +the SoC over HSGMII. + +Link: https://github.com/openwrt/openwrt/pull/19445#issuecomment-4505613294 [1] +Suggested-by: Luiz Angelo Daros de Luca +Suggested-by: Mieczyslaw Nalewaj +Signed-off-by: Johan Alvarado +Tested-by: Stanisław Pal +Reviewed-by: Luiz Angelo Daros de Luca +Reviewed-by: Mieczyslaw Nalewaj +Tested-by: Stanislaw Pal +Link: https://patch.msgid.link/20260711-rtl8367s-sgmii-v6-2-88f7944ddca7@c127.dev +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb_main.c | 134 +++++++++++++++++++++++++++---- + 1 file changed, 118 insertions(+), 16 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -40,8 +40,8 @@ + * driver has only been tested with a fixed-link, but in principle it should not + * matter. + * +- * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this +- * driver. ++ * NOTE: Currently, only the RGMII, SGMII and HSGMII interfaces are implemented ++ * in this driver. + * + * The interrupt line is asserted on link UP/DOWN events. The driver creates a + * custom irqchip to handle this interrupt and demultiplex the events by reading +@@ -251,6 +251,18 @@ + #define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 + #define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) + ++/* Port 6 ingress and egress rate limiter registers. Each limit is a 19-bit ++ * value in units of 8 Kbps, split across a 16-bit LSB register (CTRL0) and a ++ * 3-bit MSB field (CTRL1). The chip resets them to 0x1FFFF; see ++ * rtl8365mb_sds_raise_rate_limits(). ++ */ ++#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG 0x00CF ++#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG 0x00D0 ++#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK 0x0007 ++#define RTL8365MB_PORT6_EGRESSBW_CTRL0_REG 0x0398 ++#define RTL8365MB_PORT6_EGRESSBW_CTRL1_REG 0x0399 ++#define RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK 0x0007 ++ + /* SerDes indirect access registers */ + #define RTL8365MB_SDS_INDACS_CMD_REG 0x6600 + #define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100 +@@ -637,6 +649,18 @@ static const struct rtl8365mb_jam_tbl_en + { 0x0424, 0xD810 }, { 0x002E, 0x83F2 }, + }; + ++/* HSGMII SerDes tuning parameters, lifted from the vendor driver sources. As ++ * with the SGMII table, the vendor driver keeps several variants and selects ++ * one based on the chip option register; these are the values for a non-zero ++ * option, which is what RTL8367S parts seen so far report. See ++ * rtl8365mb_sds_probe_option(). ++ */ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = { ++ { 0x0500, 0x82F0 }, { 0x0501, 0xF195 }, { 0x0502, 0x31A2 }, ++ { 0x0503, 0x7960 }, { 0x0504, 0x9728 }, { 0x0423, 0x9D85 }, ++ { 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 }, ++}; ++ + enum rtl8365mb_phy_interface_mode { + RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, + RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), +@@ -1242,20 +1266,70 @@ static int rtl8365mb_sds_probe_option(st + return 0; + } + ++/* The vendor driver raises the port 6 ingress and egress rate limiters to ++ * their maximum in its switch init, unconditionally for the whole chip ++ * family. The chip reset in rtl8365mb_setup() puts them back to their reset ++ * default of 0x1FFFF, a ~1.048 Gbps limit which caps the aggregate ++ * throughput of an HSGMII CPU port at roughly 1 Gbps. The vendor ++ * documentation describes the reset default as disabling the limiter, but ++ * the cap has been observed on hardware. Raise them likewise, to 0x7FFFF ++ * (~4.19 Gbps, above the HSGMII line rate). The related HSGMII scheduler ++ * line rate register (LINE_RATE_HSG_H, 0x03FA) is already set to its ++ * maximum by the common init jam table. ++ */ ++static int rtl8365mb_sds_raise_rate_limits(struct realtek_priv *priv) ++{ ++ int ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG, ++ 0xFFFF); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG, ++ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK, ++ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL0_REG, ++ 0xFFFF); ++ if (ret) ++ return ret; ++ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL1_REG, ++ RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK, ++ RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK); ++} ++ + static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) + { ++ const struct rtl8365mb_jam_tbl_entry *sds_jam; + const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv; ++ size_t sds_jam_size; ++ u32 mode; + u16 val; + int ret; + int i; + + priv = mb->priv; + ++ if (interface == PHY_INTERFACE_MODE_2500BASEX) { ++ sds_jam = rtl8365mb_sds_jam_hsgmii; ++ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii); ++ mode = RTL8365MB_EXT_PORT_MODE_HSGMII; ++ } else { ++ sds_jam = rtl8365mb_sds_jam_sgmii; ++ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); ++ mode = RTL8365MB_EXT_PORT_MODE_SGMII; ++ } ++ + /* Hold the embedded DW8051 microcontroller in reset and keep it + * disabled. The vendor driver loads firmware into it to manage the + * SerDes link, but the firmware only duplicates work that phylink +@@ -1283,24 +1357,24 @@ static int rtl8365mb_pcs_config(struct p + return ret; + + /* Tune the SerDes with vendor-prescribed parameters */ +- for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) { +- ret = rtl8365mb_sds_write(priv, +- rtl8365mb_sds_jam_sgmii[i].reg, +- rtl8365mb_sds_jam_sgmii[i].val); ++ for (i = 0; i < sds_jam_size; i++) { ++ ret = rtl8365mb_sds_write(priv, sds_jam[i].reg, ++ sds_jam[i].val); + if (ret) + return ret; + } + +- /* Mux the SerDes to MAC8 in SGMII mode */ ++ /* Mux the SerDes to MAC8 in the requested mode */ + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, +- RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK); ++ mode == RTL8365MB_EXT_PORT_MODE_SGMII ? ++ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK : ++ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK); + if (ret) + return ret; + +- val = RTL8365MB_EXT_PORT_MODE_SGMII +- << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); ++ val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); + ret = regmap_update_bits(priv->map, + RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), + RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), +@@ -1346,7 +1420,8 @@ static int rtl8365mb_pcs_config(struct p + + static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) + { +- return interface == PHY_INTERFACE_MODE_SGMII; ++ return interface == PHY_INTERFACE_MODE_SGMII || ++ interface == PHY_INTERFACE_MODE_2500BASEX; + } + + static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs, +@@ -1400,7 +1475,9 @@ static void rtl8365mb_pcs_get_state(stru + + switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) { + case RTL8365MB_PORT_SPEED_1000M: +- state->speed = SPEED_1000; ++ state->speed = ++ state->interface == PHY_INTERFACE_MODE_2500BASEX ? ++ SPEED_2500 : SPEED_1000; + break; + case RTL8365MB_PORT_SPEED_100M: + state->speed = SPEED_100; +@@ -1425,7 +1502,11 @@ static void rtl8365mb_pcs_link_up(struct + u32 r_speed; + int ret; + +- if (speed == SPEED_1000) { ++ /* The speed field has no value for 2.5 Gbps: the rate is determined by ++ * the HSGMII SerDes configuration, and the vendor driver programs the ++ * 1 Gbps value here. ++ */ ++ if (speed == SPEED_2500 || speed == SPEED_1000) { + r_speed = RTL8365MB_PORT_SPEED_1000M; + } else if (speed == SPEED_100) { + r_speed = RTL8365MB_PORT_SPEED_100M; +@@ -1485,7 +1566,11 @@ static int rtl8365mb_ext_config_forcemod + r_rx_pause = rx_pause ? 1 : 0; + r_tx_pause = tx_pause ? 1 : 0; + +- if (speed == SPEED_1000) { ++ /* The speed field has no value for 2.5 Gbps: the rate is ++ * determined by the HSGMII SerDes configuration, and the ++ * vendor driver programs the 1 Gbps value here. ++ */ ++ if (speed == SPEED_2500 || speed == SPEED_1000) { + r_speed = RTL8365MB_PORT_SPEED_1000M; + } else if (speed == SPEED_100) { + r_speed = RTL8365MB_PORT_SPEED_100M; +@@ -1568,6 +1653,13 @@ static void rtl8365mb_phylink_get_caps(s + mb->sds_supported) + __set_bit(PHY_INTERFACE_MODE_SGMII, + config->supported_interfaces); ++ ++ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII && ++ mb->sds_supported) { ++ __set_bit(PHY_INTERFACE_MODE_2500BASEX, ++ config->supported_interfaces); ++ config->mac_capabilities |= MAC_2500FD; ++ } + } + + static struct phylink_pcs * +@@ -1609,8 +1701,8 @@ static void rtl8365mb_phylink_mac_config + return; + } + +- /* SGMII is handled by the SerDes PCS, configured through the +- * phylink_pcs ops, so there is nothing to do here for it. ++ /* SGMII and 2500base-x are handled by the SerDes PCS, configured ++ * through the phylink_pcs ops, so nothing to do here for them. + */ + if (rtl8365mb_interface_is_serdes(state->interface)) + return; +@@ -2939,6 +3031,16 @@ static int rtl8365mb_setup(struct dsa_sw + goto out_error; + } + ++ if (mb->sds_supported) { ++ ret = rtl8365mb_sds_raise_rate_limits(priv); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to raise port rate limits: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ } ++ + /* Set up cascading IRQs */ + ret = rtl8365mb_irq_setup(priv); + if (ret == -EPROBE_DEFER) diff --git a/target/linux/generic/backport-6.18/943-01-v7.3-net-dsa-realtek-rtl8365mb-add-SGMII-support.patch b/target/linux/generic/backport-6.18/943-01-v7.3-net-dsa-realtek-rtl8365mb-add-SGMII-support.patch new file mode 100644 index 00000000000..5b60932b9b8 --- /dev/null +++ b/target/linux/generic/backport-6.18/943-01-v7.3-net-dsa-realtek-rtl8365mb-add-SGMII-support.patch @@ -0,0 +1,729 @@ +From 0b577e2fe06c023ab996c3d7684538dbbf6e99bc Mon Sep 17 00:00:00 2001 +From: Johan Alvarado +Date: Sat, 11 Jul 2026 23:31:58 -0500 +Subject: net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S + +The RTL8367S can mux its embedded SerDes to external interface 1, +which is typically used to connect the switch to a CPU port. The chip +info table already declares SGMII as a supported interface mode for +this chip, but the driver only implements RGMII so far. + +Implement SGMII support as a phylink PCS, with the configuration +sequence derived from the GPL-licensed Realtek rtl8367c vendor driver +as distributed in the Mercusys MR80X GPL code drop: + + - Add accessors for the SerDes indirect access registers (SDS_INDACS), + through which the SerDes internal registers are reached. + + - Register a phylink_pcs for the SerDes, selected from mac_select_pcs + for the SGMII interface, so the SerDes handling lives in the PCS + operations rather than in the MAC operations. + + - Probe the SerDes tuning variant from the chip option register once + at setup. The vendor driver keeps two sets of SerDes tuning + parameters and selects between them based on this option; only the + variant for a non-zero option (which all RTL8367S parts seen so far + report) has been validated on hardware, so the SerDes interface + modes are only advertised in that case. An unsupported variant thus + fails at phylink validation time instead of at link configuration + time. + + - Keep the embedded DW8051 microcontroller in reset and disabled. The + vendor driver loads firmware into it to manage the SerDes link, but + analysis of that firmware shows it only duplicates the link + management phylink already performs: it polls the port status and + writes the external interface force registers behind the driver's + back. + + - Clear the line rate bypass bit for the external interface, tune the + SerDes with the vendor-prescribed parameters, mux the SerDes to MAC8 + in SGMII mode and only then take the SerDes out of reset, as the + vendor driver does. + + - After deasserting the SerDes reset, reset the SerDes data path via + the SerDes BMCR register to flush the FIFOs and resync the PLL. + This mirrors what the vendor firmware does right after deasserting + the SerDes reset, and ensures a clean link state from cold boot. + + - Force the SGMII link parameters (link, speed, duplex) in the SDS_MISC + register from pcs_link_up(). SGMII in-band autonegotiation is not + implemented, so only fixed-link and conventional PHY setups are + supported, just like RGMII. This is reported to phylink through + pcs_inband_caps() returning LINK_INBAND_DISABLE, so phylink never + selects an in-band-enabled negotiation mode for this PCS. + + - Program the SerDes pause enables in SDS_MISC from the resolved + pause modes when forcing the MAC external interface in mac_link_up, + as the vendor driver does, rather than leaving whatever state the + boot firmware left there. Flow control testing shows these bits, + not the MAC force pause bits, gate pause on the SerDes external + interface. This is done in the MAC layer because pcs_link_up() + carries no pause information. + + - Implement pcs_get_state() by reading the link status from the + SerDes, with the forced speed and duplex read back from SDS_MISC. + Although the supported fixed-link and conventional PHY setups do not + use it, the PCS owns the SerDes link state, and phylink consults + pcs_get_state() to track the physical link when operating in in-band + mode with autonegotiation disabled. The SerDes has no link interrupt + wired up, so the PCS sets its poll flag. + +Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to +the SoC over SGMII. + +Suggested-by: Luiz Angelo Daros de Luca +Suggested-by: Maxime Chevallier +Suggested-by: Mieczyslaw Nalewaj +Signed-off-by: Johan Alvarado +Reviewed-by: Maxime Chevallier +Reviewed-by: Luiz Angelo Daros de Luca +Reviewed-by: Mieczyslaw Nalewaj +Tested-by: Stanislaw Pal +Link: https://patch.msgid.link/20260711-rtl8367s-sgmii-v6-1-88f7944ddca7@c127.dev +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb_main.c | 515 ++++++++++++++++++++++++++++++- + 1 file changed, 511 insertions(+), 4 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -40,7 +40,8 @@ + * driver has only been tested with a fixed-link, but in principle it should not + * matter. + * +- * NOTE: Currently, only the RGMII interface is implemented in this driver. ++ * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this ++ * driver. + * + * The interrupt line is asserted on link UP/DOWN events. The driver creates a + * custom irqchip to handle this interrupt and demultiplex the events by reading +@@ -94,11 +95,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + + #include "realtek.h" + #include "realtek-smi.h" +@@ -129,6 +132,7 @@ + + /* Chip reset register */ + #define RTL8365MB_CHIP_RESET_REG 0x1322 ++#define RTL8365MB_CHIP_RESET_DW8051_MASK 0x0010 + #define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 + #define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 + +@@ -238,6 +242,76 @@ + #define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 + #define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 + ++/* External interface line rate bypass register - one bit per external ++ * interface, indexed by the external port number with port 5 (the first ++ * external port) as the base. Other RTL8367 families index this register ++ * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only ++ * holds for the RTL8367C-style parts this driver supports. ++ */ ++#define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 ++#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) ++ ++/* SerDes indirect access registers */ ++#define RTL8365MB_SDS_INDACS_CMD_REG 0x6600 ++#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100 ++#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK 0x0080 ++#define RTL8365MB_SDS_INDACS_CMD_WR_MASK 0x0040 ++#define RTL8365MB_SDS_INDACS_ADR_REG 0x6601 ++#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602 ++ ++/* SerDes miscellaneous configuration register */ ++#define RTL8365MB_SDS_MISC_REG 0x1D11 ++#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK 0x4000 ++#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK 0x2000 ++#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK 0x0800 ++#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK 0x0400 ++#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK 0x0200 ++#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180 ++#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040 ++ ++/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR ++ * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the ++ * vendor-specific low bits from phase 1 to phase 2, which triggers a data path ++ * reset and PLL resync. ++ */ ++#define RTL8365MB_SDS_REG_BMCR 0x0000 ++#define RTL8365MB_SDS_BMCR_DPRST_PHASE1 (BMCR_ANENABLE | BMCR_ISOLATE | 0x1) ++#define RTL8365MB_SDS_BMCR_DPRST_PHASE2 (BMCR_ANENABLE | BMCR_ISOLATE | 0x3) ++#define RTL8365MB_SDS_REG_NWAY 0x0002 ++#define RTL8365MB_SDS_NWAY_EN_MASK 0x0200 ++#define RTL8365MB_SDS_NWAY_RESTART_MASK 0x0100 ++#define RTL8365MB_SDS_REG_RESET 0x0003 ++#define RTL8365MB_SDS_RESET_DEASSERT 0x7106 ++#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d ++#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK 0x0010 ++ ++/* The embedded SerDes can only be muxed to external interface 1 (MAC8), ++ * which is port 6. ++ */ ++#define RTL8365MB_SDS_EXT_INTERFACE_ID 1 ++#define RTL8365MB_SDS_EXT_INTERFACE_PORT 6 ++ ++/* Line rate bypass bit for the SerDes external interface */ ++#define RTL8365MB_SDS_BYPASS_LINE_RATE_MASK \ ++ RTL8365MB_BYPASS_LINE_RATE_MASK(RTL8365MB_SDS_EXT_INTERFACE_PORT) ++ ++/* SerDes tuning parameter variant selector. The vendor driver picks between ++ * two sets of SerDes tuning parameters based on this chip option. Reading it ++ * requires first arming the read by writing a magic key to the arm register, ++ * then disarming it afterwards. ++ */ ++#define RTL8365MB_SDS_OPTION_ARM_REG 0x13C0 ++#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249 ++#define RTL8365MB_SDS_OPTION_REG 0x13C1 ++ ++/* Embedded DW8051 microcontroller control registers. The microcontroller ++ * can run firmware to manage the SerDes link, but this driver keeps it in ++ * reset and disabled: phylink already performs the link management that ++ * the firmware would otherwise do. ++ */ ++#define RTL8365MB_MISC_CFG0_REG 0x130C ++#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK 0x0020 ++ + /* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ + #define RTL8365MB_PORT_SPEED_10M 0 + #define RTL8365MB_PORT_SPEED_100M 1 +@@ -551,6 +625,18 @@ static const struct rtl8365mb_jam_tbl_en + { 0x1D32, 0x0002 }, + }; + ++/* SGMII SerDes tuning parameters, lifted from the vendor driver sources. The ++ * vendor driver keeps two variants of this table and selects between them ++ * based on the chip option register; these are the values for a non-zero ++ * option, which is what RTL8367S parts seen so far report. See ++ * rtl8365mb_sds_probe_option(). ++ */ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = { ++ { 0x0480, 0x04D7 }, { 0x0481, 0xF994 }, { 0x0482, 0x2420 }, ++ { 0x0483, 0x6960 }, { 0x0484, 0x9728 }, { 0x0423, 0x9D85 }, ++ { 0x0424, 0xD810 }, { 0x002E, 0x83F2 }, ++}; ++ + enum rtl8365mb_phy_interface_mode { + RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, + RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), +@@ -730,6 +816,9 @@ struct rtl8365mb_port { + * @cpu: CPU tagging and CPU port configuration for this chip + * @mib_lock: prevent concurrent reads of MIB counters + * @ports: per-port data ++ * @pcs: PCS for the SerDes external interface ++ * @sds_supported: SerDes tuning parameters match the chip option, so the ++ * SerDes interface modes can be advertised + * + * Private data for this driver. + */ +@@ -740,8 +829,12 @@ struct rtl8365mb { + struct rtl8365mb_cpu cpu; + struct mutex mib_lock; + struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; ++ struct phylink_pcs pcs; ++ bool sds_supported; + }; + ++#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs) ++ + static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) + { + u32 val; +@@ -1042,6 +1135,334 @@ static int rtl8365mb_ext_config_rgmii(st + return 0; + } + ++static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data) ++{ ++ int ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); ++ if (ret) ++ return ret; ++ ++ /* The SerDes indirect access engine completes the command within the ++ * register write transaction, so there is no need to wait or poll for ++ * completion before the next access, matching the vendor driver. ++ */ ++ return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, ++ RTL8365MB_SDS_INDACS_CMD_RUN_MASK | ++ RTL8365MB_SDS_INDACS_CMD_WR_MASK); ++} ++ ++static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data) ++{ ++ u32 val; ++ int ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, ++ RTL8365MB_SDS_INDACS_CMD_RUN_MASK); ++ if (ret) ++ return ret; ++ ++ /* Wait for the indirect read to complete: the engine clears the BUSY ++ * bit once the data register holds the result. ++ */ ++ ret = regmap_read_poll_timeout(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, ++ val, ++ !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK), ++ 10, 1000); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val); ++ if (ret) ++ return ret; ++ ++ *data = val; ++ ++ return 0; ++} ++ ++/* The vendor driver selects between two sets of SerDes tuning parameters based ++ * on the chip option register. Only the variant for a non-zero option has been ++ * tested on real hardware - the RTL8367S parts seen so far all report 1. The ++ * variant for option 0 uses different tuning values that cannot be verified, ++ * so probe the option once at setup and only advertise the SerDes interface ++ * modes when the tuning parameters are known to match, so that an unsupported ++ * variant fails at phylink validation time rather than when configuring the ++ * link. ++ */ ++static int rtl8365mb_sds_probe_option(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ const struct rtl8365mb_extint *extint; ++ u32 option; ++ int ret; ++ int i; ++ ++ /* Nothing to probe if no external interface is wired to the SerDes */ ++ for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { ++ extint = &mb->chip_info->extints[i]; ++ ++ if (extint->supported_interfaces & ++ (RTL8365MB_PHY_INTERFACE_MODE_SGMII | ++ RTL8365MB_PHY_INTERFACE_MODE_HSGMII)) ++ break; ++ } ++ if (i == RTL8365MB_MAX_NUM_EXTINTS) ++ return 0; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, ++ RTL8365MB_SDS_OPTION_ARM_KEY); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(priv->map, RTL8365MB_SDS_OPTION_REG, &option); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, 0); ++ if (ret) ++ return ret; ++ ++ if (option == 0) { ++ dev_warn(priv->dev, ++ "unsupported SerDes tuning variant (chip option 0), disabling SerDes interface modes\n"); ++ return 0; ++ } ++ ++ mb->sds_supported = true; ++ ++ return 0; ++} ++ ++static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, ++ phy_interface_t interface, ++ const unsigned long *advertising, ++ bool permit_pause_to_mac) ++{ ++ const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; ++ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); ++ struct realtek_priv *priv; ++ u16 val; ++ int ret; ++ int i; ++ ++ priv = mb->priv; ++ ++ /* Hold the embedded DW8051 microcontroller in reset and keep it ++ * disabled. The vendor driver loads firmware into it to manage the ++ * SerDes link, but the firmware only duplicates work that phylink ++ * already does: it polls the port status and forces the external ++ * interface configuration in the very registers this driver manages. ++ * Letting it run would race with phylink. ++ */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_CHIP_RESET_REG, ++ RTL8365MB_CHIP_RESET_DW8051_MASK, ++ RTL8365MB_CHIP_RESET_DW8051_MASK); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(priv->map, RTL8365MB_MISC_CFG0_REG, ++ RTL8365MB_MISC_CFG0_DW8051_EN_MASK, 0); ++ if (ret) ++ return ret; ++ ++ /* The vendor driver clears the line rate bypass for all interface ++ * modes except TMII. ++ */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_BYPASS_LINE_RATE_REG, ++ RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, 0); ++ if (ret) ++ return ret; ++ ++ /* Tune the SerDes with vendor-prescribed parameters */ ++ for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) { ++ ret = rtl8365mb_sds_write(priv, ++ rtl8365mb_sds_jam_sgmii[i].reg, ++ rtl8365mb_sds_jam_sgmii[i].val); ++ if (ret) ++ return ret; ++ } ++ ++ /* Mux the SerDes to MAC8 in SGMII mode */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, ++ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | ++ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, ++ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK); ++ if (ret) ++ return ret; ++ ++ val = RTL8365MB_EXT_PORT_MODE_SGMII ++ << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), ++ RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), ++ val); ++ if (ret) ++ return ret; ++ ++ /* Take the SerDes out of reset. The vendor driver does this only ++ * after the SerDes mux and the interface mode are configured. ++ */ ++ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET, ++ RTL8365MB_SDS_RESET_DEASSERT); ++ if (ret) ++ return ret; ++ ++ /* Reset the SerDes data path and resync its PLL, mirroring what the ++ * vendor firmware does right after deasserting the SerDes reset. ++ * This flushes the FIFOs and ensures a clean state for the link, ++ * preventing silent drops and CRC errors. ++ */ ++ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, ++ RTL8365MB_SDS_BMCR_DPRST_PHASE1); ++ if (ret) ++ return ret; ++ ++ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, ++ RTL8365MB_SDS_BMCR_DPRST_PHASE2); ++ if (ret) ++ return ret; ++ ++ /* Keep SGMII in-band autonegotiation disabled: the link parameters are ++ * forced from rtl8365mb_pcs_link_up() instead. ++ */ ++ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val); ++ if (ret) ++ return ret; ++ ++ val &= ~RTL8365MB_SDS_NWAY_EN_MASK; ++ val |= RTL8365MB_SDS_NWAY_RESTART_MASK; ++ ++ return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val); ++} ++ ++static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) ++{ ++ return interface == PHY_INTERFACE_MODE_SGMII; ++} ++ ++static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs, ++ phy_interface_t interface) ++{ ++ /* In-band autonegotiation is not implemented; the link is always ++ * forced. Report that to phylink so that it never selects an ++ * in-band-enabled negotiation mode for this PCS. ++ */ ++ return LINK_INBAND_DISABLE; ++} ++ ++static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs, ++ unsigned int neg_mode, ++ struct phylink_link_state *state) ++{ ++ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); ++ struct realtek_priv *priv = mb->priv; ++ u16 status; ++ u32 val; ++ int ret; ++ ++ /* In-band autonegotiation is not implemented, so the link parameters are ++ * forced from rtl8365mb_pcs_link_up(). The real link state must still be ++ * read from the SerDes itself: the embedded DW8051 microcontroller that ++ * the vendor firmware uses to poll the SerDes is kept disabled (see ++ * rtl8365mb_pcs_config()), so the link status register can be read ++ * directly through the SDS_INDACS window without racing the auto-poll. ++ */ ++ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status); ++ if (ret) { ++ state->link = false; ++ return; ++ } ++ ++ state->link = !!(status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK); ++ state->an_complete = state->link; ++ if (!state->link) ++ return; ++ ++ /* The speed and duplex are forced; read them back from the values ++ * programmed into the SerDes MISC register. ++ */ ++ ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val); ++ if (ret) { ++ state->link = false; ++ return; ++ } ++ ++ state->duplex = (val & RTL8365MB_SDS_MISC_SGMII_FDUP_MASK) ? ++ DUPLEX_FULL : DUPLEX_HALF; ++ ++ switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) { ++ case RTL8365MB_PORT_SPEED_1000M: ++ state->speed = SPEED_1000; ++ break; ++ case RTL8365MB_PORT_SPEED_100M: ++ state->speed = SPEED_100; ++ break; ++ case RTL8365MB_PORT_SPEED_10M: ++ state->speed = SPEED_10; ++ break; ++ } ++} ++ ++static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs, ++ unsigned int neg_mode, ++ phy_interface_t interface, int speed, ++ int duplex) ++{ ++ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); ++ struct realtek_priv *priv = mb->priv; ++ u32 mask = RTL8365MB_SDS_MISC_SGMII_FDUP_MASK | ++ RTL8365MB_SDS_MISC_SGMII_LINK_MASK | ++ RTL8365MB_SDS_MISC_SGMII_SPD_MASK; ++ u32 val = RTL8365MB_SDS_MISC_SGMII_LINK_MASK; ++ u32 r_speed; ++ int ret; ++ ++ if (speed == SPEED_1000) { ++ r_speed = RTL8365MB_PORT_SPEED_1000M; ++ } else if (speed == SPEED_100) { ++ r_speed = RTL8365MB_PORT_SPEED_100M; ++ } else if (speed == SPEED_10) { ++ r_speed = RTL8365MB_PORT_SPEED_10M; ++ } else { ++ dev_err(priv->dev, "unsupported SerDes speed %s\n", ++ phy_speed_to_str(speed)); ++ return; ++ } ++ ++ val |= FIELD_PREP(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, r_speed); ++ ++ if (duplex == DUPLEX_FULL) ++ val |= RTL8365MB_SDS_MISC_SGMII_FDUP_MASK; ++ ++ /* pcs_link_up() carries no pause information, so the SerDes flow ++ * control bits are programmed together with the MAC external interface ++ * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause ++ * modes are known. ++ */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val); ++ if (ret) { ++ dev_err(priv->dev, "failed to force SerDes link: %pe\n", ++ ERR_PTR(ret)); ++ return; ++ } ++} ++ ++static const struct phylink_pcs_ops rtl8365mb_pcs_ops = { ++ .pcs_inband_caps = rtl8365mb_pcs_inband_caps, ++ .pcs_config = rtl8365mb_pcs_config, ++ .pcs_get_state = rtl8365mb_pcs_get_state, ++ .pcs_link_up = rtl8365mb_pcs_link_up, ++}; ++ + static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, + bool link, int speed, int duplex, + bool tx_pause, bool rx_pause) +@@ -1118,6 +1539,8 @@ static void rtl8365mb_phylink_get_caps(s + { + const struct rtl8365mb_extint *extint = + rtl8365mb_get_port_extint(ds->priv, port); ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb *mb = priv->chip_data; + + config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | + MAC_10 | MAC_100 | MAC_1000FD; +@@ -1141,6 +1564,25 @@ static void rtl8365mb_phylink_get_caps(s + + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) + phy_interface_set_rgmii(config->supported_interfaces); ++ ++ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII && ++ mb->sds_supported) ++ __set_bit(PHY_INTERFACE_MODE_SGMII, ++ config->supported_interfaces); ++} ++ ++static struct phylink_pcs * ++rtl8365mb_phylink_mac_select_pcs(struct phylink_config *config, ++ phy_interface_t interface) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ struct rtl8365mb *mb = priv->chip_data; ++ ++ if (rtl8365mb_interface_is_serdes(interface)) ++ return &mb->pcs; ++ ++ return NULL; + } + + static void rtl8365mb_phylink_mac_config(struct phylink_config *config, +@@ -1168,6 +1610,12 @@ static void rtl8365mb_phylink_mac_config + return; + } + ++ /* SGMII is handled by the SerDes PCS, configured through the ++ * phylink_pcs ops, so there is nothing to do here for it. ++ */ ++ if (rtl8365mb_interface_is_serdes(state->interface)) ++ return; ++ + /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also + * supports + */ +@@ -1188,7 +1636,13 @@ static void rtl8365mb_phylink_mac_link_d + p = &mb->ports[port]; + cancel_delayed_work_sync(&p->mib_work); + +- if (phy_interface_mode_is_rgmii(interface)) { ++ /* phylink has no pcs_link_down callback, so on the SerDes path only the ++ * MAC external interface force is reset here. Clearing the MAC force is ++ * enough to bring the link down; the SerDes keeps presenting its last ++ * forced state until the next pcs_link_up() reprograms it. ++ */ ++ if (phy_interface_mode_is_rgmii(interface) || ++ rtl8365mb_interface_is_serdes(interface)) { + ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, + false, false); + if (ret) +@@ -1218,14 +1672,51 @@ static void rtl8365mb_phylink_mac_link_u + p = &mb->ports[port]; + schedule_delayed_work(&p->mib_work, 0); + +- if (phy_interface_mode_is_rgmii(interface)) { ++ /* The SerDes forced link state is programmed by the PCS in ++ * rtl8365mb_pcs_link_up(); here only the MAC external interface force ++ * is configured, for both RGMII and SerDes. ++ */ ++ if (phy_interface_mode_is_rgmii(interface) || ++ rtl8365mb_interface_is_serdes(interface)) { + ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, + duplex, tx_pause, + rx_pause); +- if (ret) ++ if (ret) { + dev_err(priv->dev, + "failed to force mode on port %d: %pe\n", port, + ERR_PTR(ret)); ++ return; ++ } ++ ++ /* The SerDes has its own pause enables; program them from ++ * the resolved pause modes, as the vendor driver does when ++ * forcing the link on a SerDes external interface. These ++ * bits, not the MAC force pause bits, gate pause on the ++ * SerDes external interface: flow control testing shows ++ * that pause frames are only emitted with the SerDes TXFC ++ * bit set, while the MAC force pause bits alone have no ++ * effect on this port. This is done here rather than in ++ * rtl8365mb_pcs_link_up() because pcs_link_up() carries no ++ * pause information. ++ */ ++ if (rtl8365mb_interface_is_serdes(interface)) { ++ u32 val = 0; ++ ++ if (tx_pause) ++ val |= RTL8365MB_SDS_MISC_SGMII_TXFC_MASK; ++ if (rx_pause) ++ val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK; ++ ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_SDS_MISC_REG, ++ RTL8365MB_SDS_MISC_SGMII_TXFC_MASK | ++ RTL8365MB_SDS_MISC_SGMII_RXFC_MASK, ++ val); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to force SerDes pause modes on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ } + + return; + } +@@ -2419,6 +2910,14 @@ static int rtl8365mb_setup(struct dsa_sw + mb = priv->chip_data; + cpu = &mb->cpu; + ++ mb->pcs.ops = &rtl8365mb_pcs_ops; ++ ++ /* The SerDes has no link interrupt wired up, so phylink must poll the ++ * PCS for link changes when it tracks the link through pcs_get_state() ++ * (in-band mode with autonegotiation disabled). ++ */ ++ mb->pcs.poll = true; ++ + ret = rtl8365mb_reset_chip(priv); + if (ret) { + dev_err(priv->dev, "failed to reset chip: %pe\n", +@@ -2426,6 +2925,13 @@ static int rtl8365mb_setup(struct dsa_sw + goto out_error; + } + ++ ret = rtl8365mb_sds_probe_option(priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to probe SerDes chip option: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ + /* Configure switch to vendor-defined initial state */ + ret = rtl8365mb_switch_init(priv); + if (ret) { +@@ -2658,6 +3164,7 @@ static int rtl8365mb_detect(struct realt + } + + static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { ++ .mac_select_pcs = rtl8365mb_phylink_mac_select_pcs, + .mac_config = rtl8365mb_phylink_mac_config, + .mac_link_down = rtl8365mb_phylink_mac_link_down, + .mac_link_up = rtl8365mb_phylink_mac_link_up, diff --git a/target/linux/generic/backport-6.18/943-02-v7.3-net-dsa-realtek-rtl8365mb-add-HSGMII-support.patch b/target/linux/generic/backport-6.18/943-02-v7.3-net-dsa-realtek-rtl8365mb-add-HSGMII-support.patch new file mode 100644 index 00000000000..557feef0ccf --- /dev/null +++ b/target/linux/generic/backport-6.18/943-02-v7.3-net-dsa-realtek-rtl8365mb-add-HSGMII-support.patch @@ -0,0 +1,305 @@ +From 987137345f3312fd68cc9c11bd46b754bfb0046f Mon Sep 17 00:00:00 2001 +From: Johan Alvarado +Date: Sat, 11 Jul 2026 23:31:59 -0500 +Subject: net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +In addition to SGMII, the RTL8367S SerDes also supports HSGMII, which +carries 2.5 Gbps with the same signaling as SGMII at 2.5x clock rate. +The chip info table already declares HSGMII as a supported interface +mode for external interface 1. + +Extend the SerDes PCS to handle HSGMII, which phylink represents as +2500base-x: + + - Select the HSGMII SerDes tuning parameters and external interface + mode, and mux the SerDes to MAC8 in HSGMII mode, from pcs_config() + according to the interface. The parameters are again lifted from the + GPL-licensed Realtek rtl8367c vendor driver, and again only cover + the tuning variant for a non-zero chip option, so the mode is gated + on the option probed at setup. + + - Advertise 2500base-x and MAC_2500FD on ports whose external + interface supports HSGMII. + + - Accept SPEED_2500 in the forced link configuration. The MAC speed + field has no 2.5 Gbps value: the rate is determined by the HSGMII + SerDes configuration, and the vendor driver programs the 1 Gbps + value here, so do the same. + + - Raise the port 6 ingress and egress rate limiters to their maximum + at setup time, as the vendor switch init does unconditionally for + the whole chip family. The chip resets them to 0x1FFFF (~1.048 Gbps + in units of 8 Kbps), which caps the aggregate HSGMII throughput at + roughly 1 Gbps. The vendor documentation describes the reset + default as disabling the limiter, but the cap is real: on an + RTL8367S-based Mercusys MR85X running an OpenWrt backport of this + series, several clients on 1 Gbps user ports were limited to about + 1.02 Gbps combined across the HSGMII CPU port until these limiters + were raised, after which throughput reached about 2 Gbps [1]. The + related HSGMII scheduler line rate (LINE_RATE_HSG_H) is already set + to its maximum by the common init jam table. + +Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to +the SoC over HSGMII. + +Link: https://github.com/openwrt/openwrt/pull/19445#issuecomment-4505613294 [1] +Suggested-by: Luiz Angelo Daros de Luca +Suggested-by: Mieczyslaw Nalewaj +Signed-off-by: Johan Alvarado +Tested-by: Stanisław Pal +Reviewed-by: Luiz Angelo Daros de Luca +Reviewed-by: Mieczyslaw Nalewaj +Tested-by: Stanislaw Pal +Link: https://patch.msgid.link/20260711-rtl8367s-sgmii-v6-2-88f7944ddca7@c127.dev +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb_main.c | 134 +++++++++++++++++++++++++++---- + 1 file changed, 118 insertions(+), 16 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -40,8 +40,8 @@ + * driver has only been tested with a fixed-link, but in principle it should not + * matter. + * +- * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this +- * driver. ++ * NOTE: Currently, only the RGMII, SGMII and HSGMII interfaces are implemented ++ * in this driver. + * + * The interrupt line is asserted on link UP/DOWN events. The driver creates a + * custom irqchip to handle this interrupt and demultiplex the events by reading +@@ -251,6 +251,18 @@ + #define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 + #define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) + ++/* Port 6 ingress and egress rate limiter registers. Each limit is a 19-bit ++ * value in units of 8 Kbps, split across a 16-bit LSB register (CTRL0) and a ++ * 3-bit MSB field (CTRL1). The chip resets them to 0x1FFFF; see ++ * rtl8365mb_sds_raise_rate_limits(). ++ */ ++#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG 0x00CF ++#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG 0x00D0 ++#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK 0x0007 ++#define RTL8365MB_PORT6_EGRESSBW_CTRL0_REG 0x0398 ++#define RTL8365MB_PORT6_EGRESSBW_CTRL1_REG 0x0399 ++#define RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK 0x0007 ++ + /* SerDes indirect access registers */ + #define RTL8365MB_SDS_INDACS_CMD_REG 0x6600 + #define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100 +@@ -637,6 +649,18 @@ static const struct rtl8365mb_jam_tbl_en + { 0x0424, 0xD810 }, { 0x002E, 0x83F2 }, + }; + ++/* HSGMII SerDes tuning parameters, lifted from the vendor driver sources. As ++ * with the SGMII table, the vendor driver keeps several variants and selects ++ * one based on the chip option register; these are the values for a non-zero ++ * option, which is what RTL8367S parts seen so far report. See ++ * rtl8365mb_sds_probe_option(). ++ */ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = { ++ { 0x0500, 0x82F0 }, { 0x0501, 0xF195 }, { 0x0502, 0x31A2 }, ++ { 0x0503, 0x7960 }, { 0x0504, 0x9728 }, { 0x0423, 0x9D85 }, ++ { 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 }, ++}; ++ + enum rtl8365mb_phy_interface_mode { + RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, + RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), +@@ -1242,20 +1266,70 @@ static int rtl8365mb_sds_probe_option(st + return 0; + } + ++/* The vendor driver raises the port 6 ingress and egress rate limiters to ++ * their maximum in its switch init, unconditionally for the whole chip ++ * family. The chip reset in rtl8365mb_setup() puts them back to their reset ++ * default of 0x1FFFF, a ~1.048 Gbps limit which caps the aggregate ++ * throughput of an HSGMII CPU port at roughly 1 Gbps. The vendor ++ * documentation describes the reset default as disabling the limiter, but ++ * the cap has been observed on hardware. Raise them likewise, to 0x7FFFF ++ * (~4.19 Gbps, above the HSGMII line rate). The related HSGMII scheduler ++ * line rate register (LINE_RATE_HSG_H, 0x03FA) is already set to its ++ * maximum by the common init jam table. ++ */ ++static int rtl8365mb_sds_raise_rate_limits(struct realtek_priv *priv) ++{ ++ int ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG, ++ 0xFFFF); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG, ++ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK, ++ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK); ++ if (ret) ++ return ret; ++ ++ ret = regmap_write(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL0_REG, ++ 0xFFFF); ++ if (ret) ++ return ret; ++ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL1_REG, ++ RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK, ++ RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK); ++} ++ + static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) + { ++ const struct rtl8365mb_jam_tbl_entry *sds_jam; + const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv; ++ size_t sds_jam_size; ++ u32 mode; + u16 val; + int ret; + int i; + + priv = mb->priv; + ++ if (interface == PHY_INTERFACE_MODE_2500BASEX) { ++ sds_jam = rtl8365mb_sds_jam_hsgmii; ++ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii); ++ mode = RTL8365MB_EXT_PORT_MODE_HSGMII; ++ } else { ++ sds_jam = rtl8365mb_sds_jam_sgmii; ++ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); ++ mode = RTL8365MB_EXT_PORT_MODE_SGMII; ++ } ++ + /* Hold the embedded DW8051 microcontroller in reset and keep it + * disabled. The vendor driver loads firmware into it to manage the + * SerDes link, but the firmware only duplicates work that phylink +@@ -1283,24 +1357,24 @@ static int rtl8365mb_pcs_config(struct p + return ret; + + /* Tune the SerDes with vendor-prescribed parameters */ +- for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) { +- ret = rtl8365mb_sds_write(priv, +- rtl8365mb_sds_jam_sgmii[i].reg, +- rtl8365mb_sds_jam_sgmii[i].val); ++ for (i = 0; i < sds_jam_size; i++) { ++ ret = rtl8365mb_sds_write(priv, sds_jam[i].reg, ++ sds_jam[i].val); + if (ret) + return ret; + } + +- /* Mux the SerDes to MAC8 in SGMII mode */ ++ /* Mux the SerDes to MAC8 in the requested mode */ + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, +- RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK); ++ mode == RTL8365MB_EXT_PORT_MODE_SGMII ? ++ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK : ++ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK); + if (ret) + return ret; + +- val = RTL8365MB_EXT_PORT_MODE_SGMII +- << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); ++ val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); + ret = regmap_update_bits(priv->map, + RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), + RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), +@@ -1346,7 +1420,8 @@ static int rtl8365mb_pcs_config(struct p + + static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) + { +- return interface == PHY_INTERFACE_MODE_SGMII; ++ return interface == PHY_INTERFACE_MODE_SGMII || ++ interface == PHY_INTERFACE_MODE_2500BASEX; + } + + static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs, +@@ -1401,7 +1476,9 @@ static void rtl8365mb_pcs_get_state(stru + + switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) { + case RTL8365MB_PORT_SPEED_1000M: +- state->speed = SPEED_1000; ++ state->speed = ++ state->interface == PHY_INTERFACE_MODE_2500BASEX ? ++ SPEED_2500 : SPEED_1000; + break; + case RTL8365MB_PORT_SPEED_100M: + state->speed = SPEED_100; +@@ -1426,7 +1503,11 @@ static void rtl8365mb_pcs_link_up(struct + u32 r_speed; + int ret; + +- if (speed == SPEED_1000) { ++ /* The speed field has no value for 2.5 Gbps: the rate is determined by ++ * the HSGMII SerDes configuration, and the vendor driver programs the ++ * 1 Gbps value here. ++ */ ++ if (speed == SPEED_2500 || speed == SPEED_1000) { + r_speed = RTL8365MB_PORT_SPEED_1000M; + } else if (speed == SPEED_100) { + r_speed = RTL8365MB_PORT_SPEED_100M; +@@ -1486,7 +1567,11 @@ static int rtl8365mb_ext_config_forcemod + r_rx_pause = rx_pause ? 1 : 0; + r_tx_pause = tx_pause ? 1 : 0; + +- if (speed == SPEED_1000) { ++ /* The speed field has no value for 2.5 Gbps: the rate is ++ * determined by the HSGMII SerDes configuration, and the ++ * vendor driver programs the 1 Gbps value here. ++ */ ++ if (speed == SPEED_2500 || speed == SPEED_1000) { + r_speed = RTL8365MB_PORT_SPEED_1000M; + } else if (speed == SPEED_100) { + r_speed = RTL8365MB_PORT_SPEED_100M; +@@ -1569,6 +1654,13 @@ static void rtl8365mb_phylink_get_caps(s + mb->sds_supported) + __set_bit(PHY_INTERFACE_MODE_SGMII, + config->supported_interfaces); ++ ++ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII && ++ mb->sds_supported) { ++ __set_bit(PHY_INTERFACE_MODE_2500BASEX, ++ config->supported_interfaces); ++ config->mac_capabilities |= MAC_2500FD; ++ } + } + + static struct phylink_pcs * +@@ -1610,8 +1702,8 @@ static void rtl8365mb_phylink_mac_config + return; + } + +- /* SGMII is handled by the SerDes PCS, configured through the +- * phylink_pcs ops, so there is nothing to do here for it. ++ /* SGMII and 2500base-x are handled by the SerDes PCS, configured ++ * through the phylink_pcs ops, so nothing to do here for them. + */ + if (rtl8365mb_interface_is_serdes(state->interface)) + return; +@@ -2940,6 +3032,16 @@ static int rtl8365mb_setup(struct dsa_sw + goto out_error; + } + ++ if (mb->sds_supported) { ++ ret = rtl8365mb_sds_raise_rate_limits(priv); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to raise port rate limits: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ } ++ + /* Set up cascading IRQs */ + ret = rtl8365mb_irq_setup(priv); + if (ret == -EPROBE_DEFER)