* [PATCH net-next v6 0/2] net: dsa: realtek: rtl8365mb: add SGMII/HSGMII support for RTL8367S
@ 2026-07-12 4:31 Johan Alvarado via B4 Relay
2026-07-12 4:31 ` [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII " Johan Alvarado via B4 Relay
2026-07-12 4:31 ` [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII " Johan Alvarado via B4 Relay
0 siblings, 2 replies; 6+ messages in thread
From: Johan Alvarado via B4 Relay @ 2026-07-12 4:31 UTC (permalink / raw)
To: Linus Walleij, Alvin Šipraga, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King
Cc: Mieczyslaw Nalewaj, Maxime Chevallier, Luiz Angelo Daros de Luca,
netdev, linux-kernel, Johan Alvarado
The RTL8367S is a 5+2 port switch from the same family as the
RTL8365MB-VC already supported by this driver. Its chip info table
entry declares SGMII and HSGMII on external interface 1, but the
driver so far only implements RGMII, leaving boards that wire the
switch to the CPU over the SerDes without a working CPU port.
This series implements both modes. The configuration sequence and the
SerDes tuning parameters are derived from the GPL-licensed Realtek
rtl8367c vendor driver, as distributed in the Mercusys MR80X GPL code
drop, and cross-checked against the real register sequence captured at
runtime by chainloading a custom U-Boot ahead of the stock firmware
and logging the live SerDes accesses on hardware.
The vendor driver brings up the SerDes by loading firmware into the
switch's embedded DW8051 microcontroller. Analysis of that firmware
(by Luiz Angelo Daros de Luca) showed it only performs a SerDes
data-path reset right after the SerDes reset is deasserted, and then
runs a link-polling loop that writes the external interface force
registers -- duplicating, and racing with, the link management phylink
already performs. This series therefore keeps the DW8051 disabled and
performs the one necessary action (the data-path reset via the SerDes
BMCR register) directly in the driver, avoiding both the race and a
dependency on a redistributable firmware blob.
The SerDes is modelled as a phylink PCS: mac_select_pcs() hands the
SerDes interfaces to a phylink_pcs whose pcs_config()/pcs_link_up()
ops own the SerDes register sequence, keeping it out of the MAC
operations. In-band autonegotiation is not implemented; the link is
forced (fixed-link or conventional PHY), as for RGMII, and the PCS
reports this to phylink through pcs_inband_caps().
Patch 1 adds the SerDes indirect access helpers, the PCS and SGMII
(1 Gbps) support. Patch 2 extends the PCS to HSGMII (2.5 Gbps), which
phylink represents as 2500base-x.
Tested on a Mercusys MR80X v2.20 (RTL8367S wired to the SoC over the
SerDes), in both SGMII and HSGMII modes with a fixed-link device tree
description: link bring-up verified across cold boots, warm reboots,
module reloads and link down/up cycles, with sustained traffic and no
CRC/symbol errors. The SerDes pause enables were verified by driving
congestion toward a 100M user port and observing pause frame emission
on the CPU port (dot3OutPauseFrames) toggle with the SDS_MISC
TXFC/RXFC bits. The port 6 rate limiters were verified to be live by
lowering them to 100 Mbps at runtime and observing iperf3 throughput
across the CPU port clamp accordingly in each direction, recovering
once the maximum was restored. The HSGMII link is confirmed running
at 2.5G at the register level (SoC uniphy mode and gmac clocks);
per-direction throughput could not be pushed past ~1 Gbps on this
board because the SoC side is driven by the IPQ5018 SSDK and the
user-facing PHY is 1G, so full 2.5G line-rate throughput remains
unverified on my hardware. Independently, >1 Gbps aggregate HSGMII
throughput (~2 Gbps with multiple clients) has been observed on an
RTL8367S-based Mercusys MR85X running an OpenWrt backport of this
series with the rate limiters raised (see patch 2).
The RTL8367SB also declares SGMII and HSGMII in its chip info entry
and therefore gains both modes as well. The vendor driver drives the
two chips through the same code path, keyed only on the chip option
register (both report chip id 0x6367), so this is expected to work
there too, but I have no RTL8367SB hardware to confirm it.
Changes across versions:
v6:
- No code changes; resend of v5. The SMTP relay used for v5 rewrote
the Message-IDs and dropped the References headers, so the patches
did not thread under the cover letter on lore and could not be
processed as a series. Reported by Mieczyslaw Nalewaj. This
version is sent through the kernel.org b4 web endpoint instead.
v5: https://lore.kernel.org/netdev/0100019f488ec83f-cd82d418-999a-40de-b58b-135b4b2aee51-000000@email.amazonses.com/
- Raise the port 6 ingress and egress rate limiters to their maximum
at setup time, as the vendor switch init does. 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 cap was
confirmed on an RTL8367S-based Mercusys MR85X, where raising the
limiters took multi-client throughput across the HSGMII CPU port
from ~1.02 Gbps combined to ~2 Gbps (link in patch 2). The related
HSGMII scheduler line rate (LINE_RATE_HSG_H) is already set to its
maximum by the common init jam table. Pointed out by Mieczyslaw
Nalewaj.
- Drop the PHYLINK_PCS_NEG_INBAND_ENABLED check from pcs_config().
With pcs_inband_caps() returning LINK_INBAND_DISABLE, phylink can
never select an in-band-enabled negotiation mode for this PCS, so
the check was unreachable. Pointed out by Maxime Chevallier.
- Document the SerDes SDS_MISC pause bits as the functional pause
enables of the SerDes external interface, after testing flow
control on hardware: pause frames are only emitted with the SerDes
TXFC bit set, while the MAC force pause bits alone have no effect
on this port. Comment-only change, prompted by a question from
Maxime Chevallier.
v4: https://lore.kernel.org/netdev/20260702204648.276112-1-contact@c127.dev/
- Drop the chip model name from the driver's NOTE comment; which
interfaces a given chip exposes is described by its chip_info entry,
not the file header. Pointed out by Luiz Angelo Daros de Luca.
- Build the SerDes BMCR data-path-reset values from the standard
BMCR_ANENABLE | BMCR_ISOLATE bits instead of a bare magic number, so
the meaning is in the code rather than only in a comment. Pointed
out by Luiz Angelo Daros de Luca.
- Use a temporary for the DIGITAL_INTERFACE_SELECT value instead of
wrapping the expression across the regmap_update_bits() arguments.
Pointed out by Luiz Angelo Daros de Luca.
- Reject the untested SerDes tuning variant. The vendor driver keeps
two sets of SerDes tuning parameters and selects between them based
on the chip option register (0x13C1); the tables in this series are
the variant for a non-zero option, which is what the RTL8367S parts
seen so far report. The option is probed once at setup and the
SerDes interface modes are only advertised to phylink when the
tuning parameters match, so an unsupported variant fails at phylink
validation time instead of when configuring the link. Thanks to
Luiz Angelo Daros de Luca for pointing out the conditional.
- Express the external interface line rate bypass bit through a
parametric macro keyed on the port number (with port 5 as the base),
instead of an open-coded BIT(interface id) that only matched by
coincidence; other RTL8367 families index this register differently.
Suggested by Luiz Angelo Daros de Luca.
- Drop the arbitrary usleep_range() after each SerDes indirect access.
SerDes writes are now fire-and-forget and reads poll the self-clearing
BUSY bit with regmap_read_poll_timeout(), matching the vendor driver,
which never sleeps. On the MR80X the BUSY bit is never even observed
set: the access completes within the register transaction. Pointed out
by Luiz Angelo Daros de Luca; poll approach suggested by Mieczyslaw
Nalewaj.
- Drop the always-zero SerDes index argument from the SerDes indirect
access helpers, along with the INDACS command index field whose
width was questioned during review; this chip has a single SerDes
block reachable through this window, so the index served no purpose.
Raised by Luiz Angelo Daros de Luca.
- Stop hardcoding external interface 1 with an early -EOPNOTSUPP in
the SerDes configuration path. The SerDes interface modes are now
advertised in phylink_get_caps() from the chip_info
supported_interfaces, and mac_select_pcs() returns the PCS only for
those modes. Pointed out by Luiz Angelo Daros de Luca.
- Keep the new register definitions as raw hex masks, matching the
prevailing style of the file. A file-wide GENMASK/BIT conversion,
raised by Luiz Angelo Daros de Luca during review, is left for a
separate cleanup patch so this series stays focused on the feature.
- Convert the SerDes path to a phylink_pcs, as suggested by Maxime
Chevallier. The SGMII/HSGMII SerDes handling now lives in
pcs_config()/pcs_get_state()/pcs_link_up() selected via
mac_select_pcs(), instead of being driven from the MAC
mac_config()/mac_link_up()/mac_link_down() operations. This
separates the MAC and SerDes layers and makes future in-band
autonegotiation an additive change. No functional change intended
for the forced-link path; retested on the MR80X v2.20. In-band
autonegotiation remains unimplemented and is left for a follow-up,
once hardware is available to validate it.
- Implement pcs_inband_caps(), returning LINK_INBAND_DISABLE so that
phylink knows this PCS cannot do in-band autonegotiation and never
selects an in-band-enabled negotiation mode for it. pcs_config()
rejects PHYLINK_PCS_NEG_INBAND_ENABLED with -EOPNOTSUPP instead of
the previous warn-and-force.
- Program the SerDes pause controls in SDS_MISC from the resolved
pause modes when forcing the MAC external interface, as the vendor
driver does, instead of leaving whatever state the boot firmware
left there. Done in mac_link_up() because pcs_link_up() carries no
pause information.
- Set the PCS poll flag: the SerDes has no link interrupt wired up,
so phylink must poll pcs_get_state() when it tracks the link
through the PCS (in-band mode with autonegotiation disabled).
- Report link down from pcs_get_state() if reading back the forced
speed/duplex fails, rather than reporting link up with a stale
state.
- Reword the misleading "disable in-band aneg" comment.
v3: https://lore.kernel.org/netdev/20260613232136.24246-1-contact@c127.dev/
- Drop the DW8051 firmware loading entirely. Analysis of the vendor
firmware showed it only duplicates the link management phylink
already does; the one needed action (SerDes data-path reset via
the BMCR register) is now performed directly in the driver, with
the DW8051 kept disabled. This removes the dependency on the
rtl8367s-sgmii.bin firmware blob, which could not be redistributed
via linux-firmware (the GPL vendor source ships it as a byte array
without the corresponding microcode source). Thanks to Luiz Angelo
Daros de Luca for the firmware analysis.
v2: https://lore.kernel.org/netdev/0100019eb0b1822e-ffc5626c-1b9f-4c8a-8a1a-759a9e665f4f-000000@email.amazonses.com/
- No code changes; resend because the SMTP provider used for v1
corrupted the mails and patch 1/2 never reached the list.
v1: https://lore.kernel.org/netdev/aebccaad-eca3-4ea4-99dd-ae7edbc8981b@smtp-relay.sendinblue.com/
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
Johan Alvarado (2):
net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
drivers/net/dsa/realtek/rtl8365mb_main.c | 619 ++++++++++++++++++++++++++++++-
1 file changed, 614 insertions(+), 5 deletions(-)
---
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
change-id: 20260711-rtl8367s-sgmii-59bf1b8eac55
Best regards,
--
Johan Alvarado <contact@c127.dev>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
2026-07-12 4:31 [PATCH net-next v6 0/2] net: dsa: realtek: rtl8365mb: add SGMII/HSGMII support for RTL8367S Johan Alvarado via B4 Relay
@ 2026-07-12 4:31 ` Johan Alvarado via B4 Relay
2026-07-12 8:23 ` Maxime Chevallier
2026-07-12 4:31 ` [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII " Johan Alvarado via B4 Relay
1 sibling, 1 reply; 6+ messages in thread
From: Johan Alvarado via B4 Relay @ 2026-07-12 4:31 UTC (permalink / raw)
To: Linus Walleij, Alvin Šipraga, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King
Cc: Mieczyslaw Nalewaj, Maxime Chevallier, Luiz Angelo Daros de Luca,
netdev, linux-kernel, Johan Alvarado
From: Johan Alvarado <contact@c127.dev>
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 <luizluca@gmail.com>
Suggested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 515 ++++++++++++++++++++++++++++++-
1 file changed, 511 insertions(+), 4 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 5ac091bf93c9..ea03c42d0f1a 100644
--- 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 <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
+#include <linux/mii.h>
#include <linux/mutex.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
+#include <linux/phylink.h>
#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_entry rtl8365mb_init_jam_common[] = {
{ 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(struct realtek_priv *priv, int port,
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(struct dsa_switch *ds, int port,
{
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(struct dsa_switch *ds, int port,
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(struct phylink_config *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_down(struct phylink_config *config,
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_up(struct phylink_config *config,
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_switch *ds)
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_switch *ds)
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 realtek_priv *priv)
}
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,
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
2026-07-12 4:31 [PATCH net-next v6 0/2] net: dsa: realtek: rtl8365mb: add SGMII/HSGMII support for RTL8367S Johan Alvarado via B4 Relay
2026-07-12 4:31 ` [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII " Johan Alvarado via B4 Relay
@ 2026-07-12 4:31 ` Johan Alvarado via B4 Relay
2026-07-12 18:05 ` Mieczyslaw Nalewaj
2026-07-13 8:14 ` Stacho_P
1 sibling, 2 replies; 6+ messages in thread
From: Johan Alvarado via B4 Relay @ 2026-07-12 4:31 UTC (permalink / raw)
To: Linus Walleij, Alvin Šipraga, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King
Cc: Mieczyslaw Nalewaj, Maxime Chevallier, Luiz Angelo Daros de Luca,
netdev, linux-kernel, Johan Alvarado
From: Johan Alvarado <contact@c127.dev>
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 <luizluca@gmail.com>
Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 134 +++++++++++++++++++++++++++----
1 file changed, 118 insertions(+), 16 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index ea03c42d0f1a..d1ba0cc9426f 100644
--- 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_entry rtl8365mb_sds_jam_sgmii[] = {
{ 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(struct realtek_priv *priv)
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 phylink_pcs *pcs, unsigned int neg_mode,
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 phylink_pcs *pcs, unsigned int neg_mode,
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(struct phylink_pcs *pcs,
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 phylink_pcs *pcs,
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_forcemode(struct realtek_priv *priv, int port,
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(struct dsa_switch *ds, int port,
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(struct phylink_config *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_switch *ds)
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)
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
2026-07-12 4:31 ` [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII " Johan Alvarado via B4 Relay
@ 2026-07-12 8:23 ` Maxime Chevallier
0 siblings, 0 replies; 6+ messages in thread
From: Maxime Chevallier @ 2026-07-12 8:23 UTC (permalink / raw)
To: contact, Linus Walleij, Alvin Šipraga, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King
Cc: Mieczyslaw Nalewaj, Luiz Angelo Daros de Luca, netdev,
linux-kernel
Hi Johan,
On 7/12/26 06:31, Johan Alvarado via B4 Relay wrote:
> From: Johan Alvarado <contact@c127.dev>
>
> 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 <luizluca@gmail.com>
> Suggested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
> Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
> Signed-off-by: Johan Alvarado <contact@c127.dev>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Maxime
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
2026-07-12 4:31 ` [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII " Johan Alvarado via B4 Relay
@ 2026-07-12 18:05 ` Mieczyslaw Nalewaj
2026-07-13 8:14 ` Stacho_P
1 sibling, 0 replies; 6+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-07-12 18:05 UTC (permalink / raw)
To: contact, Linus Walleij, Alvin Šipraga, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King
Cc: Maxime Chevallier, Luiz Angelo Daros de Luca, netdev,
linux-kernel
Hi Johan
On 7/12/2026 6:31 AM, Johan Alvarado via B4 Relay wrote:
> From: Johan Alvarado <contact@c127.dev>
>
> 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 <luizluca@gmail.com>
> Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
> Signed-off-by: Johan Alvarado <contact@c127.dev>
Thanks for the patch, this matches what I've seen too on similar
hardware.
As discussed earlier in the thread: the rate limiter helper, as well
as pcs_config()/RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, currently assume
the SerDes is always muxed to external interface 1 / port 6
(RTL8365MB_SDS_EXT_INTERFACE_ID / _PORT). That's true for every chip
currently in rtl8365mb_chip_infos[] (RTL8367S and RTL8367SB both have
their SGMII/HSGMII-capable extint at { 6, 1, ... }), so nothing is
broken today.
I still think it's worth guarding against this ahead of time though.
It isn't an architectural constant of the family, just something that
happens to hold for the two chips currently in the table, and there's
no guarantee a future chip won't mux its SerDes to a different port
or extint id. If that happens, pcs_config() would silently program
the wrong DIGITAL_INTERFACE_SELECT/FORCE register, and the rate
limiter helper would write to the wrong port's registers (or need to
guess an address it can't verify) - the kind of thing that's easy to
miss in review since it only breaks on hardware nobody in this thread
has to test with.
Since rtl8365mb_sds_probe_option() already walks
chip_info->extints[] looking for the SGMII/HSGMII-capable one, I'd
like to have it cache the discovered port and id (mb->sds_port /
mb->sds_id) instead of relying on the fixed
RTL8365MB_SDS_EXT_INTERFACE_ID/_PORT. pcs_config() and the bypass
line-rate mask would then use the cached values, and
rtl8365mb_sds_raise_rate_limits() would check mb->sds_port == 6 and
warn+skip instead of assuming, since only the port 6 register
addresses are known/verified so far.
Draft below, as a preparatory patch to go before this one. No
functional change for RTL8367S/RTL8367SB - just removes the
assumption for whatever comes next. Let me know what you think.
---
#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().
++ * rtl8365mb_sds_raise_rate_limits(). Only port 6 addresses are known - every
++ * chip currently supported muxes the SerDes to port 6 (see mb->sds_port),
++ * but this is not guaranteed for chips that may be added later.
+ */
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG 0x00CF
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG 0x00D0
@@ -30,7 +32,29 @@
/* 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_entry rtl8365mb_sds_jam_sgmii[] = {
+@@ -270,12 +284,11 @@
+ #define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180
+ #define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040
+
+-/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
+- * which is port 6.
++/* The embedded SerDes is muxed to whichever external interface the chip's
++ * extint table marks as SGMII/HSGMII-capable. For every chip currently
++ * supported this is external interface 1 (MAC8) on port 6, but this is
++ * discovered per-chip in rtl8365mb_sds_probe_option() and cached in
++ * mb->sds_port / mb->sds_id rather than assumed, since it need not hold for
++ * chips added in the future.
+ */
+-#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
+@@ -637,6 +650,18 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = {
{ 0x0424, 0xD810 }, { 0x002E, 0x83F2 },
};
@@ -49,25 +73,61 @@
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(struct realtek_priv *priv)
+@@ -871,6 +896,11 @@ struct rtl8365mb {
+ struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS];
+ struct phylink_pcs pcs;
+ bool sds_supported;
++ /* Port and extint id the SerDes is muxed to on this chip, discovered
++ * in rtl8365mb_sds_probe_option() from the chip's extint table.
++ * Valid only if sds_supported is true.
++ */
++ int sds_port;
++ int sds_id;
+ };
+
+ #define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
+@@ -1229,6 +1259,9 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
+ if (i == RTL8365MB_MAX_NUM_EXTINTS)
+ return 0;
+
++ mb->sds_port = extint->port;
++ mb->sds_id = extint->id;
++
+ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG,
+ RTL8365MB_SDS_OPTION_ARM_KEY);
+ if (ret)
+@@ -1242,20 +1275,74 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
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
++/* The vendor driver raises the ingress and egress rate limiters of the
++ * SerDes-connected port 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.
++ *
++ * Only the port 6 register addresses are known. Every chip currently
++ * supported muxes the SerDes to port 6 (see mb->sds_port, discovered in
++ * rtl8365mb_sds_probe_option()); if a future chip ends up muxing it
++ * elsewhere, skip rather than guess at an address.
+ */
+static int rtl8365mb_sds_raise_rate_limits(struct realtek_priv *priv)
+{
++ struct rtl8365mb *mb = priv->chip_data;
+ int ret;
+
++ if (mb->sds_port != 6) {
++ dev_warn(priv->dev,
++ "SerDes on port %d has no known rate limiter registers, skipping\n",
++ mb->sds_port);
++ return 0;
++ }
++
+ ret = regmap_write(priv->map, RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG,
+ 0xFFFF);
+ if (ret)
@@ -96,17 +156,18 @@
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;
++ int id;
+ u32 mode;
u16 val;
int ret;
int i;
priv = mb->priv;
-
++ id = mb->sds_id;
++
+ if (interface == PHY_INTERFACE_MODE_2500BASEX) {
+ sds_jam = rtl8365mb_sds_jam_hsgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);
@@ -116,8 +177,15 @@
/* 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 phylink_pcs *pcs, unsigned int neg_mode,
+@@ -1272,17 +1359,20 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ /* 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);
++ RTL8365MB_BYPASS_LINE_RATE_MASK(mb->sds_port),
++ 0);
+ if (ret)
return ret;
/* Tune the SerDes with vendor-prescribed parameters */
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
2026-07-12 4:31 ` [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII " Johan Alvarado via B4 Relay
2026-07-12 18:05 ` Mieczyslaw Nalewaj
@ 2026-07-13 8:14 ` Stacho_P
1 sibling, 0 replies; 6+ messages in thread
From: Stacho_P @ 2026-07-13 8:14 UTC (permalink / raw)
To: Johan Alvarado
Cc: Andrew Lunn, Vladimir Oltean, Alvin Sipraga, Linus Walleij,
Russell King, Maxime Chevallier, Luiz Angelo Daros de Luca,
Mieczyslaw Nalewaj, netdev, Stanisław Pal
Tested this series on a TP-Link Archer AX55 v1: RTL8367S with the
SerDes on external interface 1 running HSGMII (2500base-x fixed-link)
to the SoC (Qualcomm IPQ5018 stmmac gmac1). Base was OpenWrt's 6.12
kernel; the only adjustment needed was dropping the neg_mode parameter
from pcs_get_state() for the older phylink API there.
The trunk comes up at 2.5Gbps and passes traffic (all five front ports
are behind it, NAT routed through the CPU port plus wire-speed L2 on
the switch). Warm reboots and short power cycles bring it up clean
every time, no SerDes firmware involved.
One observation, quite possibly marginal silicon on my unit: after the
device has been powered off for several hours, the first boot brings
the link up (2.5Gbps/Full reported, phylink happy) but the data path
is heavily degraded - 60-70% packet loss, the surviving packets at
normal sub-ms RTT. Re-running the PCS sequence via admin down/up of
the CPU port re-rolls the dice (15% and 40% loss on two consecutive
attempts) but did not fully recover it; a soft reboot (full re-probe
including the chip reset) always restores a clean link. Short
power-offs (~a minute) do not reproduce this. For what it's worth, the
vendor firmware for this board keeps the DW8051 link-management loop
running permanently, which would effectively keep retrying out of
exactly such a state.
Happy to test follow-up patches if you want to poke at that; I don't
think it should block the series - the feature itself works as
advertised.
Tested-by: Stanisław Pal <kuncy7@gmail.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-13 8:15 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-12 4:31 [PATCH net-next v6 0/2] net: dsa: realtek: rtl8365mb: add SGMII/HSGMII support for RTL8367S Johan Alvarado via B4 Relay
2026-07-12 4:31 ` [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII " Johan Alvarado via B4 Relay
2026-07-12 8:23 ` Maxime Chevallier
2026-07-12 4:31 ` [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII " Johan Alvarado via B4 Relay
2026-07-12 18:05 ` Mieczyslaw Nalewaj
2026-07-13 8:14 ` Stacho_P
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox