* [PATCH net-next v3 1/9] dt-bindings: net: dsa: realtek: add RTL8367S-VB
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-10-10 19:38 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-10 19:39 ` [PATCH net-next v3 2/9] net: dsa: realtek: rtl8365mb: add chip family infrastructure Mieczyslaw Nalewaj
` (7 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:38 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel
The RTL8367S-VB (chip ID 0x6642) is supported by the rtl8365mb driver.
Like the other models in the family, it is identified by its chip ID
register, so it does not need a compatible string of its own. List it
under realtek,rtl8365mb.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
Documentation/devicetree/bindings/net/dsa/realtek.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/dsa/realtek.yaml b/Documentation/devicetree/bindings/net/dsa/realtek.yaml
index 473facd..ca775f6 100644
--- a/Documentation/devicetree/bindings/net/dsa/realtek.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/realtek.yaml
@@ -43,7 +43,7 @@ properties:
realtek,rtl8365mb:
Use with models RTL8363NB, RTL8363NB-VB, RTL8363SC, RTL8363SC-VB,
RTL8364NB, RTL8364NB-VB, RTL8365MB, RTL8366SC, RTL8367RB-VB, RTL8367S,
- RTL8367SB, RTL8370MB, RTL8310SR
+ RTL8367S-VB, RTL8367SB, RTL8370MB, RTL8310SR
realtek,rtl8366rb:
Use with models RTL8366RB, RTL8366S
--
2.53.0
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 1/9] dt-bindings: net: dsa: realtek: add RTL8367S-VB
2026-10-10 19:38 ` [PATCH net-next v3 1/9] dt-bindings: net: dsa: realtek: add RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, 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] This patch adds RTL8367S-VB to the list of models under the
realtek,rtl8365mb compatible in…
--
dt-bindings: net: dsa: realtek: add RTL8367S-VB
This patch adds RTL8367S-VB to the list of models in the description of
the realtek,rtl8365mb compatible in the realtek DSA binding. Only the
description text changes. The schema constraints stay the same.
> The RTL8367S-VB (chip ID 0x6642) is supported by the rtl8365mb driver.
[Severity: Low]
Is this statement accurate at this point in the series?
At this commit, every entry in rtl8365mb_chip_infos[] uses chip_id 0x6367.
rtl8365mb_detect() also needs an exact match:
drivers/net/dsa/realtek/rtl8365mb_main.c:rtl8365mb_detect() {
...
if (ci->chip_id == chip_id && ci->chip_ver == chip_ver) {
...
if (!mb->chip_info) {
dev_err(priv->dev, "unrecognized switch (id=0x%04x, ver=0x%04x)", chip_id, chip_ver);
return -ENODEV;
}
...
}
So a switch with chip ID 0x6642 still fails to probe with -ENODEV until a
later patch in the series, "net: dsa: realtek: rtl8365mb: add support for
RTL8367S-VB", adds the table entry:
.name = "RTL8367S-VB",
.chip_id = 0x6642,
.chip_ver = 0x0010,
.family = RTL8365MB_FAMILY_D,
With the whole series applied, the binding text and the driver agree, and
nothing regresses at runtime. Could the commit message say that this series
adds the support, rather than describing it as already present?
> Like the other models in the family, it is identified by its chip ID
> register, so it does not need a compatible string of its own.
A small wording point too. Later in the series, rtl8365mb_main.c gets this
header comment:
* The RTL8367S-VB (chip ID 0x6642) is not part of the family listed above. It
* belongs to a newer generation, which this driver calls family D.
Should "the other models in the family" be reworded to match that comment?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next v3 2/9] net: dsa: realtek: rtl8365mb: add chip family infrastructure
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-10-10 19:38 ` [PATCH net-next v3 1/9] dt-bindings: net: dsa: realtek: add RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-10-10 19:39 ` Mieczyslaw Nalewaj
2026-10-10 19:39 ` [PATCH net-next v3 3/9] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
` (6 subsequent siblings)
8 siblings, 0 replies; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:39 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel
Add enum rtl8365mb_family and a family member to struct
rtl8365mb_chip_info, together with rtl8365mb_get_family() declared in a
new rtl8365mb.h so that the VLAN code can use it too. Tag all existing
chips as RTL8365MB_FAMILY_C.
RTL8365MB_FAMILY_D is defined here but no chip uses it yet; it is needed
by the following patches, which add family D handling for chips whose
register layout differs from the existing ones.
No functional change.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb.h | 14 ++++++++++++++
drivers/net/dsa/realtek/rtl8365mb_main.c | 14 ++++++++++++++
2 files changed, 28 insertions(+)
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..840db5a 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -110,6 +110,7 @@
#include "rtl83xx.h"
#include "rtl8365mb_l2.h"
#include "rtl8365mb_vlan.h"
+#include "rtl8365mb.h"
/* Family-specific data and limits */
#define RTL8365MB_PHYADDRMAX 7
@@ -707,6 +708,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 +721,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 +734,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 +746,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 +759,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 +774,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) },
@@ -874,6 +881,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;
--
2.53.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH net-next v3 3/9] net: dsa: realtek: rtl8365mb: set speed for family D
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-10-10 19:38 ` [PATCH net-next v3 1/9] dt-bindings: net: dsa: realtek: add RTL8367S-VB Mieczyslaw Nalewaj
2026-10-10 19:39 ` [PATCH net-next v3 2/9] net: dsa: realtek: rtl8365mb: add chip family infrastructure Mieczyslaw Nalewaj
@ 2026-10-10 19:39 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-10 19:40 ` [PATCH net-next v3 4/9] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
` (5 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:39 UTC (permalink / raw)
To: linusw, 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 | 75 ++++++++++++++++++++----
1 file changed, 62 insertions(+), 13 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 840db5a..8f3295c 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -344,6 +344,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 */
@@ -361,6 +362,21 @@
#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)
+/* Width of the FORCE_SPEED field, bits[1:0] */
+#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH 2
+
+/* 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
@@ -1646,23 +1662,32 @@ 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;
@@ -1692,20 +1717,44 @@ 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 [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 3/9] net: dsa: realtek: rtl8365mb: set speed for family D
2026-10-10 19:39 ` [PATCH net-next v3 3/9] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, 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 does not describe what the patch changes for
family D.
- [Low] Concern: no rtl8365mb_chip_infos[] entry sets .family =
RTL8365MB_FAMILY_D, so the new family D branch in…
--
net: dsa: realtek: rtl8365mb: set speed for family D
This adds family D handling to rtl8365mb_ext_config_forcemode(). Family D
gets a dedicated 2500M speed value (5), and the third speed bit goes into
a new FORCE_SPEED2 field. Family D also programs a per-port force register
and a matching per-port enable register instead of the per-extint force
register.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 840db5ad32efe..8f3295c232362 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -1692,20 +1717,44 @@ 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;
[Severity: Low]
This isn't a bug, but the subject and commit message only cover the new
3-bit speed encoding. For family D, rtl8365mb_ext_config_forcemode() also
changes these things:
- All forced-mode programming moves from
RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id) to the per-port
register at 0x12c0 + port. That covers link, duplex, pause and every
speed, plus the link down path from mac_link_down.
- FORCE_EN (BIT(12)) is no longer set, because on family D that bit is
FORCE_SPEED2.
- Every call, including link down, writes 0xFFFF to a second per-port
register at 0x12c8 + port.
Could the commit message, or a comment here, explain what the 16 bits in
RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN() select, and why setting all
of them is correct?
If those bits choose which fields of the force register are forced, then
0xFFFF also forces fields this code writes as zero (bit 7, bits 8-11 and
bits 14-15).
It might also help to say that the family C bit positions for TXPAUSE,
RXPAUSE, LINK, DUPLEX and SPEED are reused for the register at the new
address.
This looks like it follows the vendor RTL8367D sequence. The later patches
in the series leave this code as it is and don't add an explanation.
> + } 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;
> + }
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next v3 4/9] net: dsa: realtek: rtl8365mb: set RGMII mode for family D
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (2 preceding siblings ...)
2026-10-10 19:39 ` [PATCH net-next v3 3/9] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
@ 2026-10-10 19:40 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-10 19:41 ` [PATCH net-next v3 5/9] net: dsa: realtek: rtl8365mb: set and get vlan 4k " Mieczyslaw Nalewaj
` (4 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:40 UTC (permalink / raw)
To: linusw, 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 | 50 ++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 8f3295c..c15b88b 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -332,6 +332,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
@@ -458,6 +464,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
@@ -1194,6 +1214,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;
@@ -1263,6 +1284,35 @@ 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 in
+ * 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 [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 4/9] net: dsa: realtek: rtl8365mb: set RGMII mode for family D
2026-10-10 19:40 ` [PATCH net-next v3 4/9] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, 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 concern says the family-D branch in
rtl8365mb_ext_config_rgmii() can never run because no…
--
net: dsa: realtek: rtl8365mb: set RGMII mode for family D
This commit adds family D specific RGMII setup for extension interface 1
in rtl8365mb_ext_config_rgmii(). It clears the EXT1 RGMII TX clock delay
field in EXT_TXC_DLY and binds the RGMII/MII pin mux to MAC7 when MAC4 is
not using extension 1. It also disables SerDes 1, which shares those pins.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 8f3295c232362..c15b88ba7bb5c 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -1263,6 +1284,35 @@ 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) {
[Severity: Low]
This isn't a bug, but can this branch in rtl8365mb_ext_config_rgmii()
run at this point in the series?
At this commit, no rtl8365mb_chip_infos[] entry seems to use
RTL8365MB_FAMILY_D, so the new block looks like dead code for now. The DT
binding in Documentation/devicetree/bindings/net/dsa/realtek.yaml already
lists RTL8367S-VB (0x6642), which puts the binding a little ahead of the
driver while bisecting.
The later patch "net: dsa: realtek: rtl8365mb: add support for
RTL8367S-VB" adds the entry:
.name = "RTL8367S-VB",
.chip_id = 0x6642,
.chip_ver = 0x0010,
.family = RTL8365MB_FAMILY_D,
That makes this branch reachable and brings the binding and the chip table
back in line. At the intermediate commits, an RTL8367S-VB device tree just
fails chip ID matching, the same as before the series. So I don't see a
runtime regression here, only an ordering detail in the series.
> + ret = regmap_update_bits(priv->map,
> + RTL8365MB_D_REG_EXT_TXC_DLY,
> + RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK, 0);
> + if (ret)
> + return ret;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next v3 5/9] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (3 preceding siblings ...)
2026-10-10 19:40 ` [PATCH net-next v3 4/9] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
@ 2026-10-10 19:41 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-10 19:42 ` [PATCH net-next v3 6/9] net: dsa: realtek: rtl8365mb: use raw VID for PVID on " Mieczyslaw Nalewaj
` (3 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:41 UTC (permalink / raw)
To: linusw, 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 | 161 ++++++++++++++++-------
1 file changed, 112 insertions(+), 49 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 8d49ffa..2c4c5ba 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -48,12 +48,14 @@
#include "rtl8365mb_vlan.h"
#include "rtl8365mb_table.h"
+#include "rtl8365mb.h"
#include <linux/if_bridge.h>
#include <linux/lockdep.h>
#include <linux/regmap.h>
/* 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 */
#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)
@@ -65,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)
@@ -185,13 +191,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;
@@ -199,33 +208,61 @@ 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;
}
@@ -233,6 +270,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;
@@ -244,36 +284,59 @@ 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 [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 5/9] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
2026-10-10 19:41 ` [PATCH net-next v3 5/9] net: dsa: realtek: rtl8365mb: set and get vlan 4k " Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, 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] In rtl8365mb_vlan_4k_write()
(drivers/net/dsa/realtek/rtl8365mb_vlan.c), the family-D branch sets…
--
net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
This commit gives family D its own two-word pack/unpack for the CVLAN
(VLAN4k) table entry in rtl8365mb_vlan_4k_read() and
rtl8365mb_vlan_4k_write(). Family C keeps its three-word layout. On
family D the FID is narrowed to 2 bits and the write path forces the
IVL selection bits.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> index 8d49ffadce926..2c4c5baf93f10 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
[ ... ]
> @@ -244,36 +284,59 @@ 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);
>
[ ... ]
> + 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);
[Severity: Low]
This doesn't change runtime behavior today, because
rtl8365mb_vlan_4k_port_set() always sets vlan4k.ivl_en = true. However,
the commit message and comments no longer match the code.
The commit message says:
so that control bit is forced here to match existing driver behavior,
not a hardware limitation.
Two bits are forced here, though: IVL_EN (bit 3) and SVLAN_CHK_IVL_SVL
(bit 2). Could the commit message mention the second bit and say why 1
is the right value for it?
Family D writes ignore vlan4k->ivl_en, but rtl8365mb_vlan_4k_read()
still reports the field from hardware:
vlan4k->ivl_en =
FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK,
data[1]);
So the read path and the write path handle the field differently.
Some comments in the file are also out of date for family D. The
kernel-doc for struct rtl8365mb_vlan4k still says:
* @ivl_en: enable IVL instead of default SVL
The file header still says VLAN4k entries can choose between IVL and
SVL.
The layout comment above the new size define still says u16[3]:
/* 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 */
The family D D1 masks are also mixed in with the family C masks under
that comment. Later patches in the series don't update any of these
comments either.
Should these comments be updated to describe the family D layout and
the forced IVL bits?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next v3 6/9] net: dsa: realtek: rtl8365mb: use raw VID for PVID on family D
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (4 preceding siblings ...)
2026-10-10 19:41 ` [PATCH net-next v3 5/9] net: dsa: realtek: rtl8365mb: set and get vlan 4k " Mieczyslaw Nalewaj
@ 2026-10-10 19:42 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-10 19:43 ` [PATCH net-next v3 7/9] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
` (2 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:42 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel
Family D has no working VLAN member config (MC) table. Its PVID is a
raw 12-bit VID in a per-port register at 0x0700 + port, whereas family C
stores a 5-bit MC table index, two ports per register, at
0x0700 + (port >> 1).
Running the family C code on family D therefore reads an MC table that
does not exist, and writes an MC index into the VID register of port
(port >> 1) instead of the one of the port being configured.
Read the PVID straight from its register in
rtl8365mb_vlan_port_get_pvid(), and make rtl8365mb_vlan_pvid_port_set()
and rtl8365mb_vlan_pvid_port_clear() use direct-VID variants on family D
that skip the MC table. They keep the family C frame filter handling and
rollback on error.
No functional change for family C.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 181 ++++++++++++++++++++++-
1 file changed, 176 insertions(+), 5 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 2c4c5ba..ccfa764 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -44,6 +44,10 @@
*
* VLANMC index 0 is reserved as a neutral PVID, used for standalone ports.
*
+ * Everything above describes chips that have a VLAN membership configuration
+ * table. RTL8365MB_FAMILY_D chips do not have one: their PVID is a raw 12-bit
+ * VID in a per-port register, and no VLANMC entries are allocated.
+ *
*/
#include "rtl8365mb_vlan.h"
@@ -119,6 +123,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) \
@@ -170,7 +179,7 @@ struct rtl8365mb_vlan4k {
* This structure is used to get/set entries in the VLAN membership
* configuration database. This feature is largely vestigial, but
* still needed for at least the following features:
- * - PVID configuration
+ * - PVID configuration (family C only, family D has no VLAN MC table)
* - ACL configuration
* - selection of VLAN by the CPU tag when VSEL=1, although the switch
* can also select VLAN based on the VLAN tag if VSEL=0
@@ -742,11 +751,25 @@ 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;
}
@@ -811,6 +834,68 @@ 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 below.
+ *
+ * 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
@@ -823,6 +908,9 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
* PVID. Gracefully unwinds and restores previous configuration if a hardware
* write operation fails during execution.
*
+ * On RTL8365MB_FAMILY_D there is no VLANMC table: the VID is written directly
+ * to the port's PVID register and no VLANMC entry is allocated.
+ *
* Context: Can sleep. Must be called with &priv->vlan_lock held.
* Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
@@ -840,6 +928,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);
@@ -919,6 +1014,76 @@ 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
@@ -928,6 +1093,9 @@ undo_vlan_mc_port_set:
* 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.
*
+ * On RTL8365MB_FAMILY_D the port's PVID register is reset to 0 directly; there
+ * is no VLANMC entry to free.
+ *
* Context: Can sleep. Must be called with &priv->vlan_lock held.
* Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
@@ -942,6 +1110,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 [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 6/9] net: dsa: realtek: rtl8365mb: use raw VID for PVID on family D
2026-10-10 19:42 ` [PATCH net-next v3 6/9] net: dsa: realtek: rtl8365mb: use raw VID for PVID on " Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, 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] This patch adds a family D branch to
rtl8365mb_vlan_port_get_pvid()…
--
net: dsa: realtek: rtl8365mb: use raw VID for PVID on family D
On family D chips, this commit reads the PVID as a raw 12-bit VID from the
per-port register at 0x0700 + port. Family C keeps using the VLAN MC table
index. It also adds direct-VID variants of the PVID set and clear paths
that skip the MC table on family D.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> index 2c4c5baf93f10..ccfa764b98125 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
[ ... ]
> @@ -742,11 +751,25 @@ 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;
[Severity: Medium]
Can this report a PVID that the bridge never configured?
The kernel-doc for rtl8365mb_vlan_port_get_pvid() still says:
* Returns the port PVID if defined or 0 if not.
On family C that holds. An unconfigured port has VLANMC index 0, and
rtl8365mb_vlan_get_pvid_mc() memsets the entry, so evid is 0. On family D
this branch returns whatever the register holds.
rtl8365mb_vlan_setup() only calls
rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL) for each port
and then sets RTL8365MB_VLAN_CTRL_EN_MASK. Nothing there writes 0 to the
family D PVID registers. The only code that writes 0 is
rtl8365mb_vlan_pvid_port_clear_direct(), and it only does so when the
bridge clears a VID that matches the register.
After rtl8365mb_reset_chip(), the register holds its hardware default or
a value loaded from EEPROM. If that value is non-zero, two callers act on
it.
In rtl8365mb_port_vlan_filtering() with vlan_filtering set:
if (pvid_vid)
accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME;
Here, a VLAN-filtering port with no bridge PVID (for example a bridge
with default_pvid 0) would accept untagged frames into the stale VID.
In the undo_set_pvid path of rtl8365mb_port_vlan_add():
if (pvid_vid)
(void)rtl8365mb_vlan_pvid_port_set(ds, port, pvid_vid,
NULL);
On error, this path would program the stale VID as the PVID.
I couldn't confirm the reset value of these registers from the tree, so
this depends on the hardware or EEPROM default. The family D get_pvid
branch and rtl8365mb_vlan_setup() also look unchanged at the end of the
series.
Should rtl8365mb_vlan_setup() clear the family D PVID registers to 0? That
would match the family C convention of VLANMC index 0 as the neutral
PVID.
> + } else {
> + ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx,
> + &vlanmc);
> + if (ret)
> + return ret;
> +
> + *pvid = vlanmc.evid;
> + }
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next v3 7/9] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (5 preceding siblings ...)
2026-10-10 19:42 ` [PATCH net-next v3 6/9] net: dsa: realtek: rtl8365mb: use raw VID for PVID on " Mieczyslaw Nalewaj
@ 2026-10-10 19:43 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-10 19:44 ` [PATCH net-next v3 8/9] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes Mieczyslaw Nalewaj
2026-10-10 19:44 ` [PATCH net-next v3 9/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:43 UTC (permalink / raw)
To: linusw, 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 c15b88b..0d3a2a3 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -284,6 +284,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
@@ -332,7 +333,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.
*/
@@ -713,6 +729,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),
@@ -1316,7 +1347,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;
@@ -1334,10 +1366,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;
@@ -1347,7 +1382,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;
@@ -1387,6 +1424,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];
@@ -1465,28 +1510,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
@@ -1515,34 +1579,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;
}
- /* Mux the SerDes to MAC8 in the requested mode */
+ /* 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;
+ }
+
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;
@@ -1552,12 +1635,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;
@@ -1565,14 +1650,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)
@@ -1597,10 +1684,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
@@ -1608,7 +1699,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;
@@ -1619,6 +1714,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.
*/
@@ -1660,6 +1762,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.
@@ -1989,7 +2097,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 [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 7/9] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
2026-10-10 19:43 ` [PATCH net-next v3 7/9] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, 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, every new family D branch is dead code.
- [Low] Several unconditional comments are wrong for family D after this
patch: (a) rtl8365mb_pcs_config() line 1649 says "Keep SGMII in-band…
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but
rtl8365mb_pcs_ops has no .pcs_an_restart, and phylink calls that…
--
net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
This patch extends the rtl8365mb SGMII/HSGMII PCS code to family D
switches, whose CPU SerDes is reached through the indexed SDS13 window.
It adds an index argument to the SDS indirect accessors, family D tuning
tables and SDS_MISC settings, and skips the family C link forcing in
pcs_link_up() and mac_link_up() on family D.
> 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.
[Severity: Low]
This isn't a bug, but at this commit every entry in rtl8365mb_chip_infos[]
is RTL8365MB_FAMILY_C. So rtl8365mb_get_family() never returns
RTL8365MB_FAMILY_D.
That makes all the new is_d branches unreachable for now:
- rtl8365mb_pcs_config()
- rtl8365mb_pcs_get_state()
- rtl8365mb_pcs_link_up()
- rtl8365mb_phylink_mac_link_up()
- the family D early return in rtl8365mb_sds_probe_option()
They only become reachable with the later patch in the series, "net: dsa:
realtek: rtl8365mb: add support for RTL8367S-VB". That patch adds a
family D chip_info entry.
Could the commit message say that this patch is preparatory, and that the
code is enabled by the RTL8367S-VB patch?
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index c15b88ba7bb5c..0d3a2a385a204 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -1565,14 +1650,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;
[Severity: Low]
This isn't a bug, but does this comment still hold for family D? With this
patch, rtl8365mb_pcs_link_up() returns right away on family D.
Several other comments that apply to both families have the same problem
after this change:
In rtl8365mb_pcs_get_state():
/* In-band autonegotiation is not implemented, so the link parameters are
* forced from rtl8365mb_pcs_link_up(). ...
The family D branch there hard-codes DUPLEX_FULL and takes the speed from
state->interface instead.
In rtl8365mb_phylink_mac_link_up():
/* The SerDes forced link state is programmed by the PCS in
* rtl8365mb_pcs_link_up(); ...
This is not true on family D.
Above RTL8365MB_SDS_EXT_INTERFACE_ID:
/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
* which is port 6.
*/
The new family D defines (RTL8365MB_D_SDS_EXT0_INDEX and
RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0) put the SerDes on EXT0/MAC6. The
RTL8367S-VB chip_info added at the end of the series also uses extint
id 0.
The family D path in rtl8365mb_pcs_config() still applies
RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, which is built from
RTL8365MB_SDS_EXT_INTERFACE_PORT. However, the BYPASS_LINE_RATE comment
says the port-to-bit mapping "only holds for the RTL8367C-style parts
this driver supports".
None of these comments change by the end of the series. Could they be
updated to cover family D?
[ ... ]
> @@ -1660,6 +1762,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: High]
This is a pre-existing issue, but rtl8365mb_pcs_ops has no .pcs_an_restart
callback:
static const struct phylink_pcs_ops rtl8365mb_pcs_ops = {
.pcs_inband_caps = rtl8365mb_pcs_inband_caps,
.pcs_config = rtl8365mb_pcs_config,
.pcs_get_state = rtl8365mb_pcs_get_state,
.pcs_link_up = rtl8365mb_pcs_link_up,
};
phylink calls that callback without a NULL check:
drivers/net/phy/phylink.c:phylink_pcs_an_restart() {
if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
pl->link_config.advertising) &&
phy_interface_mode_is_8023z(pl->link_config.interface) &&
phylink_autoneg_inband(pl->act_link_an_mode))
pl->pcs->ops->pcs_an_restart(pl->pcs);
}
Suppose the DT gives port 6 phy-mode = "2500base-x" and
managed = "in-band-status":
- phylink_parse_mode() sets Autoneg in the advertising mask.
- pcs_inband_caps reports LINK_INBAND_DISABLE, so phylink_pcs_neg_mode()
sets pcs_neg_mode to PHYLINK_PCS_NEG_INBAND_DISABLED.
- act_link_an_mode still stays MLO_AN_INBAND.
Can this then oops during port setup through this path?
dsa_port_enable_rt()
phylink_start()
phylink_mac_initial_config(pl, true)
phylink_major_config(..., restart = true)
phylink_pcs_config()
rtl8365mb_pcs_config()
phylink_pcs_an_restart()
pl->pcs->ops->pcs_an_restart(pl->pcs) <-- NULL
rtl8365mb_phylink_mac_config() only logs an error for in-band mode.
There is no pcs_validate or mac_prepare to reject the configuration
earlier.
The same path already exists for family C (RTL8367S, RTL8367SB with
HSGMII). This patch adds family D (RTL8367S-VB at the end of the series)
as another way to reach it.
Should rtl8365mb_pcs_ops provide a .pcs_an_restart callback?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next v3 8/9] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (6 preceding siblings ...)
2026-10-10 19:43 ` [PATCH net-next v3 7/9] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
@ 2026-10-10 19:44 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-10 19:44 ` [PATCH net-next v3 9/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:44 UTC (permalink / raw)
To: linusw, 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 work item rather than from pcs_config(), because the
attempts have to continue for seconds after pcs_config() has returned.
pcs_config() re-runs cancel the work, so an attempt never interleaves
with a reconfiguration. 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 reads the SerDes link status through the SDS_INDACS indirect
access window, a multi-register ADR/CMD/DATA sequence that the PCS
callbacks also use, so those sequences are serialized by a new sds_lock.
The SDS_MISC updates need no extra locking: each one is a single regmap
read-modify-write.
The work is only started for 2500base-x, the mode the re-latch edge
was observed with. It is cancelled from pcs_disable(), from
pcs_config() before SDS_MISC is touched, from teardown(), and by a
devres action, so that it never outlives the PCS or the driver data.
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 | 264 +++++++++++++++++++++--
1 file changed, 249 insertions(+), 15 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 0d3a2a3..5d80409 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -97,6 +97,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>
@@ -298,6 +299,28 @@
#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
+
+/* The SerDes stays parked until the restore write succeeds, so a failed
+ * write is retried this many times before giving up until the next run.
+ */
+#define RTL8365MB_D_SDS_RESTORE_TRIES 3
+
+/* 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
@@ -928,10 +951,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,
+ * 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.
*/
@@ -941,9 +970,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)
@@ -1350,43 +1383,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.
@@ -1396,15 +1439,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
@@ -1526,6 +1573,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 parks SDS_MISC and restores the target value after a sleep, so
+ * it could otherwise write a stale target over the new configuration.
+ */
+ 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) {
@@ -1611,7 +1665,9 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
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),
@@ -1658,8 +1714,155 @@ 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;
+
+ /* The re-latch edge has only been observed with HSGMII, so the work is
+ * only started for 2500base-x. Start a new episode and kick off the
+ * first attempt; see rtl8365mb_sds_relatch_work() for why this cannot
+ * wait for phylink to ask again.
+ */
+ if (is_d && interface == PHY_INTERFACE_MODE_2500BASEX) {
+ 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
+ * call rtl8365mb_pcs_get_state() for a link without in-band signalling
+ * such as the fixed-link CPU trunk (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;
+ int i;
+
+ /* 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
+ * link without in-band signalling, so keep driving one edge
+ * per interval.
+ */
+ delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL;
+
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_D_SDS_MISC_CFG_MASK, park);
+ if (ret) {
+ dev_err_ratelimited(priv->dev,
+ "failed to park SDS_MISC: %pe\n",
+ ERR_PTR(ret));
+ goto rearm;
+ }
+
+ usleep_range(20000, 21000);
+
+ for (i = 0; i < RTL8365MB_D_SDS_RESTORE_TRIES; i++) {
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_D_SDS_MISC_CFG_MASK,
+ READ_ONCE(mb->sds_misc_target_val));
+ if (!ret)
+ break;
+ usleep_range(1000, 2000);
+ }
+ if (ret) {
+ dev_err_ratelimited(priv->dev,
+ "failed to restore SDS_MISC: %pe\n",
+ ERR_PTR(ret));
+ /* The SerDes is left parked: come back soon rather than
+ * at the (possibly 10 s) pace chosen above.
+ */
+ delay = RTL8365MB_D_SDS_RELATCH_INTERVAL;
+ 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 void rtl8365mb_sds_relatch_cancel(void *data)
+{
+ struct rtl8365mb *mb = data;
+
+ cancel_delayed_work_sync(&mb->sds_relatch);
}
static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
@@ -1802,11 +2005,20 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
}
}
+static void rtl8365mb_pcs_disable(struct phylink_pcs *pcs)
+{
+ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+
+ if (rtl8365mb_get_family(mb->priv) == RTL8365MB_FAMILY_D)
+ cancel_delayed_work_sync(&mb->sds_relatch);
+}
+
static const struct phylink_pcs_ops rtl8365mb_pcs_ops = {
.pcs_inband_caps = rtl8365mb_pcs_inband_caps,
.pcs_config = rtl8365mb_pcs_config,
.pcs_get_state = rtl8365mb_pcs_get_state,
.pcs_link_up = rtl8365mb_pcs_link_up,
+ .pcs_disable = rtl8365mb_pcs_disable,
};
static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
@@ -3506,6 +3718,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);
@@ -3572,6 +3788,24 @@ 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;
+
+ ret = devm_mutex_init(priv->dev, &mb->sds_lock);
+ if (ret)
+ return ret;
+
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D) {
+ /* Registered after sds_lock, so on release the work is
+ * cancelled before the mutex is destroyed.
+ */
+ INIT_DELAYED_WORK(&mb->sds_relatch,
+ rtl8365mb_sds_relatch_work);
+ ret = devm_add_action_or_reset(priv->dev,
+ rtl8365mb_sds_relatch_cancel,
+ mb);
+ 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 [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 8/9] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes
2026-10-10 19:44 ` [PATCH net-next v3 8/9] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · 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] The re-latch work can leave the family D SerDes parked
(SDS_MISC mode field = RTL8365MB_D_PORT_SDS_MODE_DISABLE) for about 11…
- [Medium] The family D re-latch work rearms itself forever and is not
stopped on system shutdown or reboot.
--
net: dsa: realtek: rtl8365mb: re-latch the family D SerDes
This commit adds a self-rearming delayed work for family D chips. The
work parks SDS_MISC in DISABLE mode and then restores it, which drives a
DISABLE -> HSGMII edge until the SerDes receiver latches. After that it
checks the link every 10 s.
It also adds sds_lock to serialize the SDS_INDACS indirect access
sequences. The work is cancelled from pcs_config(), pcs_disable(),
teardown() and a devres action.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 0d3a2a385a204..5d804099f131a 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -1658,8 +1714,155 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
[ ... ]
> +static void rtl8365mb_sds_relatch_work(struct work_struct *work)
> +{
[ ... ]
> + 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;
> + }
[ ... ]
> + 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);
[ ... ]
> + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
> + RTL8365MB_D_SDS_MISC_CFG_MASK, park);
> + if (ret) {
> + dev_err_ratelimited(priv->dev,
> + "failed to park SDS_MISC: %pe\n",
> + ERR_PTR(ret));
> + goto rearm;
> + }
> +
> + usleep_range(20000, 21000);
> +
> + for (i = 0; i < RTL8365MB_D_SDS_RESTORE_TRIES; i++) {
> + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
> + RTL8365MB_D_SDS_MISC_CFG_MASK,
> + READ_ONCE(mb->sds_misc_target_val));
> + if (!ret)
> + break;
> + usleep_range(1000, 2000);
> + }
> + if (ret) {
> + dev_err_ratelimited(priv->dev,
> + "failed to restore SDS_MISC: %pe\n",
> + ERR_PTR(ret));
> + /* The SerDes is left parked: come back soon rather than
> + * at the (possibly 10 s) pace chosen above.
> + */
> + delay = RTL8365MB_D_SDS_RELATCH_INTERVAL;
> + goto rearm;
> + }
[Severity: Medium]
Can the SerDes stay parked for about 11 s here, instead of the ~1 s this
branch asks for? Consider the case where all RTL8365MB_D_SDS_RESTORE_TRIES
restore writes fail on the last in-budget attempt (attempts == 15):
attempt 15:
WRITE_ONCE(mb->sds_relatch_count, 16)
park write succeeds
all three restore regmap_update_bits() calls fail
delay = RTL8365MB_D_SDS_RELATCH_INTERVAL; goto rearm
next run, 1 s later:
link bit is clear, because the lane is still in MODE_DISABLE
attempts == RTL8365MB_D_SDS_RELATCH_MAX_TRIES + 1
sds_relatch_count = 17
"SerDes did not latch after 15 re-latch attempts"
delay = RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL; goto rearm
The report-only pass never writes sds_misc_target_val back to SDS_MISC.
The mode is restored only on the run after that, 10 s later. The CPU
trunk stays in DISABLE mode for that whole window, and the warning
describes an edge that was never completed.
There is a related gap in the link status read failure path above. If an
earlier run left the lane parked and rtl8365mb_sds_read() then fails, the
work jumps straight to rearm. It does not try the direct SDS_MISC
restore, even though that restore does not go through SDS_INDACS.
Could the work record that a restore is still outstanding, and retry the
restore first on the next run, before the link check and the report-only
branch?
> +
> + 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);
> +}
[Severity: Medium]
Every path in rtl8365mb_sds_relatch_work() ends here, so the work rearms
until something cancels it. What stops it on reboot or kexec?
The .shutdown path goes through rtl83xx_shutdown():
drivers/net/dsa/realtek/rtl83xx.c:rtl83xx_shutdown() {
struct dsa_switch *ds = &priv->ds;
dsa_switch_shutdown(ds);
dev_set_drvdata(priv->dev, NULL);
}
Clearing drvdata stops realtek_{smi,mdio}_remove() from running. That
means neither rtl8365mb_teardown() nor the rtl8365mb_sds_relatch_cancel()
devres action runs.
dsa_switch_shutdown() closes the conduits and unlinks the user netdevs.
It does not stop the CPU port's phylink, so rtl8365mb_pcs_disable() is
not called either.
After shutdown, couldn't the work keep issuing SDS_INDACS reads and
SDS_MISC park/restore writes over MDIO/SMI? That would happen while the
parent bus controller is shutting down, and it could leave the SerDes
parked for a kexec'd kernel. Every reboot of a family D board would hit
this once the RTL8367S-VB support later in the series is applied.
The commit message says the cancellations are there:
so that it never outlives the PCS or the driver data.
Should the work also be cancelled on the shutdown path?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next v3 9/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
2026-10-10 19:36 ` [PATCH net-next v3 0/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (7 preceding siblings ...)
2026-10-10 19:44 ` [PATCH net-next v3 8/9] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes Mieczyslaw Nalewaj
@ 2026-10-10 19:44 ` Mieczyslaw Nalewaj
2026-10-11 19:51 ` netdev-bot+sashiko
8 siblings, 1 reply; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-10 19:44 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel
Add the RTL8367S-VB (chip ID 0x6642, version 0x0010) to the chip table.
It is the first chip of family D, whose handling was added in the
preceding patches, so this is the patch that makes it reachable.
The SGMII/HSGMII SerDes of this chip sits on external interface 0
(port 6), and the MII/TMII/RMII/RGMII interface on external interface 1
(port 7), the other way round than on family C chips. The chip reuses the
existing initialization jam table.
The chip has 8 ports, not 11. Cap priv->num_ports at
RTL8365MB_D_MAX_NUM_PORTS 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.
The L2 learning limit is the same as on family C (2112 entries), as
given by the Realtek rtl8367 common API (rtl8367_common V1.4.2), so
RTL8365MB_LEARN_LIMIT_MAX needs no per-family handling; only its
comment is updated.
Tested on a TP-Link Archer AX55 v1 (RTL8367S-VB, CPU port over HSGMII
to an IPQ5018) with bridge VLAN filtering and per-port PVIDs.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 31 +++++++++++++++++++++---
1 file changed, 27 insertions(+), 4 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 5d80409..c604560 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -89,6 +89,12 @@
* things will work out-of-the-box for other chips, and a careful review of the
* vendor driver may be needed to expand support. The RTL8365MB-VC seems to be
* one of the simpler chips.
+ *
+ * The RTL8367S-VB (chip ID 0x6642) is not part of the family listed above. It
+ * belongs to a newer generation, which this driver calls family D. Its
+ * registers differ in the VLAN table, the external interface speed and RGMII
+ * configuration and the SerDes, so the code paths specific to it are selected
+ * with rtl8365mb_get_family().
*/
#include <linux/bitfield.h>
@@ -118,9 +124,10 @@
#define RTL8365MB_NUM_PHYREGS 32
#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1)
#define RTL8365MB_MAX_NUM_PORTS 11
-/* 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.
+#define RTL8365MB_D_MAX_NUM_PORTS 8
+/* Valid for the whole RTL8367C family (and for family D, RTL8367S-VB)
+ * except RTL8370B, which has 4160 entries. RTL8370B is mentioned in
+ * vendor code but it might not even belong to the same RTL8367C family.
*/
#define RTL8365MB_LEARN_LIMIT_MAX 2112
#define RTL8365MB_MAX_NUM_EXTINTS 3
@@ -874,6 +881,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 {
@@ -3787,7 +3807,10 @@ 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;
ret = devm_mutex_init(priv->dev, &mb->sds_lock);
if (ret)
--
2.53.0
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 9/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
2026-10-10 19:44 ` [PATCH net-next v3 9/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-10-11 19:51 ` netdev-bot+sashiko
2026-10-11 23:32 ` Mieczyslaw Nalewaj
0 siblings, 1 reply; 19+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 19:51 UTC (permalink / raw)
To: namiltd
Cc: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
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, rtl8365mb_pcs_get_state() replaces the configured
speed and duplex with fixed values.
- [Low] The comment above RTL8365MB_SDS_EXT_INTERFACE_ID
(drivers/net/dsa/realtek/rtl8365mb_main.c:347-350) says "The embedded…
--
net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
This patch adds the RTL8367S-VB (chip ID 0x6642, version 0x0010) to the
chip table as the first family D chip. On this chip the SGMII/HSGMII
SerDes is on external interface 0 (port 6) and RGMII is on external
interface 1 (port 7). The patch also caps priv->num_ports at 8 for
family D and updates the comment on the learn limit.
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 5d804099f131a..c6045607ade06 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
[ ... ]
> @@ -874,6 +881,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]
This entry lets port 6 run SGMII on a family D chip. That makes the
family D branch of rtl8365mb_pcs_get_state() reachable for the first
time.
Does that branch discard the forced speed and duplex that phylink has
already filled in?
drivers/net/dsa/realtek/rtl8365mb_main.c:rtl8365mb_pcs_get_state() {
...
if (is_d) {
state->duplex = DUPLEX_FULL;
state->speed = state->interface == PHY_INTERFACE_MODE_2500BASEX ?
SPEED_2500 : SPEED_1000;
return;
}
...
}
rtl8365mb_phylink_get_caps() advertises MAC_10 | MAC_100 | MAC_1000FD
for this port, so phylink accepts a forced 10/100 setting.
Take port 6 as a PHY-less SGMII user port with
managed = "in-band-status", and run:
ethtool -s <port> autoneg off speed 100 duplex full
phylink_ethtool_ksettings_set() stores link_config.speed = 100.
phylink_pcs_neg_mode() then picks PHYLINK_PCS_NEG_OUTBAND, and the mode
stays MLO_AN_INBAND because there is no phydev.
After that:
phylink_resolve()
phylink_mac_pcs_get_state()
state->speed = pl->link_config.speed; /* 100 */
rtl8365mb_pcs_get_state()
state->speed = SPEED_1000;
phylink_link_up()
rtl8365mb_phylink_mac_link_up() /* 1000/full */
rtl8365mb_ext_config_forcemode()
Would the MAC then be forced to 1000 Mbps while the configured speed is
100? Would ethtool get_link_ksettings also report 1000/full?
The family C branch avoids this by reading the forced values back from
SDS_MISC. When in-band autoneg is disabled, could the family D branch
keep the speed and duplex that phylink passed in?
[Severity: Low]
This isn't a bug, but this entry makes the comment above
RTL8365MB_SDS_EXT_INTERFACE_ID inaccurate:
/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
* which is port 6.
*/
#define RTL8365MB_SDS_EXT_INTERFACE_ID 1
On RTL8367S-VB the SerDes is on external interface 0, and external
interface 1 is the port 7 RGMII interface.
rtl8365mb_pcs_config() still does:
const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
This only works today because every use of id is in the !is_d branch.
Could this comment be updated for family D, as was done for the
LEARN_LIMIT comment?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/e84d76ee-03df-49b0-8c9a-b289dfae8728%40yahoo.com
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next v3 9/9] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
2026-10-11 19:51 ` netdev-bot+sashiko
@ 2026-10-11 23:32 ` Mieczyslaw Nalewaj
0 siblings, 0 replies; 19+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-10-11 23:32 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel,
netdev-bot+sashiko
Thanks for the review. The issues are valid; I will respin as v4.
pw-bot: cr
^ permalink raw reply [flat|nested] 19+ messages in thread