* [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
[not found] <28382f3f-03c1-4606-9b11-86f118abeafe.ref@yahoo.com>
@ 2026-10-09 4:51 ` Mieczyslaw Nalewaj
2026-10-09 4:53 ` [PATCH net-next v2 1/8] net: dsa: realtek: rtl8365mb: detect RTL8367S-VB Mieczyslaw Nalewaj
` (7 more replies)
0 siblings, 8 replies; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 4:51 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
This series adds support for the RTL8367S-VB (chip ID 0x6642) to the
rtl8365mb driver. The chip belongs to a new "family D" that differs from
the existing family C in its VLAN table layout, speed/RGMII configuration
and SerDes (SDS13) handling.
Patch 1 detects the chip and introduces the family D branch.
Patches 2-4 add port speed, PVID read and RGMII mode configuration.
Patches 5-6 add VLAN 4k table access and PVID set/clear.
Patches 7-8 add SDS13 PCS support and re-latching of the SerDes.
Family C's functional behaviour is unchanged. The only change in a
shared path is a mutex serializing SerDes register access, added in
patch 8.
Tested on TP-Link Archer AX55 v1 (RTL8367S-VB, CPU port over HSGMII to ipq5018)
with bridge VLAN filtering and per-port PVIDs.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
v2:
- 2/8: mask r_speed to the width of the FORCE_SPEED field before passing
it to FIELD_PREP(), so that the family D 2500M value (5) cannot trip
the compile-time range check; bit 2 still goes to FORCE_SPEED2
(reported by the automated review).
- 4/8: the commit message now explains why EXT_TXC_DLY[5:3] is cleared
for RGMII on EXT1: the vendor RGMII setup does it, and the init jam
table leaves the field at 2, which would add an extra TX delay on top
of tx-internal-delay-ps. Add a comment describing the EXT_TXC_DLY
layout (Andrew Lunn's review).
- 8/8: document the new struct rtl8365mb members in the kernel-doc
comment (sds_lock, sds_relatch, sds_relatch_count,
sds_misc_target_val), which fixes the kernel-doc warnings.
- 1/8, 3/8, 5/8, 6/8, 7/8: no changes.
v1: https://lore.kernel.org/netdev/84fc7483-b22d-45ea-a3b8-3285dc357265@yahoo.com/
Mieczyslaw Nalewaj (8):
net: dsa: realtek: rtl8365mb: detect RTL8367S-VB
net: dsa: realtek: rtl8365mb: set speed for family D
net: dsa: realtek: rtl8365mb: get pvid for family D
net: dsa: realtek: rtl8365mb: set RGMII mode for family D
net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
net: dsa: realtek: rtl8365mb: set/clear pvid for family D
net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
net: dsa: realtek: rtl8365mb: re-latch the family D SerDes
drivers/net/dsa/realtek/rtl8365mb.h | 14 +
drivers/net/dsa/realtek/rtl8365mb_main.c | 555 ++++++++++++++++++++---
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 304 ++++++++++---
3 files changed, 761 insertions(+), 112 deletions(-)
create mode 100644 drivers/net/dsa/realtek/rtl8365mb.h
--
2.53.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v2 1/8] net: dsa: realtek: rtl8365mb: detect RTL8367S-VB
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-10-09 4:53 ` Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 4:54 ` [PATCH net-next v2 2/8] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
` (6 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 4:53 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
The RTL8367S-VB belongs to a newer hardware generation (family D) which
features a modified register layout and independent logic blocks compared
to the older legacy chips (family C). This patch introduces the family
separation by defining RTL8365MB_FAMILY_D, hooks up the new hardware
ID (0x6642), revision, and maps its unique external interface capability
where SGMII/HSGMII sits on ext_int 0 instead of ext_int 1.
Also cap priv->num_ports at RTL8365MB_D_MAX_NUM_PORTS (8) for family D.
This is a functional change: it sets ds->num_ports, the IRQ domain size
and the bound of every loop over priv->num_ports.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb.h | 14 ++++++++++
drivers/net/dsa/realtek/rtl8365mb_main.c | 35 +++++++++++++++++++++++-
2 files changed, 48 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/dsa/realtek/rtl8365mb.h
diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h
new file mode 100644
index 0000000..315de4c
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RTL8365MB_H
+#define _RTL8365MB_H
+
+#include "realtek.h"
+
+enum rtl8365mb_family {
+ RTL8365MB_FAMILY_C,
+ RTL8365MB_FAMILY_D,
+};
+
+enum rtl8365mb_family rtl8365mb_get_family(struct realtek_priv *priv);
+
+#endif /* _RTL8365MB_H */
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index da06f95..80fc551 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -81,6 +81,7 @@
* - RTL8367RB-VB
* - RTL8367SB
* - RTL8367S
+ * - RTL8367S-VB
* - RTL8370MB
* - RTL8310SR
*
@@ -110,12 +111,14 @@
#include "rtl83xx.h"
#include "rtl8365mb_l2.h"
#include "rtl8365mb_vlan.h"
+#include "rtl8365mb.h"
/* Family-specific data and limits */
#define RTL8365MB_PHYADDRMAX 7
#define RTL8365MB_NUM_PHYREGS 32
#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1)
#define RTL8365MB_MAX_NUM_PORTS 11
+#define RTL8365MB_D_MAX_NUM_PORTS 8
/* Valid for the whole family except RTL8370B, which has 4160 entries.
* RTL8370B is mentioned in vendor code but it might not even belong
* to the same RTL8367C family.
@@ -707,6 +710,7 @@ struct rtl8365mb_extint {
* @name: human-readable chip name
* @chip_id: chip identifier
* @chip_ver: chip silicon revision
+ * @family: chip family
* @extints: available external interfaces
* @jam_table: chip-specific initialization jam table
* @jam_size: size of the chip's jam table
@@ -719,6 +723,7 @@ struct rtl8365mb_chip_info {
const char *name;
u32 chip_id;
u32 chip_ver;
+ enum rtl8365mb_family family;
const struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS];
const struct rtl8365mb_jam_tbl_entry *jam_table;
size_t jam_size;
@@ -731,6 +736,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
.name = "RTL8365MB-VC",
.chip_id = 0x6367,
.chip_ver = 0x0040,
+ .family = RTL8365MB_FAMILY_C,
.extints = {
{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
PHY_INTF(RMII) | PHY_INTF(RGMII) },
@@ -742,6 +748,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
.name = "RTL8367S",
.chip_id = 0x6367,
.chip_ver = 0x00A0,
+ .family = RTL8365MB_FAMILY_C,
.extints = {
{ 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
{ 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) |
@@ -754,6 +761,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
.name = "RTL8367SB",
.chip_id = 0x6367,
.chip_ver = 0x0010,
+ .family = RTL8365MB_FAMILY_C,
.extints = {
{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
PHY_INTF(RMII) | PHY_INTF(RGMII) |
@@ -768,6 +776,7 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
.name = "RTL8367RB-VB",
.chip_id = 0x6367,
.chip_ver = 0x0020,
+ .family = RTL8365MB_FAMILY_C,
.extints = {
{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
PHY_INTF(RMII) | PHY_INTF(RGMII) },
@@ -777,6 +786,19 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
.jam_table = rtl8365mb_init_jam_8365mb_vc,
.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
},
+ {
+ .name = "RTL8367S-VB",
+ .chip_id = 0x6642,
+ .chip_ver = 0x0010,
+ .family = RTL8365MB_FAMILY_D,
+ .extints = {
+ { 6, 0, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
+ { 7, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
+ PHY_INTF(RMII) | PHY_INTF(RGMII) },
+ },
+ .jam_table = rtl8365mb_init_jam_8365mb_vc,
+ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
+ },
};
enum rtl8365mb_stp_state {
@@ -874,6 +896,13 @@ struct rtl8365mb {
#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
+enum rtl8365mb_family rtl8365mb_get_family(struct realtek_priv *priv)
+{
+ struct rtl8365mb *mb = priv->chip_data;
+
+ return mb->chip_info->family;
+}
+
static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
{
u32 val;
@@ -3349,7 +3378,11 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
dev_info(priv->dev, "found an %s switch\n", mb->chip_info->name);
- priv->num_ports = RTL8365MB_MAX_NUM_PORTS;
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ priv->num_ports = RTL8365MB_D_MAX_NUM_PORTS;
+ else
+ priv->num_ports = RTL8365MB_MAX_NUM_PORTS;
+
mb->priv = priv;
mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS;
mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL;
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 2/8] net: dsa: realtek: rtl8365mb: set speed for family D
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-10-09 4:53 ` [PATCH net-next v2 1/8] net: dsa: realtek: rtl8365mb: detect RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-10-09 4:54 ` Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 4:55 ` [PATCH net-next v2 3/8] net: dsa: realtek: rtl8365mb: get pvid " Mieczyslaw Nalewaj
` (5 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 4:54 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
On family C hardware, forcing a 2.5 Gbps link speed requires relying
strictly on the HSGMII SerDes configuration block, while forcing a dummy
1 Gbps value inside the standard digital interface register. Family D
introduces a dedicated hardware link speed value (5) for 2500M. The speed
bitfield width has been expanded to 3 bits, splitting the most significant
bit into a new separate register field (FORCE_SPEED2), which maps directly
to a distinct per-port digital interface force layout.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 71 +++++++++++++++++++-----
1 file changed, 58 insertions(+), 13 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 80fc551..b1ea8b0 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -346,6 +346,7 @@
#define RTL8365MB_PORT_SPEED_10M 0
#define RTL8365MB_PORT_SPEED_100M 1
#define RTL8365MB_PORT_SPEED_1000M 2
+#define RTL8365MB_D_PORT_SPEED_2500M 5
/* External interface force configuration registers 0~2 */
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */
@@ -363,6 +364,20 @@
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK BIT(4)
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK BIT(2)
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK GENMASK(1, 0)
+#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH 2 /* bits[1:0] */
+
+/* bits[13:12]; bit 13 reserved, no defined speed value uses it yet */
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK GENMASK(13, 12)
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE 0x12c0
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(_port) \
+ (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE + (_port))
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE 0x12c8
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(_port) \
+ (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE + (_port))
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK GENMASK(15, 0)
/* CPU port mask register - controls which ports are treated as CPU ports */
#define RTL8365MB_CPU_PORT_MASK_REG 0x1219
@@ -1661,23 +1676,31 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
u32 r_duplex;
u32 r_speed;
u32 r_link;
+ bool is_d;
int val;
int ret;
if (!extint)
return -ENODEV;
+ is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
if (link) {
/* Force the link up with the desired configuration */
r_link = 1;
r_rx_pause = rx_pause ? 1 : 0;
r_tx_pause = tx_pause ? 1 : 0;
- /* 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) {
+ if (speed == SPEED_2500) {
+ if (is_d) {
+ r_speed = RTL8365MB_D_PORT_SPEED_2500M;
+ } else {
+ /* 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.
+ */
+ r_speed = RTL8365MB_PORT_SPEED_1000M;
+ }
+ } else if (speed == SPEED_1000) {
r_speed = RTL8365MB_PORT_SPEED_1000M;
} else if (speed == SPEED_100) {
r_speed = RTL8365MB_PORT_SPEED_100M;
@@ -1707,20 +1730,42 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
r_duplex = 0;
}
- val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) |
- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK,
+ val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK,
r_tx_pause) |
FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK,
r_rx_pause) |
FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) |
FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK,
r_duplex) |
- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed);
- ret = regmap_write(priv->map,
- RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id),
- val);
- if (ret)
- return ret;
+ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK,
+ r_speed & RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK);
+
+ if (is_d) {
+ /* Speed is 3 bits on family D: bits[1:0] go into FORCE_SPEED,
+ * bit[2] goes into FORCE_SPEED2 (bit 12); only 2500M sets it,
+ * bit 13 of the field is unused.
+ */
+ val |= FIELD_PREP(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK,
+ r_speed >> RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH);
+ ret = regmap_write(priv->map,
+ RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(port),
+ val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map,
+ RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(port),
+ RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK);
+ if (ret)
+ return ret;
+ } else {
+ val |= FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1);
+ ret = regmap_write(priv->map,
+ RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id),
+ val);
+ if (ret)
+ return ret;
+ }
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 3/8] net: dsa: realtek: rtl8365mb: get pvid for family D
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-10-09 4:53 ` [PATCH net-next v2 1/8] net: dsa: realtek: rtl8365mb: detect RTL8367S-VB Mieczyslaw Nalewaj
2026-10-09 4:54 ` [PATCH net-next v2 2/8] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
@ 2026-10-09 4:55 ` Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 4:56 ` [PATCH net-next v2 4/8] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
` (4 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 4:55 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Family C silicon requires reading the port's default PVID mask by
executing an indirect lookup via the shared VLAN member configuration
(MC) table index path. Since family D lacks a working hardware
implementation of this MC allocation table, attempting to parse it
returns junk. Fix this by bypassing the old MC loop entirely on family
D and reading the 12-bit PVID mask value straight from the dedicated
per-port hardware control register base.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 25 ++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 8d49ffa..7add1fb 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -48,6 +48,7 @@
#include "rtl8365mb_vlan.h"
#include "rtl8365mb_table.h"
+#include "rtl8365mb.h"
#include <linux/if_bridge.h>
#include <linux/lockdep.h>
#include <linux/regmap.h>
@@ -113,6 +114,11 @@
#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \
(0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p))
+#define RTL8365MB_D_VLAN_PVID_CTRL_BASE 0x0700
+#define RTL8365MB_D_VLAN_PVID_CTRL_REG(port) \
+ (RTL8365MB_D_VLAN_PVID_CTRL_BASE + (port))
+#define RTL8365MB_D_VLAN_PVID_CTRL_MASK GENMASK(11, 0)
+
/* Frame type filtering registers */
#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa
#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \
@@ -679,11 +685,22 @@ int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid)
u8 vlanmc_idx;
int ret;
- ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc);
- if (ret)
- return ret;
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {
+ u32 data;
+
+ ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port), &data);
+ if (ret)
+ return ret;
+
+ *pvid = data & RTL8365MB_D_VLAN_PVID_CTRL_MASK;
+ } else {
+ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc);
+ if (ret)
+ return ret;
+
+ *pvid = vlanmc.evid;
+ }
- *pvid = vlanmc.evid;
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 4/8] net: dsa: realtek: rtl8365mb: set RGMII mode for family D
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (2 preceding siblings ...)
2026-10-09 4:55 ` [PATCH net-next v2 3/8] net: dsa: realtek: rtl8365mb: get pvid " Mieczyslaw Nalewaj
@ 2026-10-09 4:56 ` Mieczyslaw Nalewaj
2026-10-09 4:58 ` [PATCH net-next v2 5/8] net: dsa: realtek: rtl8365mb: set and get vlan 4k " Mieczyslaw Nalewaj
` (3 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 4:56 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
On family D, RGMII on extension interface 1 needs two things that the
other families do not: the EXT1 RGMII TX clock delay field in
EXT_TXC_DLY (0x13f9, bits [5:3]) must be cleared, and the pin mux in
the top-level configuration register must be set up.
The vendor RGMII setup for EXT1 clears the RGMII field of EXT_TXC_DLY
(0x13f9, bits [5:3]) in addition to programming TXDELAY in RGMXF.
Do the same. The init jam table writes 0x0090 to this register, which
leaves the field at 2, so without the clear an extra TX delay would be
applied on top of tx-internal-delay-ps. Clearing it makes
tx-internal-delay-ps the only source of TX delay.
If the internal MAC4 block is not occupying extension 1, bind the
RGMII/MII pins to MAC7. SerDes 1, which shares the pins, is always
disabled.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 47 ++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index b1ea8b0..3819a48 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -334,6 +334,12 @@
#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249
#define RTL8365MB_SDS_OPTION_REG 0x13C1
+#define RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK GENMASK(4, 0)
+/* Shared "disable" encoding for both SDS_MISC's and SDS1_MISC0's
+ * 5-bit mode fields.
+ */
+#define RTL8365MB_D_PORT_SDS_MODE_DISABLE 0x1f
+
/* 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
@@ -459,6 +465,20 @@
#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK GENMASK(5, 4)
#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK GENMASK(3, 0)
+/* EXT_TXC_DLY holds a 3-bit TX clock delay per external interface, for
+ * RGMII ([2:0] EXT0, [5:3] EXT1, [8:6] EXT2) and separately for GMII.
+ * Only the EXT1 RGMII field is used here, and it is cleared in RGMII
+ * mode so that tx-internal-delay-ps is the only TX delay applied.
+ */
+#define RTL8365MB_D_REG_EXT_TXC_DLY 0x13f9
+#define RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK GENMASK(5, 3)
+
+#define RTL8365MB_D_REG_TOP_CON0 0x1d70
+#define RTL8365MB_D_MAC7_SEL_EXT1_MASK BIT(13)
+#define RTL8365MB_D_MAC4_SEL_EXT1_MASK BIT(12)
+
+#define RTL8365MB_D_REG_SDS1_MISC0 0x1d78
+
/**
* enum rtl8365mb_vlan_egress_mode - port VLAN egress mode
* @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry
@@ -1208,6 +1228,7 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
struct dsa_port *dp;
int tx_delay = 0;
int rx_delay = 0;
+ u32 data;
u32 val;
int ret;
@@ -1277,6 +1298,32 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
if (ret)
return ret;
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D && extint->id == 1) {
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_D_REG_EXT_TXC_DLY,
+ RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK, 0);
+ if (ret)
+ return ret;
+ /* Configure RGMII/MII mux to port 7 if UTP_PORT4 is not RGMII mode */
+ ret = regmap_read(priv->map, RTL8365MB_D_REG_TOP_CON0, &data);
+ if (ret)
+ return ret;
+ if ((data & RTL8365MB_D_MAC4_SEL_EXT1_MASK) == 0) {
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_D_REG_TOP_CON0,
+ RTL8365MB_D_MAC7_SEL_EXT1_MASK,
+ RTL8365MB_D_MAC7_SEL_EXT1_MASK);
+ if (ret)
+ return ret;
+ }
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_D_REG_SDS1_MISC0,
+ RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK,
+ RTL8365MB_D_PORT_SDS_MODE_DISABLE);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 5/8] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (3 preceding siblings ...)
2026-10-09 4:56 ` [PATCH net-next v2 4/8] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
@ 2026-10-09 4:58 ` Mieczyslaw Nalewaj
2026-10-09 4:59 ` [PATCH net-next v2 6/8] net: dsa: realtek: rtl8365mb: set/clear pvid " Mieczyslaw Nalewaj
` (2 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 4:58 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
The hardware structure of 4K VLAN table entries has changed between
silicon families. Family D's CVLAN entry is two 16-bit words rather
than three: there is no third word extending member/untag past bit 7,
and no priority/meter fields at all. The FID field is restricted to a
narrower 2-bit width. The chip itself supports both IVL and SVL, but
this driver always requests IVL for VLAN 4K entries
(rtl8365mb_vlan_4k_port_set() sets ivl_en = true on both families), so
that control bit is forced here to match existing driver behavior,
not a hardware limitation.
Reusing the three-word family C layout unconditionally means the third
word is read from and written back to the register that holds it on
family C, but does not exist as a distinct table word on family D's
die; whatever data happens to be there gets folded into member/untag
bits [10:8] on read, and re-written on every read-modify-write cycle
in rtl8365mb_vlan_4k_port_set(). Give family D its own two-word
pack/unpack instead.
The two-word layout matches the vendor switch API for this chip
family, which writes only 0x0510/0x0511 for a CVLAN entry.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 143 +++++++++++++++--------
1 file changed, 94 insertions(+), 49 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 7add1fb..0466e8c 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -55,6 +55,7 @@
/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */
#define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */
+#define RTL8365MB_D_CVLAN_ENTRY_SIZE 2 /* 32-bits, no 3rd word */
#define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK GENMASK(7, 0)
#define RTL8365MB_CVLAN_MBR_LO_MASK GENMASK(7, 0)
#define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK GENMASK(15, 8)
@@ -66,6 +67,10 @@
#define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK GENMASK(13, 9)
#define RTL8365MB_CVLAN_METERIDX_LO_MASK GENMASK(4, 0)
#define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK GENMASK(14, 14)
+#define RTL8365MB_D_CVLAN_ENTRY_D1_SVLAN_CHK_IVL_SVL_MASK \
+ GENMASK(2, 2)
+#define RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK GENMASK(3, 3)
+#define RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK GENMASK(1, 0)
/* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */
#define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK GENMASK(2, 0)
#define RTL8365MB_CVLAN_MBR_HI_MASK GENMASK(10, 8)
@@ -191,13 +196,16 @@ struct rtl8365mb_vlanmc {
static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
struct rtl8365mb_vlan4k *vlan4k)
{
+ bool is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+ size_t entry_size = is_d ? RTL8365MB_D_CVLAN_ENTRY_SIZE :
+ RTL8365MB_CVLAN_ENTRY_SIZE;
u16 data[RTL8365MB_CVLAN_ENTRY_SIZE];
int val;
int ret;
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
RTL8365MB_TABLE_OP_READ, &vid, 0, 0,
- data, ARRAY_SIZE(data));
+ data, entry_size);
if (ret)
return ret;
@@ -205,33 +213,51 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
memset(vlan4k, 0, sizeof(*vlan4k));
vlan4k->vid = vid;
+ /* member/untag: d0[7:0]/d0[15:8] on both families. Family C
+ * extends these into data[2] bits [2:0]/[5:3] for its 9th-11th
+ * ports; family D's die has only 8 ports and no third table
+ * word, so data[2] does not exist there and must not be read.
+ */
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]);
vlan4k->member = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val);
- val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);
- vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);
+ if (!is_d) {
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);
+ vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);
+ }
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]);
vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val);
- val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);
- vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);
-
- vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);
- vlan4k->priority_en =
- FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);
- vlan4k->priority =
- FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);
- vlan4k->policing_en =
- FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);
-
- val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);
- val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
- vlan4k->meteridx = val;
- val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);
- val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
- vlan4k->meteridx |= val;
-
- vlan4k->ivl_en =
- FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);
+ if (!is_d) {
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);
+ vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);
+ }
+
+ if (is_d) {
+ vlan4k->fid = FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, data[1]);
+ /* Family D has no priority/meter fields in this entry -
+ * left zeroed by the memset() above.
+ */
+ vlan4k->ivl_en =
+ FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK, data[1]);
+ } else {
+ vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);
+ vlan4k->priority_en =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);
+ vlan4k->priority =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);
+ vlan4k->policing_en =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);
+ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
+ vlan4k->meteridx = val;
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);
+ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
+ vlan4k->meteridx |= val;
+
+ vlan4k->ivl_en =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);
+ }
return 0;
}
@@ -239,6 +265,9 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,
const struct rtl8365mb_vlan4k *vlan4k)
{
+ bool is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+ size_t entry_size = is_d ? RTL8365MB_D_CVLAN_ENTRY_SIZE :
+ RTL8365MB_CVLAN_ENTRY_SIZE;
u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 };
u16 vid;
int val;
@@ -250,36 +279,52 @@ static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,
val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag);
data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val);
- data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid);
- data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,
- vlan4k->priority_en);
- data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,
- vlan4k->priority);
- data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,
- vlan4k->policing_en);
-
- /* FIELD_* does not play nice with struct bitfield. */
- val = vlan4k->meteridx;
- val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
- data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);
-
- data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,
- vlan4k->ivl_en);
-
- val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member);
- data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);
-
- val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag);
- data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);
-
- val = vlan4k->meteridx;
- val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
- data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);
+ if (is_d) {
+ /* The chip supports both IVL and SVL, but the caller (see
+ * rtl8365mb_vlan_4k_port_set()) never requests SVL, so both
+ * IVL/SVL selector bits are forced here rather than threaded
+ * through from vlan4k->ivl_en, which family C does honor.
+ */
+ data[1] |= FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK, 1) |
+ FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_SVLAN_CHK_IVL_SVL_MASK, 1);
+ data[1] |= FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid);
+ /* No priority/meter/member-untag-extension fields exist in
+ * family D's 2-word entry - data[1] and data[0] above are
+ * the whole entry, and data[2] is not part of it at all.
+ */
+ } else {
+ val = vlan4k->fid;
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, val);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,
+ vlan4k->priority_en);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,
+ vlan4k->priority);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,
+ vlan4k->policing_en);
+
+ /* FIELD_* does not play nice with struct bitfield. */
+ val = vlan4k->meteridx;
+ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);
+
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,
+ vlan4k->ivl_en);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member);
+ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag);
+ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);
+
+ val = vlan4k->meteridx;
+ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
+ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);
+ }
vid = vlan4k->vid;
return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0,
- data, ARRAY_SIZE(data));
+ data, entry_size);
}
static int
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 6/8] net: dsa: realtek: rtl8365mb: set/clear pvid for family D
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (4 preceding siblings ...)
2026-10-09 4:58 ` [PATCH net-next v2 5/8] net: dsa: realtek: rtl8365mb: set and get vlan 4k " Mieczyslaw Nalewaj
@ 2026-10-09 4:59 ` Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 5:00 ` [PATCH net-next v2 7/8] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
2026-10-09 5:02 ` [PATCH net-next v2 8/8] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes Mieczyslaw Nalewaj
7 siblings, 1 reply; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 4:59 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
The RTL8367S-VB (family D) has no working VLAN member-config (MC)
table in hardware. rtl8365mb_vlan_port_get_pvid() was already fixed
to read PVID directly from its dedicated per-port register, but
rtl8365mb_vlan_pvid_port_set() and rtl8365mb_vlan_pvid_port_clear()
still went through the MC-index allocation path meant for family C,
which on family D hardware writes into the wrong register/field
(colliding with the neighbouring port's real PVID register) and reads
back a VLAN MC table that does not exist on this silicon.
Add a direct-VID fast path for both functions, mirroring what was
already done for the getter, and skip the MC table entirely for
family D.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 136 +++++++++++++++++++++++
1 file changed, 136 insertions(+)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 0466e8c..da517b1 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -810,6 +810,64 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
val);
}
+/*
+ * rtl8365mb_vlan_pvid_port_set_direct() - Configure a port's PVID as a raw
+ * VID written to its dedicated register, for chip families without a
+ * working VLAN MC table (RTL8365MB_FAMILY_D)
+ *
+ * Reads back the previous PVID and frame filter first so both can be
+ * restored if enabling the new PVID fails partway through, matching
+ * the rollback behavior of the family-C implementation above.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rtl8365mb_vlan_pvid_port_set_direct(struct realtek_priv *priv,
+ int port, u16 vid)
+{
+ enum rtl8365mb_frame_ingress prev_accepted_frame;
+ u32 prev_pvid;
+ int ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ &prev_pvid);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read current PVID\n");
+ return ret;
+ }
+ prev_pvid &= RTL8365MB_D_VLAN_PVID_CTRL_MASK;
+
+ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, &prev_accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get current framefilter\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_D_VLAN_PVID_CTRL_MASK,
+ vid & RTL8365MB_D_VLAN_PVID_CTRL_MASK);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port PVID\n");
+ return ret;
+ }
+
+ /* Changing accept frame is what enables PVID (if not enabled before) */
+ ret = rtl8365mb_vlan_port_set_framefilter(priv, port,
+ RTL8365MB_FRAME_TYPE_ANY_FRAME);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port frame filter\n");
+ goto undo_pvid_write;
+ }
+
+ return 0;
+
+undo_pvid_write:
+ (void)regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_D_VLAN_PVID_CTRL_MASK, prev_pvid);
+ (void)rtl8365mb_vlan_port_set_framefilter(priv, port, prev_accepted_frame);
+ return ret;
+}
+
/*
* rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated
* VLANMC entry
@@ -839,6 +897,13 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
lockdep_assert_held(&priv->vlan_lock);
+ /* This chip family has no VLAN MC table - PVID is a raw VID in a
+ * dedicated per-port register, and there is no separate membership
+ * table entry to allocate/track.
+ */
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ return rtl8365mb_vlan_pvid_port_set_direct(priv, port, vid);
+
/* Read the old PVID exclusively to undo in case of error */
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx,
&prev_vlanmc);
@@ -918,6 +983,74 @@ undo_vlan_mc_port_set:
return ret;
}
+/*
+ * rtl8365mb_vlan_pvid_port_clear_direct() - Remove a port's raw-VID PVID
+ * configuration, for chip families without a working VLAN MC table
+ * (RTL8365MB_FAMILY_D)
+ *
+ * Reads back the previous frame filter first so it can be restored if
+ * clearing the PVID register fails.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rtl8365mb_vlan_pvid_port_clear_direct(struct dsa_switch *ds,
+ int port, u16 vid)
+{
+ enum rtl8365mb_frame_ingress prev_accepted_frame;
+ struct realtek_priv *priv = ds->priv;
+ bool filtering;
+ u32 cur_pvid;
+ int ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ &cur_pvid);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read current PVID\n");
+ return ret;
+ }
+
+ /* Port is not using this VID as PVID. Nothing to remove. */
+ if ((cur_pvid & RTL8365MB_D_VLAN_PVID_CTRL_MASK) != vid)
+ return 0;
+
+ filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, port));
+
+ /* Changing accept frame is what really removes PVID. But only do
+ * that if VLAN filtering is enabled.
+ */
+ if (filtering) {
+ ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
+ &prev_accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get current framefilter\n");
+ return ret;
+ }
+
+ ret = rtl8365mb_vlan_port_set_framefilter(
+ priv, port, RTL8365MB_FRAME_TYPE_TAGGED_ONLY);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port frame filter\n");
+ return ret;
+ }
+ }
+
+ ret = regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_D_VLAN_PVID_CTRL_MASK, 0);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port PVID to 0\n");
+ goto undo_set_framefilter;
+ }
+
+ return 0;
+
+undo_set_framefilter:
+ if (filtering)
+ (void)rtl8365mb_vlan_port_set_framefilter(priv, port,
+ prev_accepted_frame);
+ return ret;
+}
+
/*
* rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration
* @ds: dsa switch instance
@@ -941,6 +1074,9 @@ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
lockdep_assert_held(&priv->vlan_lock);
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ return rtl8365mb_vlan_pvid_port_clear_direct(ds, port, vid);
+
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx,
&vlanmc);
if (ret) {
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 7/8] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (5 preceding siblings ...)
2026-10-09 4:59 ` [PATCH net-next v2 6/8] net: dsa: realtek: rtl8365mb: set/clear pvid " Mieczyslaw Nalewaj
@ 2026-10-09 5:00 ` Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 5:02 ` [PATCH net-next v2 8/8] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes Mieczyslaw Nalewaj
7 siblings, 1 reply; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 5:00 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Extend the existing RTL8367S SGMII/HSGMII PCS implementation in this
driver for family D switches, whose CPU SerDes is reached through the
indexed SDS13 window and whose SDS_MISC fields differ from family C.
The register sequence and tuning tables are derived from the GPL-licensed
Realtek RTL8367D port API distributed in the Mercusys MR80X GPL release.
Keep the existing family C path unchanged and select the new behavior from
the driver's chip-family metadata, making the implementation reusable by
other family D boards.
Co-developed-by: Fabiano Tassotti <fabianotassotti@gmail.com>
Signed-off-by: Fabiano Tassotti <fabianotassotti@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 181 ++++++++++++++++++-----
1 file changed, 145 insertions(+), 36 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 3819a48..4cad90f 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -286,6 +286,7 @@
#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK BIT(8)
#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK BIT(7)
#define RTL8365MB_SDS_INDACS_CMD_WR_MASK BIT(6)
+#define RTL8365MB_SDS_INDACS_CMD_INDEX_MASK GENMASK(5, 0)
#define RTL8365MB_SDS_INDACS_ADR_REG 0x6601
#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602
@@ -334,7 +335,22 @@
#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249
#define RTL8365MB_SDS_OPTION_REG 0x13C1
+/* Family D uses the SDS13 indirect window for its MAC6 SerDes. */
+#define RTL8365MB_D_SDS_EXT0_INDEX 13
+#define RTL8365MB_D_FIBER_CFG2_REG 0x13E8
+#define RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK GENMASK(7, 6)
+#define RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0 BIT(6)
+#define RTL8365MB_D_SDS_MISC_PA33PC_EN BIT(11)
+#define RTL8365MB_D_SDS_MISC_PA12PC_EN BIT(10)
+#define RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 BIT(9)
#define RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK GENMASK(4, 0)
+#define RTL8365MB_D_SDS_MISC_MODE_SGMII 0x02
+#define RTL8365MB_D_SDS_MISC_MODE_HSGMII 0x12
+#define RTL8365MB_D_SDS_MISC_CFG_MASK \
+ (RTL8365MB_D_SDS_MISC_PA33PC_EN | \
+ RTL8365MB_D_SDS_MISC_PA12PC_EN | \
+ RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 | \
+ RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK)
/* Shared "disable" encoding for both SDS_MISC's and SDS1_MISC0's
* 5-bit mode fields.
*/
@@ -714,6 +730,21 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = {
{ 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 },
};
+/* Family D tuning tables from the Realtek vendor port API. */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_sgmii[] = {
+ { 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 },
+ { 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 },
+ { 0x0484, 0x011B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 },
+ { 0x0004, 0x074F },
+};
+
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_hsgmii[] = {
+ { 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 },
+ { 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 },
+ { 0x0504, 0x051B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 },
+ { 0x0004, 0x074F },
+};
+
enum rtl8365mb_phy_interface_mode {
RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,
RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),
@@ -1327,7 +1358,8 @@ 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)
+static int rtl8365mb_sds_write(struct realtek_priv *priv, u8 index,
+ u16 addr, u16 data)
{
int ret;
@@ -1345,10 +1377,13 @@ static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)
*/
return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
- RTL8365MB_SDS_INDACS_CMD_WR_MASK);
+ RTL8365MB_SDS_INDACS_CMD_WR_MASK |
+ FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
+ index));
}
-static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
+static int rtl8365mb_sds_read(struct realtek_priv *priv, u8 index,
+ u16 addr, u16 *data)
{
u32 val;
int ret;
@@ -1358,7 +1393,9 @@ static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
return ret;
ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
- RTL8365MB_SDS_INDACS_CMD_RUN_MASK);
+ RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+ FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
+ index));
if (ret)
return ret;
@@ -1398,6 +1435,14 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
int ret;
int i;
+ /* Family D has a fixed SDS13 programming model and does not use the
+ * family C option register to select its tuning table.
+ */
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {
+ mb->sds_supported = true;
+ return 0;
+ }
+
/* 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];
@@ -1476,28 +1521,47 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
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;
+ const struct rtl8365mb_jam_tbl_entry *sds_jam;
struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
- struct realtek_priv *priv;
+ struct realtek_priv *priv = mb->priv;
size_t sds_jam_size;
- u32 mode;
+ u32 misc_mask;
+ u32 misc_val;
+ u32 sds_mode;
+ u8 sds_index;
+ bool is_d;
u16 val;
int ret;
int i;
- priv = mb->priv;
+ is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+ /* Select the appropriate tuning table and SDS mode */
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;
+ if (is_d) {
+ sds_jam = rtl8365mb_d_sds_jam_hsgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_hsgmii);
+ sds_mode = RTL8365MB_D_SDS_MISC_MODE_HSGMII;
+ } else {
+ sds_jam = rtl8365mb_sds_jam_hsgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);
+ sds_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;
+ if (is_d) {
+ sds_jam = rtl8365mb_d_sds_jam_sgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_sgmii);
+ sds_mode = RTL8365MB_D_SDS_MISC_MODE_SGMII;
+ } else {
+ sds_jam = rtl8365mb_sds_jam_sgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);
+ sds_mode = RTL8365MB_EXT_PORT_MODE_SGMII;
+ }
}
+ sds_index = is_d ? RTL8365MB_D_SDS_EXT0_INDEX : 0;
+
/* 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
@@ -1526,34 +1590,53 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
/* Tune the SerDes with vendor-prescribed parameters */
for (i = 0; i < sds_jam_size; i++) {
- ret = rtl8365mb_sds_write(priv, sds_jam[i].reg,
- sds_jam[i].val);
+ ret = rtl8365mb_sds_write(priv, sds_index,
+ sds_jam[i].reg, sds_jam[i].val);
+ if (ret)
+ return ret;
+ }
+
+ /* Family-specific post-tuning configuration */
+ if (is_d) {
+ ret = regmap_update_bits(priv->map, RTL8365MB_D_FIBER_CFG2_REG,
+ RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK,
+ RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0);
if (ret)
return ret;
+
+ misc_mask = RTL8365MB_D_SDS_MISC_CFG_MASK;
+ misc_val = RTL8365MB_D_SDS_MISC_PA33PC_EN |
+ RTL8365MB_D_SDS_MISC_PA12PC_EN |
+ RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 | sds_mode;
+ } else {
+ /* Mux the SerDes to MAC8 in the requested mode */
+ misc_mask = RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
+ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK;
+ misc_val = (sds_mode == RTL8365MB_EXT_PORT_MODE_SGMII) ?
+ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
+ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK;
}
- /* 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,
- mode == RTL8365MB_EXT_PORT_MODE_SGMII ?
- RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
- RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK);
+ misc_mask, misc_val);
if (ret)
return ret;
- 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),
- val);
- if (ret)
- return ret;
+ if (!is_d) {
+ val = sds_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),
+ 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,
+ ret = rtl8365mb_sds_write(priv, sds_index,
+ RTL8365MB_SDS_REG_RESET,
RTL8365MB_SDS_RESET_DEASSERT);
if (ret)
return ret;
@@ -1563,12 +1646,14 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
* 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,
+ ret = rtl8365mb_sds_write(priv, sds_index,
+ RTL8365MB_SDS_REG_BMCR,
RTL8365MB_SDS_BMCR_DPRST_PHASE1);
if (ret)
return ret;
- ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+ ret = rtl8365mb_sds_write(priv, sds_index,
+ RTL8365MB_SDS_REG_BMCR,
RTL8365MB_SDS_BMCR_DPRST_PHASE2);
if (ret)
return ret;
@@ -1576,14 +1661,16 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
/* 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);
+ ret = rtl8365mb_sds_read(priv, sds_index,
+ 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);
+ return rtl8365mb_sds_write(priv, sds_index,
+ RTL8365MB_SDS_REG_NWAY, val);
}
static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
@@ -1608,10 +1695,14 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
{
struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
struct realtek_priv *priv = mb->priv;
+ u8 sds_index = 0;
u16 status;
+ bool is_d;
u32 val;
int ret;
+ is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+
/* 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
@@ -1619,7 +1710,11 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
* 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 (is_d)
+ sds_index = RTL8365MB_D_SDS_EXT0_INDEX;
+
+ ret = rtl8365mb_sds_read(priv, sds_index,
+ RTL8365MB_SDS_REG_LINK_STATUS, &status);
if (ret) {
state->link = false;
return;
@@ -1630,6 +1725,13 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
if (!state->link)
return;
+ if (is_d) {
+ state->duplex = DUPLEX_FULL;
+ state->speed = state->interface == PHY_INTERFACE_MODE_2500BASEX ?
+ SPEED_2500 : SPEED_1000;
+ return;
+ }
+
/* The speed and duplex are forced; read them back from the values
* programmed into the SerDes MISC register.
*/
@@ -1671,6 +1773,12 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
u32 r_speed;
int ret;
+ /* Family D forces the external MAC ability from mac_link_up(); its
+ * SDS_MISC fields do not share the family C link-force layout.
+ */
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ return;
+
/* 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.
@@ -1997,7 +2105,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
* rtl8365mb_pcs_link_up() because pcs_link_up() carries no
* pause information.
*/
- if (rtl8365mb_interface_is_serdes(interface)) {
+ if (rtl8365mb_interface_is_serdes(interface) &&
+ rtl8365mb_get_family(priv) != RTL8365MB_FAMILY_D) {
u32 val = 0;
if (tx_pause)
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 8/8] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (6 preceding siblings ...)
2026-10-09 5:00 ` [PATCH net-next v2 7/8] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
@ 2026-10-09 5:02 ` Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
7 siblings, 1 reply; 15+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-09 5:02 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
The family D receiver latches on the DISABLE -> HSGMII edge in SDS_MISC
rather than on the value, and it will not latch until the MAC at the
other end of the trunk has brought its own half of the link up. If it
does not latch, the trunk reports 2.5Gbps/Full on both sides but moves
no frames: the CPU MAC transmits, the switch CPU port counts no octets,
and there are no CRC, alignment or FIFO errors.
The two ends can be seconds apart, e.g. on an IPQ5018 board:
5.27 rtl8365mb: configuring for fixed/2500base-x <- pcs_config()
7.72 ipq5018-gmac-dwmac eth0: configuring for fixed/2500base-x
8.02 ipq5018-gmac-dwmac eth0: Link is Up - 2.5Gbps/Full
8.56 SerDes latches
pcs_config() runs seconds before the far-end MAC even starts
configuring, so a single edge driven from there is not enough: the
receiver has nothing to latch onto yet. Neither is pcs_link_up(),
which is seconds early for the same reason. phylink does not help
either - it never calls rtl8365mb_pcs_get_state() for this port in
the first place, since mb->pcs.poll only arms phylink's own poll for
in-band links, and this CPU port is a fixed link.
There is no local signal that predicts when the far-end MAC will
bring up its half of the link (bit 8 of the SDS link-status word
does not change ahead of it), so the work cannot wait for it; it can
only tell afterwards, from the link-status bit, whether an edge took.
Nothing external re-checks this later. So pcs_config() kicks off one
attempt, and the work re-arms itself after each edge; the next run
checks whether the edge took, rather than waiting on a poll that will
not come.
Each attempt (park for 20 ms, then restore the target mode) is
performed by a short work: sleeping is not permitted in the PCS ops,
and the work also serializes against pcs_config() re-runs, which
cancel it. Before parking, the work reads the link status itself and
does not park if the receiver has already latched, since the condition
that queued it may be stale by the time it runs; it then ends the
episode instead.
Attempts are spaced about 1 s apart and capped at
RTL8365MB_D_SDS_RELATCH_MAX_TRIES (15) per fast episode. After the last
attempt one more run checks whether it took and only then warns that
the SerDes did not latch. The work then keeps driving one edge per
RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL, so a far end that comes up late
or a link that drops again is recovered without a reconfiguration,
at a bounded cost of one 20 ms park per interval.
Once the receiver has latched, the work keeps checking the link every
10 s (RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL). If it finds the link down
outside a reconfiguration, e.g. after the far end reset its PLL, it
logs that and starts a new episode. An episode also starts at every
pcs_config().
For family D, pcs_config() keeps the masked update of SDS_MISC and saves
the value it wrote as the target the work restores. The work parks and
restores with masked updates over the same fields, so other bits are
never touched.
The work now accesses the shared SDS_INDACS ADR/CMD/DATA window and
SDS_MISC concurrently with the PCS and MAC link_up paths and
pcs_get_state(), so those accesses are serialized by a new sds_lock.
The work is cancelled in teardown(), and in pcs_config() before
SDS_MISC is touched, since a link that flaps would otherwise re-arm
the sequence against a half-reconfigured SerDes.
Co-developed-by: Oleg Gavrilov <gabonpivovich@gmail.com>
Signed-off-by: Oleg Gavrilov <gabonpivovich@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 233 +++++++++++++++++++++--
1 file changed, 218 insertions(+), 15 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 4cad90f..eb71c89 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -98,6 +98,7 @@
#include <linux/irqdomain.h>
#include <linux/mii.h>
#include <linux/mutex.h>
+#include <linux/workqueue.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
#include <linux/if_bridge.h>
@@ -300,6 +301,23 @@
#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK GENMASK(8, 7)
#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK BIT(6)
+/* Re-latch retry interval and attempt budget. Each attempt parks the
+ * SerDes for 20 ms, so a far end that never comes up must not be retried
+ * at this rate forever, and the interval must not be so tight that
+ * back-to-back parks starve a link that is about to latch. The count is
+ * reset to 1 by pcs_config() and to 0 whenever the receiver latches;
+ * attempts start at pcs_config(), well before the far end MAC is up, so
+ * with the interval below the budget is roughly RELATCH_MAX_TRIES seconds,
+ * generous enough to outlast a slow conduit.
+ */
+#define RTL8365MB_D_SDS_RELATCH_INTERVAL (HZ)
+#define RTL8365MB_D_SDS_RELATCH_MAX_TRIES 15
+
+/* How often a latched (or out-of-budget) SerDes is checked, and re-latched
+ * if still down. Much slower than the re-latch interval.
+ */
+#define RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL (10 * HZ)
+
/* 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
@@ -942,10 +960,16 @@ struct rtl8365mb_port {
* @chip_info: chip-specific info about the attached switch
* @cpu: CPU tagging and CPU port configuration for this chip
* @mib_lock: prevent concurrent reads of MIB counters
+ * @sds_lock: serializes access to the shared SDS_INDACS ADR/CMD/DATA window
+ * and to RTL8365MB_SDS_MISC_REG, reachable both from phylink's
+ * PCS callbacks and from the family D SerDes re-latch work
* @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
+ * @sds_relatch: re-latch edges and link health check (family D)
+ * @sds_relatch_count: next attempt of the re-latch episode, 0 once latched
+ * @sds_misc_target_val: SDS_MISC_CFG_MASK fields the re-latch work restores
*
* Private data for this driver.
*/
@@ -955,9 +979,13 @@ struct rtl8365mb {
const struct rtl8365mb_chip_info *chip_info;
struct rtl8365mb_cpu cpu;
struct mutex mib_lock;
+ struct mutex sds_lock;
struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS];
struct phylink_pcs pcs;
bool sds_supported;
+ struct delayed_work sds_relatch;
+ unsigned int sds_relatch_count;
+ u32 sds_misc_target_val;
};
#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
@@ -1361,43 +1389,53 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
static int rtl8365mb_sds_write(struct realtek_priv *priv, u8 index,
u16 addr, u16 data)
{
+ struct rtl8365mb *mb = priv->chip_data;
int ret;
+ mutex_lock(&mb->sds_lock);
+
ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data);
if (ret)
- return ret;
+ goto out_unlock;
ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
if (ret)
- return ret;
+ goto out_unlock;
/* 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 |
- FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
- index));
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+ RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+ RTL8365MB_SDS_INDACS_CMD_WR_MASK |
+ FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
+ index));
+
+out_unlock:
+ mutex_unlock(&mb->sds_lock);
+ return ret;
}
static int rtl8365mb_sds_read(struct realtek_priv *priv, u8 index,
u16 addr, u16 *data)
{
+ struct rtl8365mb *mb = priv->chip_data;
u32 val;
int ret;
+ mutex_lock(&mb->sds_lock);
+
ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
if (ret)
- return ret;
+ goto out_unlock;
ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
index));
if (ret)
- return ret;
+ goto out_unlock;
/* Wait for the indirect read to complete: the engine clears the BUSY
* bit once the data register holds the result.
@@ -1407,15 +1445,19 @@ static int rtl8365mb_sds_read(struct realtek_priv *priv, u8 index,
!(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK),
10, 1000);
if (ret)
- return ret;
+ goto out_unlock;
ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val);
if (ret)
- return ret;
+ goto out_unlock;
*data = val;
- return 0;
+ ret = 0;
+
+out_unlock:
+ mutex_unlock(&mb->sds_lock);
+ return ret;
}
/* The vendor driver selects between two sets of SerDes tuning parameters based
@@ -1537,6 +1579,13 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
+ /* Cancel any in-flight re-latch edge before touching SDS_MISC: the
+ * work drops sds_lock across its sleep and could otherwise interleave
+ * with the reconfiguration below.
+ */
+ if (is_d)
+ cancel_delayed_work_sync(&mb->sds_relatch);
+
/* Select the appropriate tuning table and SDS mode */
if (interface == PHY_INTERFACE_MODE_2500BASEX) {
if (is_d) {
@@ -1617,12 +1666,16 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK;
}
+ mutex_lock(&mb->sds_lock);
ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
misc_mask, misc_val);
+ mutex_unlock(&mb->sds_lock);
if (ret)
return ret;
- if (!is_d) {
+ if (is_d) {
+ WRITE_ONCE(mb->sds_misc_target_val, misc_val);
+ } else {
val = sds_mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
ret = regmap_update_bits(priv->map,
RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
@@ -1669,8 +1722,140 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
val &= ~RTL8365MB_SDS_NWAY_EN_MASK;
val |= RTL8365MB_SDS_NWAY_RESTART_MASK;
- return rtl8365mb_sds_write(priv, sds_index,
- RTL8365MB_SDS_REG_NWAY, val);
+ ret = rtl8365mb_sds_write(priv, sds_index,
+ RTL8365MB_SDS_REG_NWAY, val);
+ if (ret)
+ return ret;
+
+ if (is_d) {
+ /* Start a new re-latch episode and kick off the first attempt;
+ * see rtl8365mb_sds_relatch_work() for why this cannot wait
+ * for phylink to ask again.
+ */
+ WRITE_ONCE(mb->sds_relatch_count, 1);
+ schedule_delayed_work(&mb->sds_relatch, 0);
+ }
+
+ return 0;
+}
+
+/* The family D receiver latches on a DISABLE -> mode edge in SDS_MISC
+ * rather than on the value, and only once the far-end MAC has brought up
+ * its half of the link. No local signal predicts that (bit 8 of the SDS
+ * link-status word does not change ahead of it), so the work cannot wait
+ * for it, only check afterwards whether an edge took. Nor does phylink
+ * ever call rtl8365mb_pcs_get_state() for this fixed-link port
+ * (mb->pcs.poll only arms phylink's own poll for in-band links), so this
+ * cannot rely on being polled at all. Instead, pcs_config() starts an
+ * episode and this work re-arms itself after every edge, its next run
+ * checking whether the edge took.
+ *
+ * An episode is capped at RTL8365MB_D_SDS_RELATCH_MAX_TRIES edges, spaced
+ * RTL8365MB_D_SDS_RELATCH_INTERVAL apart, so that a far end which never
+ * comes up costs a bounded burst of 20 ms parks. The run after the last
+ * edge only checks whether it took and reports failure; from then on one
+ * edge is driven per RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL, so a far end
+ * that comes up late is recovered without a reconfiguration. Once the
+ * receiver has latched the work just polls the link at the same slow
+ * interval; finding it down then starts a new episode, e.g. after the far
+ * end reset its PLL without a reconfiguration.
+ *
+ * sds_relatch_count is the number of the next attempt of the episode: 0
+ * while latched, 1..MAX_TRIES in budget, MAX_TRIES + 1 for the run that
+ * only reports, and above that once out of budget.
+ */
+static void rtl8365mb_sds_relatch_work(struct work_struct *work)
+{
+ struct rtl8365mb *mb = container_of(to_delayed_work(work),
+ struct rtl8365mb, sds_relatch);
+ struct realtek_priv *priv = mb->priv;
+ u32 park = (READ_ONCE(mb->sds_misc_target_val) &
+ ~RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK) |
+ RTL8365MB_D_PORT_SDS_MODE_DISABLE;
+ unsigned long delay = RTL8365MB_D_SDS_RELATCH_INTERVAL;
+ unsigned int attempts;
+ u16 status;
+ int ret;
+
+ /* The condition that queued this work may already be stale by the
+ * time it runs (e.g. an earlier edge from a prior attempt just
+ * latched). Parking an already-live link would drop it for no
+ * reason.
+ */
+ ret = rtl8365mb_sds_read(priv, RTL8365MB_D_SDS_EXT0_INDEX,
+ RTL8365MB_SDS_REG_LINK_STATUS, &status);
+ if (ret) {
+ dev_err_ratelimited(priv->dev,
+ "failed to read SerDes link status: %pe\n",
+ ERR_PTR(ret));
+ goto rearm;
+ }
+ if (status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK) {
+ /* Latched: end the episode, keep watching for a later loss. */
+ WRITE_ONCE(mb->sds_relatch_count, 0);
+ delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
+ goto rearm;
+ }
+
+ attempts = READ_ONCE(mb->sds_relatch_count);
+ if (!attempts) {
+ dev_warn(priv->dev,
+ "SerDes link lost outside a reconfiguration; re-latching\n");
+ attempts = 1;
+ } else if (attempts == RTL8365MB_D_SDS_RELATCH_MAX_TRIES + 1) {
+ /* This run only checked whether the last in-budget edge
+ * took. The first out-of-budget edge comes one healthcheck
+ * interval from now, not on this pass.
+ */
+ WRITE_ONCE(mb->sds_relatch_count, attempts + 1);
+ dev_warn(priv->dev,
+ "SerDes did not latch after %u re-latch attempts; still retrying every %d s\n",
+ RTL8365MB_D_SDS_RELATCH_MAX_TRIES,
+ RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL / HZ);
+ delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
+ goto rearm;
+ }
+
+ if (attempts <= RTL8365MB_D_SDS_RELATCH_MAX_TRIES)
+ WRITE_ONCE(mb->sds_relatch_count, attempts + 1);
+ else
+ /* Past the fast-retry budget. The receiver latches only on a
+ * DISABLE -> mode edge and nothing re-runs pcs_config() for a
+ * fixed-link port, so keep driving one edge per interval.
+ */
+ delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
+
+ mutex_lock(&mb->sds_lock);
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_D_SDS_MISC_CFG_MASK, park);
+ mutex_unlock(&mb->sds_lock);
+ if (ret) {
+ dev_err_ratelimited(priv->dev,
+ "failed to park SDS_MISC: %pe\n",
+ ERR_PTR(ret));
+ goto rearm;
+ }
+
+ usleep_range(20000, 21000);
+
+ mutex_lock(&mb->sds_lock);
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_D_SDS_MISC_CFG_MASK,
+ READ_ONCE(mb->sds_misc_target_val));
+ mutex_unlock(&mb->sds_lock);
+ if (ret) {
+ dev_err_ratelimited(priv->dev,
+ "failed to restore SDS_MISC: %pe\n",
+ ERR_PTR(ret));
+ goto rearm;
+ }
+
+ dev_dbg(priv->dev, "SerDes re-latch edge driven (attempt %u)\n",
+ attempts);
+
+ /* This edge may not have taken either; the next run checks. */
+rearm:
+ schedule_delayed_work(&mb->sds_relatch, delay);
}
static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
@@ -1735,7 +1920,9 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
/* The speed and duplex are forced; read them back from the values
* programmed into the SerDes MISC register.
*/
+ mutex_lock(&mb->sds_lock);
ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val);
+ mutex_unlock(&mb->sds_lock);
if (ret) {
state->link = false;
return;
@@ -1805,7 +1992,9 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
* force from rtl8365mb_phylink_mac_link_up(), where the resolved pause
* modes are known.
*/
+ mutex_lock(&mb->sds_lock);
ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val);
+ mutex_unlock(&mb->sds_lock);
if (ret) {
dev_err(priv->dev, "failed to force SerDes link: %pe\n",
ERR_PTR(ret));
@@ -2114,11 +2303,13 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
if (rx_pause)
val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK;
+ mutex_lock(&mb->sds_lock);
ret = regmap_update_bits(priv->map,
RTL8365MB_SDS_MISC_REG,
RTL8365MB_SDS_MISC_SGMII_TXFC_MASK |
RTL8365MB_SDS_MISC_SGMII_RXFC_MASK,
val);
+ mutex_unlock(&mb->sds_lock);
if (ret)
dev_err(priv->dev,
"failed to force SerDes pause modes on port %d: %pe\n",
@@ -3330,6 +3521,10 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
*/
mb->pcs.poll = true;
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ INIT_DELAYED_WORK(&mb->sds_relatch,
+ rtl8365mb_sds_relatch_work);
+
ret = rtl8365mb_reset_chip(priv);
if (ret) {
dev_err(priv->dev, "failed to reset chip: %pe\n",
@@ -3514,6 +3709,10 @@ out_error:
static void rtl8365mb_teardown(struct dsa_switch *ds)
{
struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
+
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ cancel_delayed_work_sync(&mb->sds_relatch);
rtl8365mb_stats_teardown(priv);
rtl8365mb_irq_teardown(priv);
@@ -3584,6 +3783,10 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
else
priv->num_ports = RTL8365MB_MAX_NUM_PORTS;
+ ret = devm_mutex_init(priv->dev, &mb->sds_lock);
+ if (ret)
+ return ret;
+
mb->priv = priv;
mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS;
mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL;
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v2 1/8] net: dsa: realtek: rtl8365mb: detect RTL8367S-VB
2026-10-09 4:53 ` [PATCH net-next v2 1/8] net: dsa: realtek: rtl8365mb: detect RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-10-10 5:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 5:14 UTC (permalink / raw)
To: namiltd
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Not a final-state bug: a later patch in this series fixes it.
- [Low] The file header comment in
drivers/net/dsa/realtek/rtl8365mb_main.c adds RTL8367S-VB to the list…
- [Low] The driver now recognises RTL8367S-VB (chip_id 0x6642, chip_ver
0x0010) under the existing compatible "realtek,rtl8365mb", and its…
--
net: dsa: realtek: rtl8365mb: detect RTL8367S-VB
This adds detection of the RTL8367S-VB (chip_id 0x6642, chip_ver 0x0010).
It introduces a family C / family D split in the chip_info table. The
RTL8367S-VB entry is tagged as family D with SGMII/HSGMII on ext_int 0,
and priv->num_ports is capped at 8 for family D.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index da06f952a053d..80fc551992195 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
> @@ -81,6 +81,7 @@
> * - RTL8367RB-VB
> * - RTL8367SB
> * - RTL8367S
> + * - RTL8367S-VB
> * - RTL8370MB
> * - RTL8310SR
> *
[Severity: Low]
This isn't a bug, but the header comment now puts RTL8367S-VB in the list
introduced by "According to documentation from Realtek, the family should
include the following chips". That list describes the single RTL8367C
family the driver targets.
The same comment block also separates out chips that "require the rtl8367d
vendor driver". The new chip_info entry tags RTL8367S-VB as
RTL8365MB_FAMILY_D, so the comment and the code disagree about the
chip's family.
The comment above RTL8365MB_LEARN_LIMIT_MAX also still describes one
family:
/* Valid for the whole family except RTL8370B, which has 4160 entries.
* RTL8370B is mentioned in vendor code but it might not even belong
* to the same RTL8367C family.
*/
The 2112 limit now applies to family D chips as well.
Could these comments be updated to cover the family D split? The mismatch
is still there at the end of the series.
[ ... ]
> @@ -777,6 +786,19 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
> .jam_table = rtl8365mb_init_jam_8365mb_vc,
> .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
> },
> + {
> + .name = "RTL8367S-VB",
> + .chip_id = 0x6642,
> + .chip_ver = 0x0010,
> + .family = RTL8365MB_FAMILY_D,
> + .extints = {
> + { 6, 0, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
[Severity: Medium]
On RTL8367S-VB the SerDes sits on ext_int id 0 (port 6) and RGMII sits on
id 1 (port 7). Is rtl8365mb_pcs_config() still correct for this layout at
this point in the series? It hardcodes the family C interface ID:
#define RTL8365MB_SDS_EXT_INTERFACE_ID 1
rtl8365mb_pcs_config() {
const int id = RTL8365MB_SDS_EXT_INTERFACE_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),
val);
...
}
RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 (0x1305) holds EXT0 in bits 3:0
and EXT1 in bits 7:4. On family D this write would therefore put the
SGMII/HSGMII mode into the nibble for port 7's RGMII interface.
rtl8365mb_ext_config_forcemode() works from a different interface. It
forces the link through RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id),
and extint->id is 0 here.
rtl8365mb_sds_probe_option() doesn't check the family either. If the
family C option register reads non-zero, sds_supported gets set and
phylink can pick SGMII or 2500base-x on port 6:
rtl8365mb_setup()
rtl8365mb_sds_probe_option()
mb->sds_supported = true;
...
rtl8365mb_pcs_config()
regmap_update_bits(..., DIGITAL_INTERFACE_SELECT_REG(1), ...)
Could this break port 7 RGMII, or leave the SerDes mode unset on
RTL8367S-VB?
A later patch in the series, "net: dsa: realtek: rtl8365mb: add family D
SDS13 PCS support", appears to fix this. It adds a family D branch to
rtl8365mb_pcs_config() that skips the DIGITAL_INTERFACE_SELECT write, and
it makes rtl8365mb_sds_probe_option() return early for family D.
Could SerDes modes stay disabled for family D in this patch, so the
intermediate commits remain bisectable?
> + { 7, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
> + PHY_INTF(RMII) | PHY_INTF(RGMII) },
> + },
> + .jam_table = rtl8365mb_init_jam_8365mb_vc,
> + .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
> + },
> };
[Severity: Low]
The driver now matches RTL8367S-VB under the existing realtek,rtl8365mb
compatible. Documentation/devicetree/bindings/net/dsa/realtek.yaml wasn't
updated, though, and its model list still reads:
realtek,rtl8365mb:
Use with models RTL8363NB, RTL8363NB-VB, RTL8363SC, RTL8363SC-VB,
RTL8364NB, RTL8364NB-VB, RTL8365MB, RTL8366SC, RTL8367RB-VB, RTL8367S,
RTL8367SB, RTL8370MB, RTL8310SR
Should RTL8367S-VB be added to that list? None of the later patches in
the series update the binding.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/28382f3f-03c1-4606-9b11-86f118abeafe%40yahoo.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v2 2/8] net: dsa: realtek: rtl8365mb: set speed for family D
2026-10-09 4:54 ` [PATCH net-next v2 2/8] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
@ 2026-10-10 5:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 5:14 UTC (permalink / raw)
To: namiltd
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] At this commit, the family D 2.5G MAC force encoding
(RTL8365MB_D_PORT_SPEED_2500M, split across FORCE_SPEED bits[1:0] and…
--
net: dsa: realtek: rtl8365mb: set speed for family D
This patch makes rtl8365mb_ext_config_forcemode() use the family D
2500M speed code (5). The code is split across FORCE_SPEED bits[1:0] and
a new FORCE_SPEED2 field. On family D the value goes to a per-port force
register at 0x12c0 + port, with an enable register at 0x12c8 + port.
Family C keeps programming 1000M for 2.5G.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 80fc551992195..b1ea8b06a1a18 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -1661,23 +1676,31 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
[ ... ]
> + is_d = rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D;
> if (link) {
> /* Force the link up with the desired configuration */
> r_link = 1;
> r_rx_pause = rx_pause ? 1 : 0;
> r_tx_pause = tx_pause ? 1 : 0;
>
> - /* 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) {
> + if (speed == SPEED_2500) {
> + if (is_d) {
> + r_speed = RTL8365MB_D_PORT_SPEED_2500M;
[Severity: Medium]
At this point in the series, does the family D 2500M MAC force match
what the PCS side programs for the same link?
At this commit the PCS path still only handles family C.
rtl8365mb_pcs_config() uses the hardcoded ext id, the family C jam
tables and the MAC8 mux:
const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
...
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;
rtl8365mb_pcs_link_up() also still writes the family C value (1000M for
2.5G) into SDS_MISC:
if (speed == SPEED_2500 || speed == SPEED_1000) {
r_speed = RTL8365MB_PORT_SPEED_1000M;
The RTL8367S-VB chip_info entry puts the SerDes on port 6 with ext id 0:
{ 6, 0, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
RTL8365MB_SDS_EXT_INTERFACE_ID is 1.
If sds_supported is set on an RTL8367S-VB, rtl8365mb_phylink_get_caps()
advertises 2500BASEX. mac_select_pcs() then returns &mb->pcs, so the
family C pcs_config() and pcs_link_up() run. Meanwhile
rtl8365mb_ext_config_forcemode() writes RTL8365MB_D_PORT_SPEED_2500M.
Would a 2500BASE-X link on family D come up at this commit?
A later patch in the series, "net: dsa: realtek: rtl8365mb: add family
D SDS13 PCS support", appears to fix this. It adds an is_d path to
rtl8365mb_pcs_config(), and rtl8365mb_pcs_link_up() returns early on
family D. The family C PCS handling on family D was added by "net: dsa:
realtek: rtl8365mb: detect RTL8367S-VB", not by this patch.
Could the series be reordered, or 2500BASEX held back for family D until
the PCS support lands, so this in-between state can't be reached?
> + } else {
> + /* 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.
> + */
> + r_speed = RTL8365MB_PORT_SPEED_1000M;
> + }
> + } else if (speed == SPEED_1000) {
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/28382f3f-03c1-4606-9b11-86f118abeafe%40yahoo.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v2 3/8] net: dsa: realtek: rtl8365mb: get pvid for family D
2026-10-09 4:55 ` [PATCH net-next v2 3/8] net: dsa: realtek: rtl8365mb: get pvid " Mieczyslaw Nalewaj
@ 2026-10-10 5:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 5:14 UTC (permalink / raw)
To: namiltd
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] At this commit only rtl8365mb_vlan_port_get_pvid() is converted
to the family D layout.
--
net: dsa: realtek: rtl8365mb: get pvid for family D
On family D chips, this commit changes rtl8365mb_vlan_port_get_pvid() to
read the PVID directly from a per-port register at 0x0700 + port, taking
bits [11:0]. Family C keeps the old lookup through the VLAN MC table
index.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> index 8d49ffadce926..7add1fb1bea19 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
[ ... ]
> @@ -679,11 +685,22 @@ int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid)
> u8 vlanmc_idx;
> int ret;
>
> - ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc);
> - if (ret)
> - return ret;
> + if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {
> + u32 data;
> +
> + ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port), &data);
[Severity: Low]
At this commit, do the family D reader and writers use different register
layouts?
The new branch reads 0x0700 + port and treats bits [11:0] as a VID. The
writers are not changed, so on family D they still use the family C
layout. rtl8365mb_vlan_port_set_pvid() writes a 5-bit MC index into
0x0700 + (port >> 1):
drivers/net/dsa/realtek/rtl8365mb_vlan.c:rtl8365mb_vlan_port_set_pvid() {
...
val = vlanmc_idx << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port);
ret = regmap_update_bits(priv->map,
RTL8365MB_VLAN_PVID_CTRL_REG(port),
RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port),
val);
...
}
rtl8365mb_vlan_pvid_port_set() and rtl8365mb_vlan_pvid_port_clear() also
still read the old state through rtl8365mb_vlan_get_pvid_mc(). So on
family D at this commit, reading the PVID back after
rtl8365mb_vlan_pvid_port_set() would not return the VID that was set.
That affects callers such as rtl8365mb_port_vlan_filtering() in
rtl8365mb_main.c.
This is resolved later in the series. The commit "net: dsa: realtek:
rtl8365mb: set/clear pvid for family D" adds family D dispatches at the
top of rtl8365mb_vlan_pvid_port_set() and rtl8365mb_vlan_pvid_port_clear().
Those dispatches go to rtl8365mb_vlan_pvid_port_set_direct() and
rtl8365mb_vlan_pvid_port_clear_direct(), which use the same
RTL8365MB_D_VLAN_PVID_CTRL_REG(port) / GENMASK(11, 0) layout as this
getter.
Also, the family D chip (ID 0x6642) only starts probing with "net: dsa:
realtek: rtl8365mb: detect RTL8367S-VB" later in the series. So no
existing setup is affected.
Could the getter and the set/clear conversion be merged into one commit,
or reordered? That would keep the family D read and write paths
consistent at every point in the series.
> + if (ret)
> + return ret;
> +
> + *pvid = data & RTL8365MB_D_VLAN_PVID_CTRL_MASK;
> + } else {
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/28382f3f-03c1-4606-9b11-86f118abeafe%40yahoo.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v2 6/8] net: dsa: realtek: rtl8365mb: set/clear pvid for family D
2026-10-09 4:59 ` [PATCH net-next v2 6/8] net: dsa: realtek: rtl8365mb: set/clear pvid " Mieczyslaw Nalewaj
@ 2026-10-10 5:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 5:14 UTC (permalink / raw)
To: namiltd
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The commit message and the new kernel-doc are inaccurate in two
places.
- [Low] Several comments are stale after this patch.
--
net: dsa: realtek: rtl8365mb: set/clear pvid for family D
This patch adds a direct-VID path to rtl8365mb_vlan_pvid_port_set() and
rtl8365mb_vlan_pvid_port_clear() for RTL8365MB_FAMILY_D chips. On those
chips, the PVID is written as a raw 12-bit VID to a per-port register, and
the VLAN MC table is skipped entirely.
> The RTL8367S-VB (family D) has no working VLAN member-config (MC)
> table in hardware. rtl8365mb_vlan_port_get_pvid() was already fixed
> to read PVID directly from its dedicated per-port register, but
> rtl8365mb_vlan_pvid_port_set() and rtl8365mb_vlan_pvid_port_clear()
> still went through the MC-index allocation path meant for family C,
> which on family D hardware writes into the wrong register/field
> (colliding with the neighbouring port's real PVID register) and reads
> back a VLAN MC table that does not exist on this silicon.
[Severity: Low]
Is "the neighbouring port's real PVID register" accurate for all ports?
The family-C macros map port p like this, with a 5-bit field at bit offset
((_p) & 1) << 3:
#define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \
(RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1))
On family D, 0x0700 + q is port q's own 12-bit VID register:
#define RTL8365MB_D_VLAN_PVID_CTRL_REG(port) \
(RTL8365MB_D_VLAN_PVID_CTRL_BASE + (port))
So a family-C write for port p lands in the VID field of port (p >> 1).
Port 0 corrupts its own VID. Only ports 1 and 2 hit a neighbour (ports 0
and 1). Port 3 hits port 1, ports 4 and 5 hit port 2, and ports 6 and 7
hit port 3.
Could the commit message say that port p's write lands in port
(p >> 1)'s PVID register?
Separately, the new kernel-doc for rtl8365mb_vlan_pvid_port_set_direct()
says:
* the rollback behavior of the family-C implementation above.
The family-C rtl8365mb_vlan_pvid_port_set() is defined below this helper,
though, not above it.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> index 0466e8cbc4123..da517b1aae208 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> @@ -810,6 +810,64 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
[ ... ]
> /*
> * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated
> * VLANMC entry
> @@ -839,6 +897,13 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
>
> lockdep_assert_held(&priv->vlan_lock);
>
> + /* This chip family has no VLAN MC table - PVID is a raw VID in a
> + * dedicated per-port register, and there is no separate membership
> + * table entry to allocate/track.
> + */
> + if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
> + return rtl8365mb_vlan_pvid_port_set_direct(priv, port, vid);
> +
[Severity: Low]
With this early return in place, should the kernel-doc for
rtl8365mb_vlan_pvid_port_set() be updated too? It still says:
* rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated
* VLANMC entry
...
* Allocates or reuses a hardware VLANMC entry to map the given port to its new
* PVID.
On RTL8365MB_FAMILY_D, rtl8365mb_vlan_pvid_port_set_direct() writes a raw
VID to RTL8365MB_D_VLAN_PVID_CTRL_REG(port) and never touches the VLANMC
table.
The same applies to rtl8365mb_vlan_pvid_port_clear(). Its kernel-doc still
says:
* Resets the target port's hardware PVID allocation to 0. Cleans up and frees
* the associated VLANMC entry if no other ports are referencing it.
The new family D branch calls rtl8365mb_vlan_pvid_port_clear_direct()
instead, which does no VLANMC management.
The file header comment also still says, without naming a family:
* programming of a port-based VLAN ID (PVID). The PVID is programmed on a
* per-port basis via register field, which refers to a specific VLAN membership
* configuration via an index 0~31.
...
* VLANMC index 0 is reserved as a neutral PVID, used for standalone ports.
The struct rtl8365mb_vlanmc kernel-doc also still lists "PVID
configuration" as one of the remaining uses of the VLAN membership
configuration database.
Could these comments mention the family D raw-VID path? The "above"
reference in the rtl8365mb_vlan_pvid_port_set_direct() kernel-doc, noted
earlier, is the same kind of stale comment.
> /* Read the old PVID exclusively to undo in case of error */
> ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx,
> &prev_vlanmc);
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/28382f3f-03c1-4606-9b11-86f118abeafe%40yahoo.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v2 7/8] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
2026-10-09 5:00 ` [PATCH net-next v2 7/8] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
@ 2026-10-10 5:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 5:14 UTC (permalink / raw)
To: namiltd
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] On family D (RTL8367S-VB), the SerDes path skips every SerDes-
side speed, duplex and pause step, but phylink is still told the port…
- [Medium] In this commit, the family D `rtl8365mb_pcs_config()` writes
the SDS_MISC mode field (bits 4:0) straight to SGMII (0x02) or HSGMII…
- [Low] The `struct rtl8365mb` kernel-doc says `@sds_supported` means
"SerDes tuning parameters match the chip option, so the SerDes…
- [Low] Several comments in the SerDes PCS/MAC path still say the SerDes
link parameters are forced, or reprogrammed, in…
--
net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
This commit extends the RTL8367S SGMII/HSGMII PCS code to family D
switches, whose CPU SerDes is reached through the indexed SDS13 window.
It adds family D tuning tables and SDS_MISC encodings. The family D path
is chosen from the chip-family metadata, and family C is left unchanged.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 3819a48386006..4cad90f5c0181 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -1398,6 +1435,14 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
> int ret;
> int i;
>
> + /* Family D has a fixed SDS13 programming model and does not use the
> + * family C option register to select its tuning table.
> + */
> + if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {
> + mb->sds_supported = true;
> + return 0;
> + }
> +
> /* 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];
[Severity: Low]
This isn't a bug, but this early return skips both the extint scan ("Nothing
to probe if no external interface is wired to the SerDes") and the option
register check. The kernel-doc for struct rtl8365mb still says:
* @sds_supported: SerDes tuning parameters match the chip option, so the
* SerDes interface modes can be advertised
Should the kernel-doc be updated for family D?
rtl8365mb_setup() also calls rtl8365mb_sds_raise_rate_limits() whenever
sds_supported is set. As a result, every family D chip gets its port 6
ingress and egress rate limits rewritten.
RTL8367S-VB does have an SGMII/HSGMII extint on port 6, so nothing changes
today. The commit message says this code can be reused by other family D
boards, though. Would it make sense to keep the extint scan ahead of the
family D shortcut?
[ ... ]
> @@ -1526,34 +1590,53 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
>
> /* Tune the SerDes with vendor-prescribed parameters */
> for (i = 0; i < sds_jam_size; i++) {
> - ret = rtl8365mb_sds_write(priv, sds_jam[i].reg,
> - sds_jam[i].val);
> + ret = rtl8365mb_sds_write(priv, sds_index,
> + sds_jam[i].reg, sds_jam[i].val);
> + if (ret)
> + return ret;
> + }
> +
> + /* Family-specific post-tuning configuration */
> + if (is_d) {
> + ret = regmap_update_bits(priv->map, RTL8365MB_D_FIBER_CFG2_REG,
> + RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK,
> + RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0);
> if (ret)
> return ret;
> +
> + misc_mask = RTL8365MB_D_SDS_MISC_CFG_MASK;
> + misc_val = RTL8365MB_D_SDS_MISC_PA33PC_EN |
> + RTL8365MB_D_SDS_MISC_PA12PC_EN |
> + RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 | sds_mode;
> + } else {
[ ... ]
> ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
> - RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
> - RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,
> - mode == RTL8365MB_EXT_PORT_MODE_SGMII ?
> - RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
> - RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK);
> + misc_mask, misc_val);
> if (ret)
> return ret;
[Severity: Medium]
On family D, does this write give the SerDes receiver a mode edge to latch
on?
The mode field (bits 4:0) is written straight to SGMII (0x02) or HSGMII
(0x12). It is never parked at RTL8365MB_D_PORT_SDS_MODE_DISABLE (0x1f)
first.
The register may already hold the target value, either from the bootloader
or from an earlier pcs_config() with the same interface. In that case
regmap_update_bits() writes nothing. Even when the write does happen, it can
land before the far-end MAC is up.
At this point in the series, could the CPU-facing trunk then report link up
on both sides but pass no frames?
The later patch "net: dsa: realtek: rtl8365mb: re-latch the family D SerDes"
seems to fix this with rtl8365mb_sds_relatch_work(). That work parks
SDS_MISC at DISABLE, sleeps 20 ms, restores the target, and retries until
the link status bit is set. Could that logic be folded into this patch, so
this commit works on its own when bisecting?
[ ... ]
> @@ -1576,14 +1661,16 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
> /* 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);
> + ret = rtl8365mb_sds_read(priv, sds_index,
> + RTL8365MB_SDS_REG_NWAY, &val);
[Severity: Low]
This isn't a bug, but this comment is no longer accurate for family D,
because rtl8365mb_pcs_link_up() now returns right away there. Several other
comments say the same thing and are unchanged.
In rtl8365mb_pcs_get_state():
/* In-band autonegotiation is not implemented, so the link parameters are
* forced from rtl8365mb_pcs_link_up(). ...
In rtl8365mb_phylink_mac_link_up():
/* 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.
*/
In rtl8365mb_phylink_mac_link_down():
* enough to bring the link down; the SerDes keeps presenting its last
* forced state until the next pcs_link_up() reprograms it.
The comment above RTL8365MB_SDS_EXT_INTERFACE_ID also says:
/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
* which is port 6.
*/
However, the family D chip_info puts the SerDes on extint 0 (MAC6, through
SDS13).
Could these comments be updated to cover the family D path too?
[ ... ]
> @@ -1630,6 +1725,13 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
> if (!state->link)
> return;
>
> + if (is_d) {
> + state->duplex = DUPLEX_FULL;
> + state->speed = state->interface == PHY_INTERFACE_MODE_2500BASEX ?
> + SPEED_2500 : SPEED_1000;
> + return;
> + }
> +
> /* The speed and duplex are forced; read them back from the values
> * programmed into the SerDes MISC register.
> */
> @@ -1671,6 +1773,12 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
> u32 r_speed;
> int ret;
>
> + /* Family D forces the external MAC ability from mac_link_up(); its
> + * SDS_MISC fields do not share the family C link-force layout.
> + */
> + if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
> + return;
> +
[Severity: Medium]
With this early return, does anything on family D program the SerDes for
10/100 or half duplex?
The family D SDS_MISC fields added here (PA33PC_EN, PA12PC_EN, MAC6_SEL_SDS0
and the 5-bit mode) have no speed or duplex field. pcs_config() also clears
NWAY, so no in-band code word carries the rate either. If the family D MAC
force register alone is enough for these modes, could a comment say so?
Pause has the same gap. The rtl8365mb_phylink_mac_link_up() hunk later in
this patch skips the SDS_MISC TXFC/RXFC write on family D:
if (rtl8365mb_interface_is_serdes(interface) &&
rtl8365mb_get_family(priv) != RTL8365MB_FAMILY_D) {
Nothing replaces that write. The comment just above it says these SerDes
bits, "not the MAC force pause bits, gate pause on the SerDes external
interface".
The family D branch in rtl8365mb_pcs_get_state() above always reports
DUPLEX_FULL, plus SPEED_1000 or SPEED_2500.
When in-band AN is disabled, phylink_mac_pcs_get_state() fills in
state->speed and state->duplex from the forced link_config before it calls
pcs_get_state(). rtl8365mb_pcs_inband_caps() always returns
LINK_INBAND_DISABLE.
Take managed = "in-band-status" with 100/half forced through ethtool. Would
this branch replace those values with 1000/full? That would affect both what
ethtool reports and what the next mac_link_up() forces into the MAC.
Meanwhile, rtl8365mb_phylink_get_caps() still advertises:
config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000FD;
rtl8365mb_sds_probe_option() now sets sds_supported for every family D
chip. That means SGMII and 2500base-X are offered on the RTL8367S-VB port 6
extint.
Should the family D SerDes port limit its mac_capabilities to what this path
actually handles? Or should the commit message explain these limits? Right
now it only says the "SDS_MISC fields differ from family C", and this
appears unchanged at the end of the series.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/28382f3f-03c1-4606-9b11-86f118abeafe%40yahoo.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v2 8/8] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes
2026-10-09 5:02 ` [PATCH net-next v2 8/8] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes Mieczyslaw Nalewaj
@ 2026-10-10 5:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 5:14 UTC (permalink / raw)
To: namiltd
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Nothing ties the lifetime or scope of the family D sds_relatch
work to phylink.
- [Medium] The shutdown path never cancels sds_relatch.
- [Low] Two statements in the commit message do not match the code.
- [Low] Series ordering: the family D SerDes PCS support added by the
previous commit in this series ('net: dsa: realtek: rtl8365mb: add…
- [Low] If the park write in rtl8365mb_sds_relatch_work() succeeds but
the restore write fails, the SDS_MISC mode field is left at DISABLE…
--
net: dsa: realtek: rtl8365mb: re-latch the family D SerDes
This patch adds a delayed work for family D chips. The work drives
DISABLE -> mode edges on SDS_MISC until the SerDes receiver latches, then
re-checks the link every 10 seconds, re-arming itself each time. It also
adds an sds_lock mutex around the SDS_INDACS indirect access window and the
SDS_MISC accesses, and cancels the work in pcs_config() and teardown().
> The family D receiver latches on the DISABLE -> HSGMII edge in SDS_MISC
> rather than on the value, and it will not latch until the MAC at the
> other end of the trunk has brought its own half of the link up. If it
> does not latch, the trunk reports 2.5Gbps/Full on both sides but moves
> no frames: the CPU MAC transmits, the switch CPU port counts no octets,
> and there are no CRC, alignment or FIFO errors.
[Severity: Low]
Does this mean the previous patch in the series ("net: dsa: realtek:
rtl8365mb: add family D SDS13 PCS support") leaves a family D 2500base-x
CPU trunk that reports link up but passes no frames?
Family D support is new in this series, so this is not a regression of
existing behaviour. However, a bisect that lands between the two commits
would get a non-working CPU port on family D boards.
Could the two be folded together, or could the dependency at least be
mentioned in the commit message?
[ ... ]
> Each attempt (park for 20 ms, then restore the target mode) is
> performed by a short work: sleeping is not permitted in the PCS ops,
> and the work also serializes against pcs_config() re-runs, which
> cancel it.
[ ... ]
> The work now accesses the shared SDS_INDACS ADR/CMD/DATA window and
> SDS_MISC concurrently with the PCS and MAC link_up paths and
> pcs_get_state(), so those accesses are serialized by a new sds_lock.
[Severity: Low]
Are these two statements accurate?
PCS ops run in sleepable process context under phylink's state_mutex.
rtl8365mb_pcs_config() already sleeps in its regmap I/O and in the
regmap_read_poll_timeout() in rtl8365mb_sds_read(). This patch also adds
cancel_delayed_work_sync() and mutex_lock() calls to pcs_config(). The
real reason for the work item looks like the need to keep retrying for
seconds after pcs_config() returns.
On family D, rtl8365mb_pcs_link_up() returns before it touches SDS_MISC:
if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
return;
The SDS_MISC update in rtl8365mb_phylink_mac_link_up() is guarded by:
if (rtl8365mb_interface_is_serdes(interface) &&
rtl8365mb_get_family(priv) != RTL8365MB_FAMILY_D) {
The SDS_MISC read in rtl8365mb_pcs_get_state() comes after the is_d early
return. All three accesses therefore run only on family C, where the work
is never initialised.
Only the SDS_INDACS window and the pcs_config() SDS_MISC update are shared
with the work. Could the commit message say that instead?
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 4cad90f5c0181..eb71c89945c16 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -1669,8 +1722,140 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
> val &= ~RTL8365MB_SDS_NWAY_EN_MASK;
> val |= RTL8365MB_SDS_NWAY_RESTART_MASK;
>
> - return rtl8365mb_sds_write(priv, sds_index,
> - RTL8365MB_SDS_REG_NWAY, val);
> + ret = rtl8365mb_sds_write(priv, sds_index,
> + RTL8365MB_SDS_REG_NWAY, val);
> + if (ret)
> + return ret;
> +
> + if (is_d) {
> + /* Start a new re-latch episode and kick off the first attempt;
> + * see rtl8365mb_sds_relatch_work() for why this cannot wait
> + * for phylink to ask again.
> + */
> + WRITE_ONCE(mb->sds_relatch_count, 1);
> + schedule_delayed_work(&mb->sds_relatch, 0);
> + }
[Severity: Medium]
Is the lifetime of this work tied to phylink in any way?
The work is scheduled for every family D SerDes pcs_config(), whatever the
interface (SGMII or 2500base-x) and neg_mode.
rtl8365mb_phylink_mac_config() accepts both MLO_AN_PHY and MLO_AN_FIXED.
Once started, the work re-arms itself on every exit through the rearm
label.
Only the next pcs_config() or rtl8365mb_teardown() cancels it.
rtl8365mb_pcs_ops has no .pcs_disable, so the phylink_pcs_disable() call
in phylink_stop() (and in phylink_major_config() when the PCS changes) has
no effect here. rtl8365mb_phylink_mac_link_down() only cancels
p->mib_work.
As an example, take a family D SerDes port wired as a user port to an
SGMII PHY, and set the port down. Would the work then keep reading the SDS
link status and log "SerDes link lost outside a reconfiguration"? It would
then park SDS_MISC for 20 ms every second for 15 seconds, log "SerDes did
not latch", and park every 10 seconds for as long as the port stays down.
The comment above rtl8365mb_sds_relatch_work() refers to "this fixed-link
port", and the work itself says "nothing re-runs pcs_config() for a
fixed-link port". Nothing in the code checks for a fixed link, though. The
commit message also only justifies the DISABLE -> mode edge for HSGMII on
a fixed-link CPU trunk.
Is the same behaviour correct for SGMII and for PHY-mode ports? Should the
work also be stopped from a .pcs_disable callback or from the link-down
path?
[ ... ]
> + mutex_lock(&mb->sds_lock);
> + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
> + RTL8365MB_D_SDS_MISC_CFG_MASK, park);
> + mutex_unlock(&mb->sds_lock);
> + if (ret) {
> + dev_err_ratelimited(priv->dev,
> + "failed to park SDS_MISC: %pe\n",
> + ERR_PTR(ret));
> + goto rearm;
> + }
> +
> + usleep_range(20000, 21000);
> +
> + mutex_lock(&mb->sds_lock);
> + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
> + RTL8365MB_D_SDS_MISC_CFG_MASK,
> + READ_ONCE(mb->sds_misc_target_val));
> + mutex_unlock(&mb->sds_lock);
> + if (ret) {
> + dev_err_ratelimited(priv->dev,
> + "failed to restore SDS_MISC: %pe\n",
> + ERR_PTR(ret));
> + goto rearm;
> + }
[Severity: Low]
If the park write succeeds but this restore write fails, is the SDS_MISC
mode field left at DISABLE with nothing recording that?
The next run reads the link status bit and sees 0, because the SerDes is
disabled. It treats that as the far end not being up yet. If the failure
happened on the last in-budget attempt, the next run is the report-only
pass:
} else if (attempts == RTL8365MB_D_SDS_RELATCH_MAX_TRIES + 1) {
WRITE_ONCE(mb->sds_relatch_count, attempts + 1);
dev_warn(priv->dev,
"SerDes did not latch after %u re-latch attempts; ...
delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
goto rearm;
}
That pass does not write SDS_MISC. The SerDes would then stay disabled for
about 11 seconds, and the log message would blame the far end.
If rtl8365mb_teardown() cancels the work in this window, nothing restores
the target mode at all.
Would it make sense to retry the restore here, or re-arm with a short
delay?
[ ... ]
> @@ -3514,6 +3709,10 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
> static void rtl8365mb_teardown(struct dsa_switch *ds)
> {
> struct realtek_priv *priv = ds->priv;
> + struct rtl8365mb *mb = priv->chip_data;
> +
> + if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
> + cancel_delayed_work_sync(&mb->sds_relatch);
>
> rtl8365mb_stats_teardown(priv);
> rtl8365mb_irq_teardown(priv);
[Severity: Medium]
Is this the only place the work gets cancelled at end of life?
Once pcs_config() has run, the work always re-arms itself. The .shutdown
path never reaches rtl8365mb_teardown():
realtek_{mdio,smi}_shutdown()
rtl83xx_shutdown()
dsa_switch_shutdown(ds);
dev_set_drvdata(priv->dev, NULL);
dsa_switch_shutdown() only closes the conduits and unlinks the uppers. It
does not call ds->ops->teardown. Because drvdata has been cleared, a later
realtek_mdio_remove() returns early:
if (!priv)
return;
The rtl83xx_shutdown() kernel-doc says this can happen if the parent bus
implements its own .shutdown() as .remove(). In that case devres frees
priv while sds_relatch is still armed. mb is freed with it, because
rtl83xx_probe() allocates chip_data in the same devm_kzalloc() block, and
sds_lock is devm_mutex_init()'d.
Can the next run of rtl8365mb_sds_relatch_work() then dereference the
freed mb->priv, mb->sds_lock and priv->map?
Even without an unbind, the work keeps issuing SDS reads and SDS_MISC
park/restore writes over MDIO/SMI after the switch has been shut down for
reboot.
The CPU port mib_work already has the same gap at shutdown. This patch
adds a second work that re-arms itself indefinitely and also writes
hardware state.
Should the shutdown path cancel sds_relatch as well?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/28382f3f-03c1-4606-9b11-86f118abeafe%40yahoo.com
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-10-10 5:14 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <28382f3f-03c1-4606-9b11-86f118abeafe.ref@yahoo.com>
2026-10-09 4:51 ` [PATCH net-next v2 0/8] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-10-09 4:53 ` [PATCH net-next v2 1/8] net: dsa: realtek: rtl8365mb: detect RTL8367S-VB Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 4:54 ` [PATCH net-next v2 2/8] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 4:55 ` [PATCH net-next v2 3/8] net: dsa: realtek: rtl8365mb: get pvid " Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 4:56 ` [PATCH net-next v2 4/8] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
2026-10-09 4:58 ` [PATCH net-next v2 5/8] net: dsa: realtek: rtl8365mb: set and get vlan 4k " Mieczyslaw Nalewaj
2026-10-09 4:59 ` [PATCH net-next v2 6/8] net: dsa: realtek: rtl8365mb: set/clear pvid " Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 5:00 ` [PATCH net-next v2 7/8] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
2026-10-09 5:02 ` [PATCH net-next v2 8/8] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes Mieczyslaw Nalewaj
2026-10-10 5:14 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).