* [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
[not found] <d9da17af-f49e-4878-a8dd-59a8919e6930.ref@yahoo.com>
@ 2026-11-12 17:31 ` Mieczyslaw Nalewaj
2026-11-12 17:32 ` [PATCH net-next v10 01/12] dt-bindings: net: dsa: realtek: add RTL8367S-VB Mieczyslaw Nalewaj
` (11 more replies)
0 siblings, 12 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:31 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
This series adds support for the RTL8367S-VB (chip ID 0x6642) to the
rtl8365mb driver. The chip belongs to a new "family D" that differs from
the existing family C in its VLAN and L2 table layouts, speed/RGMII
configuration and SerDes (SDS13) handling.
Patch 1 documents the chip in the devicetree binding.
Patch 2 adds the chip family infrastructure, with the per-family data
(port count, init jam table) in struct rtl8365mb_family_info (no
functional change).
Patch 3 moves the external interface force-mode configuration behind
the family info (no functional change).
Patches 4-5 add the port speed and RGMII mode configuration for family D.
Patch 6 moves the VLAN 4k table accessors behind the family info (no
functional change).
Patches 7-8 add the VLAN 4k table access (dynamically selecting the IVL
learning mode from the filtering state of the user ports for family D
only) and the raw-VID PVID handling for family D.
Patch 9 adds SDS13 PCS support.
Patch 10 refactors the family C L2 MAC handling by introducing shared
pack/unpack helpers and reaching the family C serializers through
function pointers in the family info (no functional change).
Patch 11 makes the L2 (FDB/MDB) table access work on family D, which
has a 5-word entry layout, narrower table access fields and no EFID,
and where a VLAN-unaware bridge (VID 0) is handled with an SVL key.
Patch 12 adds the RTL8367S-VB chip table entry. It comes last, so that
no intermediate commit exposes a family D chip with incomplete
handling and the series stays bisectable.
Family C's functional behaviour is unchanged. The L2 table access and
FDB/MDB code is shared; the family D handling is selected at runtime
through the family info and rtl8365mb_is_d() (patch 11). The RTL8367S-VB
has 8 ports, described by num_ports in its family info (patch 12). On
family D, FDB/MDB offload is available, but there is no per-bridge FDB
isolation (no EFID), so only one bridge is offloaded
(max_num_bridges = 1). Further bridges are forwarded in software and
isolated by the port isolation masks only (patch 11). Standalone ports,
software-bridged ports and the offloaded VLAN-unaware bridge share FID 0
in the LUT.
Tested on a TP-Link Archer AX55 v1 (RTL8367S-VB, CPU port over HSGMII
to an IPQ5018, with the UNIPHY mode sequence run at GMAC probe) with
bridge VLAN filtering and per-port PVIDs, by Oleg Gavrilov.
Tested on family D by Gustavo Foletto Mora on a TP-Link EX511 v2
(RTL8367S-VB, CPU port 6 over 1G SGMII to an IPQ5018) on a live network,
with a VLAN-aware bridge (tagged trunk with VIDs 10-40, access ports on
VIDs 10 and 20) and a VLAN-unaware one: VLAN table and PVIDs, tagged and
untagged traffic through the CPU port, VLAN isolation, FDB and MDB
entries, VLAN add/delete churn, moving a port to a second bridge,
traffic in the switch and through the CPU port, a conduit down/up and
reboots, with no FCS, symbol, drop, fragment or jabber errors on the
checked ports.
Regression-tested on family C by Stanislaw Pal on a TP-Link Archer
AX55 v1 (RTL8367S, CPU port over a 2500base-x fixed link to an IPQ5018),
on a tree that also carries an 802.1Q-based tagger for this switch:
three boots (after sysupgrade, a reboot and a cold start) and traffic
between a LAN client and an 802.11s mesh in both directions, with no FCS
errors, drops or driver warnings. Family D paths are not exercised on
that chip.
Not covered: RGMII and real Wi-Fi stations.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
v10:
- 8/11 of v9 (SerDes re-latch): dropped. Johan Alvarado traced the
symptom to the SoC side: the bootloader hands the UNIPHY over in SGMII
and the out-of-tree qca-uniphy PCS programs SGMII+ only from
pcs_config() at the first open of the conduit, about two seconds after
the switch brought its SerDes up in HSGMII at DSA setup, so the switch
receiver trains against the wrong line rate. Running the UNIPHY mode
sequence at GMAC probe, before the switch probes, fixes it with no
change in rtl8365mb (0 of 32 broken boots, versus 18 of 51). As neither
dwmac-ipq5018 nor pcs-qca-uniphy is in mainline, the workqueue, the
extra locks and the reboot notifier are not needed in this driver
(Johan, Alvin)
- 2/12: say "family-specific" in the comment on the init jam table
- 3/12 (new): move ext_config_forcemode() behind struct
rtl8365mb_family_info, the existing code becoming the family C
implementation; no functional change (Alvin)
- 4/12: only add the family D force-mode implementation, instead of
is_d() branches in the shared code; no functional change; say in the
commit message why the function is not static until the last patch
- 5/12: log an error instead of a warning when RGMII is not available
on port 7, and say in the commit message that port 7 then has no
supported interface
- 6/12 (new): move the VLAN 4k table accessors behind struct
rtl8365mb_family_info, the existing code becoming the family C
implementation; no functional change (Alvin)
- 7/12: only add the family D VLAN 4k accessors, instead of is_d()
branches in the shared code; on family D enable IVL while any user
port has VLAN filtering enabled, independent of the port being
configured (so the CPU port cannot reset it); document that IVL_EN
(bit 3, versus bit 14 on family C) is refreshed on the next
port_vlan_add/del of the VID, not on a vlan_filtering change; say in
the commit message why the accessors are not static until the last
patch
- 9/12: do not advertise 10 and 100 Mbit/s on the family D SerDes port,
as no SerDes rate is programmed for them
- 11/12: do not delete a dynamic unicast entry on family D; correct the
commit message about standalone ports (DSA programs no entries for
them without fdb_isolation) and describe that FID 0 is shared; name
the vendor function that writes the LUT aging value
(dal_rtl8367d_l2_init); say in the commit message why the family D
functions are not static until the last patch
- 2/12 and 12/12: restored Reviewed-by from Linus, reconfirmed on the
list after v9
v9: https://lore.kernel.org/netdev/9b73858d-fc09-4988-8bab-fd7b88db9412@yahoo.com/
- 2/11: add struct rtl8365mb_family_info holding per-family data
(port count, init jam table); use num_ports from it in
rtl8365mb_detect(); rename the port count constant to
RTL8365MB_C_MAX_NUM_PORTS (Alvin)
- 3/11: drop the FORCE_SPEED_WIDTH macro and add a mask for the most
significant speed bit (Alvin)
- 5/11: restructure the family D checks when unpacking the untag
field; no functional change
- 7/11: update the kernel-doc of sds_supported and the commit message
for family D always setting it; note in a comment that the register
and field names follow the vendor API
- 8/11:
- use explicit mutex_lock()/mutex_unlock() instead of
guard(mutex); no functional change
- arm the re-latch only for 2500base-x on a CPU port, and say
so in the commit message; ignore conduit state changes of other
switches in the tree
- 9/11: reach the family C L2 serialisers and entry size through
function pointers in the family info (Alvin)
- 10/11:
- add the family D L2 serialisers to the family info, drop
RTL8365MB_D_L2_MAX_PORT and the port checks, as the core never
passes a port above num_ports - 1 (Alvin)
- use an SVL key (IVL=0, FID=0) for VID 0, so static unicast
and multicast entries match the learned ones in a VLAN-unaware
bridge; they were written with IVL set and never replaced the
learned entries (reported and tested by Gustavo Foletto Mora)
- offload only one bridge on family D (max_num_bridges = 1),
as the LUT cannot tell bridges apart without EFID; further bridges
fall back to software forwarding
- do not delete a unicast entry on family D if the entry
found belongs to another port; update the l2_lock and EFID
comments; note that the LUT aging value is undocumented
- 11/11: describe the 8 ports of the RTL8367S-VB in its family info
instead of a family check in rtl8365mb_detect(); mention the L2/LUT
and table access differences in the family D overview comment
- Dropped the Reviewed-by tags of Linus and Luiz from 2/11 and the
one of Linus from 11/11, as these patches changed substantially.
Linus, Luiz, could you please take another look? Tags on the other
patches are unchanged.
- Added Tested-by tags: Gustavo Foletto Mora on 2, 3, 5, 6, 7, 9, 10,
11 and Stanislaw Pal on 2, 3, 7, 8, 9, 10
v8: https://lore.kernel.org/netdev/08611959-c555-46d9-8e7e-20b85d0c4056@yahoo.com/
- 2/11: declare the rtl8365mb_is_d() helper as a global inline function
in the new header
- 9/10: split into two patches to improve structure and address reviewer
comments:
- 9/11 (new): introduce shared MAC pack/unpack helpers and refactor
family C serializers without functional changes
- 10/11: drop the confusing _any() suffixes in favor of clean
symmetric dispatchers, switch to the global rtl8365mb_is_d() helper,
and fix the memset size in family D serializers
v7: https://lore.kernel.org/netdev/3bf4dd70-9771-497b-8c27-e925e0ef81eb@yahoo.com/
- 3/10, 6/10: note that the family D path is unreachable until the last
patch
- 4/10: program EXT_TXC_DLY, the pin mux and the SerDes 1 disable
before selecting RGMII; warn when RGMII is disabled because MAC4 owns
EXT1
- 8/10: act only on the conduit of the CPU port that carries the
SerDes; queue the work with schedule_delayed_work() and rename the
devres action to rtl8365mb_sds_relatch_disable()
- 9/10: new, make L2/LUT access work on family D (5-word entries,
narrower table access fields, LUT init, no FDB isolation)
- 10/10: update the NET_DSA_REALTEK_RTL8365MB help text
v6: https://lore.kernel.org/netdev/5a4715dd-89e0-4868-9ffb-c8fb73b18d04@yahoo.com/
- 4/9: do not advertise RGMII for port 7 when MAC4 owns EXT1, instead
of returning -EBUSY from mac_config(), which phylink ignores
- 5/9: take IVL_EN from vlan4k->ivl_en and leave SVLAN_CHK_IVL_SVL at
0, as the vendor API does
- 7/9: pcs_get_state(): keep phylink's speed and duplex for SGMII and
never report the link up with an unknown speed
- 8/9: start the re-latch work when the conduit comes up and stop it
when it goes down (.conduit_state_change); serialize it against
pcs_config() with sds_cfg_lock; disable it for good on shutdown and
unbind
v5: https://lore.kernel.org/netdev/aa5fa12c-e43e-4e5e-9f77-c6eaf477abc4@yahoo.com/
- 1/9: fix the subject
- 4/9: refuse RGMII on EXT1 with -EBUSY when it is owned by MAC4,
before touching any EXT1 register
- 5/9: drop the unverified rationale for forcing SVLAN_CHK_IVL_SVL, say
that the vendor API leaves it at 0, and note that the family D paths
are not reachable until the last patch
- 6/9: init prev_accepted_frame to avoid a maybe-uninitialized warning
- 7/9: comment the write of the whole RX disable field, as in the vendor
sequence; fix two comments that still credit pcs_link_up() on family D
- 8/9: cancel the re-latch work from a reboot notifier instead of
checking the driver data; fix the RESTORE_TRIES comment and do not
sleep after the last failed restore attempt
- 9/9: fix the description of the external interface indices. The v4
changelog said that only HSGMII is exposed; that was wrong, SGMII is
still exposed
v4: https://lore.kernel.org/netdev/38872e6f-1264-4a93-9f81-8212173e909d@yahoo.com/
- 1/9: reword the commit message (support is added by the series)
- 3/9: document the per-port force enable register
- 5/9: document both forced IVL bits, update stale comments
- 6/9: clear the family D PVID registers at setup
- 7/9: do not override the forced SGMII speed in pcs_get_state(),
update stale comments, note that the patch is preparatory
- 8/9: remember a failed restore and retry it first; stop the work
after shutdown (driver data cleared)
- 9/9: expose HSGMII only on the SerDes port, update the SerDes
interface comment
v3: https://lore.kernel.org/netdev/e84d76ee-03df-49b0-8c9a-b289dfae8728@yahoo.com/
- add a dt-bindings patch (1/9): RTL8367S-VB listed under
realtek,rtl8365mb
- add chip family infrastructure as its own patch (2/9), no
functional change
- squash the family D PVID getter and the PVID set/clear patches
into one (6/9), so the read and write paths use the same register
layout at every point of the series
- add the RTL8367S-VB chip table entry last (9/9), after the family
D speed, RGMII, VLAN and PCS support, so no intermediate commit
can reach the chip with incomplete handling (reported by the
automated review)
- 6/9: fix the commit message (port p's write lands in port p >> 1)
and update the stale kernel-doc and file comments
- 8/9: start the re-latch work only for 2500base-x, cancel it from
.pcs_disable, from pcs_config() and via a devres action; retry the
SDS_MISC restore and come back soon if it fails; rewrite the
commit message to match the code (reported by the automated
review)
- 9/9: document family D in the file header comment and in the
learn limit comment (limit confirmed against the Realtek vendor
API)
v2: https://lore.kernel.org/netdev/28382f3f-03c1-4606-9b11-86f118abeafe@yahoo.com/
- 2/8: mask r_speed to the width of the FORCE_SPEED field before passing
it to FIELD_PREP(), so that the family D 2500M value (5) cannot trip
the compile-time range check; bit 2 still goes to FORCE_SPEED2
(reported by the automated review).
- 4/8: the commit message now explains why EXT_TXC_DLY[5:3] is cleared
for RGMII on EXT1: the vendor RGMII setup does it, and the init jam
table leaves the field at 2, which would add an extra TX delay on top
of tx-internal-delay-ps. Add a comment describing the EXT_TXC_DLY
layout (Andrew Lunn's review).
- 8/8: document the new struct rtl8365mb members in the kernel-doc
comment (sds_lock, sds_relatch, sds_relatch_count,
sds_misc_target_val), which fixes the kernel-doc warnings.
- 1/8, 3/8, 5/8, 6/8, 7/8: no changes.
v1: https://lore.kernel.org/netdev/84fc7483-b22d-45ea-a3b8-3285dc357265@yahoo.com/
Mieczyslaw Nalewaj (12):
dt-bindings: net: dsa: realtek: add RTL8367S-VB
net: dsa: realtek: rtl8365mb: add chip family infrastructure
net: dsa: realtek: rtl8365mb: refactor forcemode handling
net: dsa: realtek: rtl8365mb: set speed for family D
net: dsa: realtek: rtl8365mb: set RGMII mode for family D
net: dsa: realtek: rtl8365mb: refactor VLAN 4k handling
net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
net: dsa: realtek: rtl8365mb: use raw VID for PVID on family D
net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
net: dsa: realtek: rtl8365mb: refactor L2 MAC handling
net: dsa: realtek: rtl8365mb: make L2/LUT access work on family D
net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
.../devicetree/bindings/net/dsa/realtek.yaml | 2 +-
drivers/net/dsa/realtek/Kconfig | 3 +-
drivers/net/dsa/realtek/realtek.h | 4 +-
drivers/net/dsa/realtek/rtl8365mb.h | 48 ++
drivers/net/dsa/realtek/rtl8365mb_l2.c | 249 +++++--
drivers/net/dsa/realtek/rtl8365mb_l2.h | 18 +
drivers/net/dsa/realtek/rtl8365mb_main.c | 647 +++++++++++++++---
drivers/net/dsa/realtek/rtl8365mb_table.c | 44 +-
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 321 ++++++++-
drivers/net/dsa/realtek/rtl8365mb_vlan.h | 12 +
drivers/net/dsa/realtek/rtl83xx.c | 5 +-
11 files changed, 1150 insertions(+), 203 deletions(-)
create mode 100644 drivers/net/dsa/realtek/rtl8365mb.h
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v10 01/12] dt-bindings: net: dsa: realtek: add RTL8367S-VB
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-11-12 17:32 ` Mieczyslaw Nalewaj
2026-11-12 17:33 ` [PATCH net-next v10 02/12] net: dsa: realtek: rtl8365mb: add chip family infrastructure Mieczyslaw Nalewaj
` (10 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:32 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
The RTL8367S-VB (chip ID 0x6642) is supported by the rtl8365mb driver
with the changes that follow in this series. 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>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Luiz Angelo Daros de Luca <luizluca@gmail.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 related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 02/12] net: dsa: realtek: rtl8365mb: add chip family infrastructure
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-11-12 17:32 ` [PATCH net-next v10 01/12] dt-bindings: net: dsa: realtek: add RTL8367S-VB Mieczyslaw Nalewaj
@ 2026-11-12 17:33 ` Mieczyslaw Nalewaj
2026-11-12 17:33 ` [PATCH net-next v10 03/12] net: dsa: realtek: rtl8365mb: refactor forcemode handling Mieczyslaw Nalewaj
` (9 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:33 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
Add enum rtl8365mb_family and struct rtl8365mb_family_info, which holds
the data shared by all chips of one family: the family id, the port
count and the init jam table. struct rtl8365mb_chip_info now points to
it through a family_info member, and all existing chips use the
RTL8365MB_FAMILY_C instance. rtl8365mb_get_family_info() and
rtl8365mb_is_d() are declared in a new rtl8365mb.h so that the VLAN
code can use them too.
Use num_ports from the family info in rtl8365mb_detect() instead of a
fixed constant, so that a family with a different port count can be
described by its own entry.
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.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Tested-by: Stanislaw Pal <kuncy7@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/realtek/rtl8365mb.h | 29 +++++++++++++
drivers/net/dsa/realtek/rtl8365mb_main.c | 54 +++++++++++++++---------
2 files changed, 62 insertions(+), 21 deletions(-)
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..daa8f0b
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RTL8365MB_H
+#define _RTL8365MB_H
+
+#include "realtek.h"
+
+enum rtl8365mb_family {
+ RTL8365MB_FAMILY_C,
+ RTL8365MB_FAMILY_D,
+};
+
+struct rtl8365mb_jam_tbl_entry;
+
+struct rtl8365mb_family_info {
+ enum rtl8365mb_family family;
+ unsigned int num_ports;
+ const struct rtl8365mb_jam_tbl_entry *jam_table;
+ size_t jam_size;
+};
+
+const struct rtl8365mb_family_info *
+rtl8365mb_get_family_info(struct realtek_priv *priv);
+
+static inline bool rtl8365mb_is_d(struct realtek_priv *priv)
+{
+ return rtl8365mb_get_family_info(priv)->family == RTL8365MB_FAMILY_D;
+}
+
+#endif /* _RTL8365MB_H */
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index da06f95..e25eb7e 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -110,12 +110,15 @@
#include "rtl83xx.h"
#include "rtl8365mb_l2.h"
#include "rtl8365mb_vlan.h"
+#include "rtl8365mb.h"
/* Family-specific data and limits */
#define RTL8365MB_PHYADDRMAX 7
#define RTL8365MB_NUM_PHYREGS 32
#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1)
-#define RTL8365MB_MAX_NUM_PORTS 11
+#define RTL8365MB_C_MAX_NUM_PORTS 11
+/* Highest port count of any supported chip, used for sizing */
+#define RTL8365MB_MAX_NUM_PORTS RTL8365MB_C_MAX_NUM_PORTS
/* 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.
@@ -702,14 +705,20 @@ struct rtl8365mb_extint {
unsigned int supported_interfaces;
};
+static const struct rtl8365mb_family_info rtl8365mb_family_c = {
+ .family = RTL8365MB_FAMILY_C,
+ .num_ports = RTL8365MB_C_MAX_NUM_PORTS,
+ .jam_table = rtl8365mb_init_jam_8365mb_vc,
+ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
+};
+
/**
* struct rtl8365mb_chip_info - static chip-specific info
* @name: human-readable chip name
* @chip_id: chip identifier
* @chip_ver: chip silicon revision
+ * @family_info: data shared by all chips of the same family
* @extints: available external interfaces
- * @jam_table: chip-specific initialization jam table
- * @jam_size: size of the chip's jam table
*
* These data are specific to a given chip in the family of switches supported
* by this driver. When adding support for another chip in the family, a new
@@ -719,9 +728,8 @@ struct rtl8365mb_chip_info {
const char *name;
u32 chip_id;
u32 chip_ver;
+ const struct rtl8365mb_family_info *family_info;
const struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS];
- const struct rtl8365mb_jam_tbl_entry *jam_table;
- size_t jam_size;
};
/* Chip info for each supported switch in the family */
@@ -731,29 +739,28 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
.name = "RTL8365MB-VC",
.chip_id = 0x6367,
.chip_ver = 0x0040,
+ .family_info = &rtl8365mb_family_c,
.extints = {
{ 6, 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),
},
{
.name = "RTL8367S",
.chip_id = 0x6367,
.chip_ver = 0x00A0,
+ .family_info = &rtl8365mb_family_c,
.extints = {
{ 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
{ 7, 2, 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),
},
{
.name = "RTL8367SB",
.chip_id = 0x6367,
.chip_ver = 0x0010,
+ .family_info = &rtl8365mb_family_c,
.extints = {
{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
PHY_INTF(RMII) | PHY_INTF(RGMII) |
@@ -761,21 +768,18 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
{ 7, 2, 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),
},
{
.name = "RTL8367RB-VB",
.chip_id = 0x6367,
.chip_ver = 0x0020,
+ .family_info = &rtl8365mb_family_c,
.extints = {
{ 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
PHY_INTF(RMII) | PHY_INTF(RGMII) },
{ 7, 2, 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),
},
};
@@ -874,6 +878,14 @@ struct rtl8365mb {
#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
+const struct rtl8365mb_family_info *
+rtl8365mb_get_family_info(struct realtek_priv *priv)
+{
+ struct rtl8365mb *mb = priv->chip_data;
+
+ return mb->chip_info->family_info;
+}
+
static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
{
u32 val;
@@ -3036,17 +3048,17 @@ static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds,
static int rtl8365mb_switch_init(struct realtek_priv *priv)
{
struct rtl8365mb *mb = priv->chip_data;
- const struct rtl8365mb_chip_info *ci;
+ const struct rtl8365mb_family_info *fi;
int ret;
int i;
- ci = mb->chip_info;
+ fi = mb->chip_info->family_info;
- /* Do any chip-specific init jam before getting to the common stuff */
- if (ci->jam_table) {
- for (i = 0; i < ci->jam_size; i++) {
- ret = regmap_write(priv->map, ci->jam_table[i].reg,
- ci->jam_table[i].val);
+ /* Do any family-specific init jam before getting to the common stuff */
+ if (fi->jam_table) {
+ for (i = 0; i < fi->jam_size; i++) {
+ ret = regmap_write(priv->map, fi->jam_table[i].reg,
+ fi->jam_table[i].val);
if (ret)
return ret;
}
@@ -3349,7 +3361,7 @@ 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;
+ priv->num_ports = mb->chip_info->family_info->num_ports;
mb->priv = priv;
mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS;
mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL;
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 03/12] net: dsa: realtek: rtl8365mb: refactor forcemode handling
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-11-12 17:32 ` [PATCH net-next v10 01/12] dt-bindings: net: dsa: realtek: add RTL8367S-VB Mieczyslaw Nalewaj
2026-11-12 17:33 ` [PATCH net-next v10 02/12] net: dsa: realtek: rtl8365mb: add chip family infrastructure Mieczyslaw Nalewaj
@ 2026-11-12 17:33 ` Mieczyslaw Nalewaj
2026-11-12 20:23 ` Linus Walleij
2026-11-12 17:34 ` [PATCH net-next v10 04/12] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
` (8 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:33 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
Move the external interface force-mode configuration behind struct
rtl8365mb_family_info, so that family D can provide its own versions
without is_d() checks in the shared code. The existing code becomes
the family C implementation and is otherwise unchanged. The
mac_link_up() and mac_link_down() callbacks call it through the family
info.
No functional change.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Tested-by: Stanislaw Pal <kuncy7@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb.h | 3 ++
drivers/net/dsa/realtek/rtl8365mb_main.c | 44 ++++++++++++++----------
2 files changed, 29 insertions(+), 18 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h
index daa8f0b..664bddb 100644
--- a/drivers/net/dsa/realtek/rtl8365mb.h
+++ b/drivers/net/dsa/realtek/rtl8365mb.h
@@ -16,6 +16,9 @@ struct rtl8365mb_family_info {
unsigned int num_ports;
const struct rtl8365mb_jam_tbl_entry *jam_table;
size_t jam_size;
+ int (*ext_config_forcemode)(struct realtek_priv *priv, int port,
+ bool link, int speed, int duplex,
+ bool tx_pause, bool rx_pause);
};
const struct rtl8365mb_family_info *
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index e25eb7e..ef75dc2 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -348,15 +348,15 @@
#define RTL8365MB_PORT_SPEED_1000M 2
/* External interface force configuration registers 0~2 */
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 0x1311 /* EXT1 */
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 0x13C4 /* EXT2 */
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(_extint) \
- ((_extint) == 0 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 : \
- (_extint) == 1 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 : \
- (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 : \
+#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */
+#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG1 0x1311 /* EXT1 */
+#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG2 0x13C4 /* EXT2 */
+#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG(_extint) \
+ ((_extint) == 0 ? RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG0 : \
+ (_extint) == 1 ? RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG1 : \
+ (_extint) == 2 ? RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG2 : \
0x0)
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK BIT(12)
+#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_EN_MASK BIT(12)
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK BIT(7)
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK BIT(6)
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK BIT(5)
@@ -705,11 +705,16 @@ struct rtl8365mb_extint {
unsigned int supported_interfaces;
};
+static int rtl8365mb_c_ext_config_forcemode(struct realtek_priv *priv, int port,
+ bool link, int speed, int duplex,
+ bool tx_pause, bool rx_pause);
+
static const struct rtl8365mb_family_info rtl8365mb_family_c = {
.family = RTL8365MB_FAMILY_C,
.num_ports = RTL8365MB_C_MAX_NUM_PORTS,
.jam_table = rtl8365mb_init_jam_8365mb_vc,
.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
+ .ext_config_forcemode = rtl8365mb_c_ext_config_forcemode,
};
/**
@@ -1633,9 +1638,9 @@ static const struct phylink_pcs_ops rtl8365mb_pcs_ops = {
.pcs_link_up = rtl8365mb_pcs_link_up,
};
-static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
- bool link, int speed, int duplex,
- bool tx_pause, bool rx_pause)
+static int rtl8365mb_c_ext_config_forcemode(struct realtek_priv *priv, int port,
+ bool link, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
{
const struct rtl8365mb_extint *extint =
rtl8365mb_get_port_extint(priv, port);
@@ -1690,7 +1695,7 @@ 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) |
+ val = FIELD_PREP(RTL8365MB_C_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) |
FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK,
r_tx_pause) |
FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK,
@@ -1700,7 +1705,7 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
r_duplex) |
FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed);
ret = regmap_write(priv->map,
- RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id),
+ RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG(extint->id),
val);
if (ret)
return ret;
@@ -1816,12 +1821,14 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct realtek_priv *priv = dp->ds->priv;
+ const struct rtl8365mb_family_info *fi;
struct rtl8365mb_port *p;
struct rtl8365mb *mb;
u8 port = dp->index;
int ret;
mb = priv->chip_data;
+ fi = rtl8365mb_get_family_info(priv);
p = &mb->ports[port];
cancel_delayed_work_sync(&p->mib_work);
@@ -1832,8 +1839,8 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
*/
if (phy_interface_mode_is_rgmii(interface) ||
rtl8365mb_interface_is_serdes(interface)) {
- ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0,
- false, false);
+ ret = fi->ext_config_forcemode(priv, port, false, 0, 0,
+ false, false);
if (ret)
dev_err(priv->dev,
"failed to reset forced mode on port %d: %pe\n",
@@ -1852,12 +1859,14 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct realtek_priv *priv = dp->ds->priv;
+ const struct rtl8365mb_family_info *fi;
struct rtl8365mb_port *p;
struct rtl8365mb *mb;
u8 port = dp->index;
int ret;
mb = priv->chip_data;
+ fi = rtl8365mb_get_family_info(priv);
p = &mb->ports[port];
schedule_delayed_work(&p->mib_work, 0);
@@ -1867,9 +1876,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
*/
if (phy_interface_mode_is_rgmii(interface) ||
rtl8365mb_interface_is_serdes(interface)) {
- ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed,
- duplex, tx_pause,
- rx_pause);
+ ret = fi->ext_config_forcemode(priv, port, true, speed,
+ duplex, tx_pause, rx_pause);
if (ret) {
dev_err(priv->dev,
"failed to force mode on port %d: %pe\n", port,
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 04/12] net: dsa: realtek: rtl8365mb: set speed for family D
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (2 preceding siblings ...)
2026-11-12 17:33 ` [PATCH net-next v10 03/12] net: dsa: realtek: rtl8365mb: refactor forcemode handling Mieczyslaw Nalewaj
@ 2026-11-12 17:34 ` Mieczyslaw Nalewaj
2026-11-12 17:35 ` [PATCH net-next v10 05/12] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
` (7 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:34 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
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.
On family D the forced-mode configuration (link, duplex, pause, speed)
is written to a per-port register at 0x12c0 + port instead of the
per-extint register. The bit positions of TXPAUSE, RXPAUSE, LINK, DUPLEX
and the low two speed bits are the same as on family C; the upper speed
bits go to bits 13:12 (FORCE_SPEED2), so FORCE_EN (bit 12 on family C)
is not set. A second per-port register at 0x12c8 + port enables the
forced mode: the vendor RTL8367D API writes 0xFFFF there when the MAC
is forced and 0 otherwise. This driver always forces the MAC, as on
family C, so it writes 0xFFFF on every call, including link down.
Add the family D implementation as rtl8365mb_d_ext_config_forcemode().
The shared code reaches it through the ext_config_forcemode pointer in
struct rtl8365mb_family_info, so it needs no is_d() check.
Nothing points to it until the family D info is added together with the
chip table entry in the last patch. Until then it is not static and is
declared in rtl8365mb.h, so that the build does not warn about an unused
function.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/realtek/rtl8365mb.h | 4 +
drivers/net/dsa/realtek/rtl8365mb_main.c | 110 +++++++++++++++++++++++
2 files changed, 114 insertions(+)
diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h
index 664bddb..3d607bd 100644
--- a/drivers/net/dsa/realtek/rtl8365mb.h
+++ b/drivers/net/dsa/realtek/rtl8365mb.h
@@ -24,6 +24,10 @@ struct rtl8365mb_family_info {
const struct rtl8365mb_family_info *
rtl8365mb_get_family_info(struct realtek_priv *priv);
+int rtl8365mb_d_ext_config_forcemode(struct realtek_priv *priv, int port,
+ bool link, int speed, int duplex,
+ bool tx_pause, bool rx_pause);
+
static inline bool rtl8365mb_is_d(struct realtek_priv *priv)
{
return rtl8365mb_get_family_info(priv)->family == RTL8365MB_FAMILY_D;
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index ef75dc2..27a6917 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -346,6 +346,8 @@
#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
+#define RTL8365MB_D_PORT_SPEED_MSB_MASK BIT(2)
/* External interface force configuration registers 0~2 */
#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */
@@ -364,6 +366,26 @@
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK BIT(2)
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK GENMASK(1, 0)
+/* bits[13:12]; bit 13 reserved, no defined speed value uses it yet */
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK GENMASK(13, 12)
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE 0x12c0
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(_port) \
+ (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE + (_port))
+
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE 0x12c8
+#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(_port) \
+ (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE + (_port))
+
+/* Per-port force-mode enable. The vendor RTL8367D API
+ * (dal_rtl8367d_port_macForceLink_set()) treats it as an on/off switch for
+ * the whole forced configuration: all bits set when the MAC is forced, zero
+ * when it is not. The meaning of the individual bits is not documented.
+ * This driver always forces the MAC, as on family C, so all bits are set
+ * on every call, including link down.
+ */
+#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
#define RTL8365MB_CPU_PORT_MASK_MASK RTL8365MB_PORT_MASK
@@ -1713,6 +1735,94 @@ static int rtl8365mb_c_ext_config_forcemode(struct realtek_priv *priv, int port,
return 0;
}
+int rtl8365mb_d_ext_config_forcemode(struct realtek_priv *priv, int port,
+ bool link, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ const struct rtl8365mb_extint *extint =
+ rtl8365mb_get_port_extint(priv, port);
+ u32 r_tx_pause;
+ u32 r_rx_pause;
+ u32 r_duplex;
+ u32 r_speed;
+ u32 r_link;
+ int val;
+ int ret;
+
+ if (!extint)
+ return -ENODEV;
+
+ 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;
+
+ if (speed == SPEED_2500) {
+ r_speed = RTL8365MB_D_PORT_SPEED_2500M;
+ } else if (speed == SPEED_1000) {
+ r_speed = RTL8365MB_PORT_SPEED_1000M;
+ } else if (speed == SPEED_100) {
+ r_speed = RTL8365MB_PORT_SPEED_100M;
+ } else if (speed == SPEED_10) {
+ r_speed = RTL8365MB_PORT_SPEED_10M;
+ } else {
+ dev_err(priv->dev, "unsupported port speed %s\n",
+ phy_speed_to_str(speed));
+ return -EINVAL;
+ }
+
+ if (duplex == DUPLEX_FULL) {
+ r_duplex = 1;
+ } else if (duplex == DUPLEX_HALF) {
+ r_duplex = 0;
+ } else {
+ dev_err(priv->dev, "unsupported duplex %s\n",
+ phy_duplex_to_str(duplex));
+ return -EINVAL;
+ }
+ } else {
+ /* Force the link down and reset any programmed configuration */
+ r_link = 0;
+ r_tx_pause = 0;
+ r_rx_pause = 0;
+ r_speed = 0;
+ r_duplex = 0;
+ }
+
+ 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 &
+ RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK);
+
+ /* Speed is 3 bits on family D: bits[1:0] go into FORCE_SPEED,
+ * bit[2] goes into FORCE_SPEED2 (register bit 12); only 2500M
+ * sets it. Register bit 13 is reserved.
+ */
+ val |= FIELD_PREP(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK,
+ FIELD_GET(RTL8365MB_D_PORT_SPEED_MSB_MASK,
+ r_speed));
+ 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;
+
+ return 0;
+}
+
static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 05/12] net: dsa: realtek: rtl8365mb: set RGMII mode for family D
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (3 preceding siblings ...)
2026-11-12 17:34 ` [PATCH net-next v10 04/12] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
@ 2026-11-12 17:35 ` Mieczyslaw Nalewaj
2026-11-12 17:36 ` [PATCH net-next v10 06/12] net: dsa: realtek: rtl8365mb: refactor VLAN 4k handling Mieczyslaw Nalewaj
` (6 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:35 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
On family D, RGMII on extension interface 1 needs three 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, the pin mux in the
top-level configuration register must be set up, and SerDes 1, which
shares the pins, must be disabled.
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 occupies extension 1, do not advertise RGMII
for port 7 in phylink_get_caps(), so the configuration is refused before
any EXT1 register is touched. This mirrors the vendor RTL8367D API, which
returns an error for RGMII on port 7 when UTP port 4 uses the RGMII
interface. The driver logs an error naming the conflict. RGMII is the
only interface of port 7 this driver implements, so port 7 then has no
supported interface: phylink_create() fails for it, and if it is a CPU
port the switch does not probe.
When MAC4 does not own the pins, bind the RGMII/MII pins to MAC7 and
disable SerDes 1, which shares them. The TX delay field, the pin mux and
the SerDes 1 disable are all programmed before RGMII is selected, so the
shared pins are never driven by RGMII and SerDes 1 at the same time.
The family D branch is not reachable until the chip table entry is
added in the last patch.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 70 +++++++++++++++++++++++-
1 file changed, 68 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 27a6917..bdc29d0 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -334,6 +334,12 @@
#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249
#define RTL8365MB_SDS_OPTION_REG 0x13C1
+#define RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK GENMASK(4, 0)
+/* Shared "disable" encoding for both SDS_MISC's and SDS1_MISC0's
+ * 5-bit mode fields.
+ */
+#define RTL8365MB_D_PORT_SDS_MODE_DISABLE 0x1f
+
/* Embedded DW8051 microcontroller control registers. The microcontroller
* can run firmware to manage the SerDes link, but this driver keeps it in
* reset and disabled: phylink already performs the link management that
@@ -466,6 +472,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
@@ -1263,6 +1283,32 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
if (ret)
return ret;
+ if (rtl8365mb_is_d(priv) && 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;
+
+ /* Bind the RGMII/MII pins to MAC7. MAC4 does not own them:
+ * RGMII is not advertised otherwise, see
+ * rtl8365mb_phylink_get_caps().
+ */
+ 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;
+ }
+
ret = regmap_update_bits(
priv->map, RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(extint->id),
RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(extint->id),
@@ -1830,6 +1876,8 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
rtl8365mb_get_port_extint(ds->priv, port);
struct realtek_priv *priv = ds->priv;
struct rtl8365mb *mb = priv->chip_data;
+ u32 data;
+ int ret;
config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000FD;
@@ -1859,8 +1907,26 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
* which remain unimplemented.
*/
- if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII)
- phy_interface_set_rgmii(config->supported_interfaces);
+ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) {
+ /* On family D the EXT1 pins are shared with MAC4 (UTP port 4).
+ * If MAC4 owns them, RGMII on this port cannot work.
+ */
+ if (rtl8365mb_is_d(priv) && extint->id == 1) {
+ ret = regmap_read(priv->map, RTL8365MB_D_REG_TOP_CON0, &data);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to read TOP_CON0, RGMII on port %d disabled: %pe\n",
+ port, ERR_PTR(ret));
+ else if (data & RTL8365MB_D_MAC4_SEL_EXT1_MASK)
+ dev_err(priv->dev,
+ "port %d: EXT1 is owned by MAC4, RGMII cannot be used\n",
+ port);
+ else
+ phy_interface_set_rgmii(config->supported_interfaces);
+ } else {
+ phy_interface_set_rgmii(config->supported_interfaces);
+ }
+ }
if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII &&
mb->sds_supported)
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 06/12] net: dsa: realtek: rtl8365mb: refactor VLAN 4k handling
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (4 preceding siblings ...)
2026-11-12 17:35 ` [PATCH net-next v10 05/12] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
@ 2026-11-12 17:36 ` Mieczyslaw Nalewaj
2026-11-12 20:24 ` Linus Walleij
2026-11-12 17:37 ` [PATCH net-next v10 07/12] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D Mieczyslaw Nalewaj
` (5 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:36 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
Move the family-specific VLAN 4k table accessors behind struct
rtl8365mb_family_info, so that family D can provide its own versions
without is_d() checks in the shared code. The existing code becomes the
family C implementation and is otherwise unchanged: rename the
functions to rtl8365mb_c_vlan_4k_read() and rtl8365mb_c_vlan_4k_write(),
export them and call them through the family info.
No functional change.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Tested-by: Stanislaw Pal <kuncy7@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb.h | 5 +++++
drivers/net/dsa/realtek/rtl8365mb_main.c | 2 ++
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 21 ++++++++++++---------
drivers/net/dsa/realtek/rtl8365mb_vlan.h | 7 +++++++
4 files changed, 26 insertions(+), 9 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h
index 3d607bd..d3d5fb3 100644
--- a/drivers/net/dsa/realtek/rtl8365mb.h
+++ b/drivers/net/dsa/realtek/rtl8365mb.h
@@ -10,6 +10,7 @@ enum rtl8365mb_family {
};
struct rtl8365mb_jam_tbl_entry;
+struct rtl8365mb_vlan4k;
struct rtl8365mb_family_info {
enum rtl8365mb_family family;
@@ -19,6 +20,10 @@ struct rtl8365mb_family_info {
int (*ext_config_forcemode)(struct realtek_priv *priv, int port,
bool link, int speed, int duplex,
bool tx_pause, bool rx_pause);
+ int (*vlan_4k_read)(struct realtek_priv *priv, u16 vid,
+ struct rtl8365mb_vlan4k *vlan4k);
+ int (*vlan_4k_write)(struct realtek_priv *priv,
+ const struct rtl8365mb_vlan4k *vlan4k);
};
const struct rtl8365mb_family_info *
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index bdc29d0..611e58c 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -757,6 +757,8 @@ static const struct rtl8365mb_family_info rtl8365mb_family_c = {
.jam_table = rtl8365mb_init_jam_8365mb_vc,
.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
.ext_config_forcemode = rtl8365mb_c_ext_config_forcemode,
+ .vlan_4k_read = rtl8365mb_c_vlan_4k_read,
+ .vlan_4k_write = rtl8365mb_c_vlan_4k_write,
};
/**
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 8d49ffa..566c6bc 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -48,12 +48,13 @@
#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_C_CVLAN_ENTRY_SIZE 3 /* 48-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)
@@ -182,10 +183,10 @@ struct rtl8365mb_vlanmc {
u8 meteridx : 6;
};
-static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
- struct rtl8365mb_vlan4k *vlan4k)
+int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid,
+ struct rtl8365mb_vlan4k *vlan4k)
{
- u16 data[RTL8365MB_CVLAN_ENTRY_SIZE];
+ u16 data[RTL8365MB_C_CVLAN_ENTRY_SIZE];
int val;
int ret;
@@ -230,10 +231,10 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
return 0;
}
-static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,
- const struct rtl8365mb_vlan4k *vlan4k)
+int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv,
+ const struct rtl8365mb_vlan4k *vlan4k)
{
- u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 };
+ u16 data[RTL8365MB_C_CVLAN_ENTRY_SIZE] = { 0 };
u16 vid;
int val;
@@ -282,6 +283,7 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack,
bool include)
{
+ const struct rtl8365mb_family_info *fi;
struct realtek_priv *priv = ds->priv;
struct rtl8365mb_vlan4k vlan4k = {0};
int ret;
@@ -296,7 +298,8 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
return -EINVAL;
}
- ret = rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k);
+ fi = rtl8365mb_get_family_info(priv);
+ ret = fi->vlan_4k_read(priv, vlan->vid, &vlan4k);
if (ret) {
dev_err(priv->dev, "Failed to read VLAN 4k table\n");
return ret;
@@ -313,7 +316,7 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
vlan4k.untag &= ~BIT(port);
vlan4k.ivl_en = true; /* always use Independent VLAN Learning */
- ret = rtl8365mb_vlan_4k_write(priv, &vlan4k);
+ ret = fi->vlan_4k_write(priv, &vlan4k);
if (ret) {
dev_err(priv->dev, "Failed to write VLAN 4k table\n");
return ret;
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
index 27a526b..009b127 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
@@ -18,6 +18,13 @@ enum rtl8365mb_frame_ingress {
RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY,
};
+struct rtl8365mb_vlan4k;
+
+int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid,
+ struct rtl8365mb_vlan4k *vlan4k);
+int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv,
+ const struct rtl8365mb_vlan4k *vlan4k);
+
int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port,
u16 *pvid);
int
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 07/12] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (5 preceding siblings ...)
2026-11-12 17:36 ` [PATCH net-next v10 06/12] net: dsa: realtek: rtl8365mb: refactor VLAN 4k handling Mieczyslaw Nalewaj
@ 2026-11-12 17:37 ` Mieczyslaw Nalewaj
2026-11-12 20:25 ` Linus Walleij
2026-11-12 17:38 ` [PATCH net-next v10 08/12] net: dsa: realtek: rtl8365mb: use raw VID for PVID on " Mieczyslaw Nalewaj
` (4 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:37 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
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.
IVL_EN (bit 3, versus bit 14 on family C) follows vlan4k->ivl_en. On
family D, rtl8365mb_vlan_4k_port_set() enables it while any user port
has VLAN filtering enabled (dsa_port_is_vlan_filtering()), so that
static and learned FDB/MDB entries use the same key. Family C keeps IVL
always enabled, as before.
The stored IVL_EN is refreshed on the next port_vlan_add/del for the
VID, not on a vlan_filtering change.
Reusing the three-word family C layout on family D would read a third
word that does not exist as a distinct table word on family D's die.
Whatever data happens to be in that register would be folded into
member/untag bits [10:8] on read, and written back on every
read-modify-write cycle in rtl8365mb_vlan_4k_port_set(). Give family D
its own two-word pack/unpack instead: add rtl8365mb_d_vlan_4k_read() and
rtl8365mb_d_vlan_4k_write(). The shared code reaches them through the
vlan_4k_read and vlan_4k_write pointers in struct rtl8365mb_family_info,
so the pack/unpack code needs no is_d() check.
The two-word layout matches the vendor switch API for this chip
family, which writes only 0x0510/0x0511 for a CVLAN entry.
Nothing points to them until the family D info is added together with
the chip table entry in the last patch. Until then they are not static
and are declared in rtl8365mb_vlan.h, so that the build does not warn
about unused functions.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 95 +++++++++++++++++++++++-
drivers/net/dsa/realtek/rtl8365mb_vlan.h | 4 +
2 files changed, 97 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 566c6bc..bf1a6a9 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -53,8 +53,11 @@
#include <linux/lockdep.h>
#include <linux/regmap.h>
-/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */
+/* CVLAN (i.e. VLAN4k) table entry layout: u16[3] on family C, u16[2] on
+ * family D
+ */
#define RTL8365MB_C_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)
@@ -66,6 +69,9 @@
#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)
+/* Family D CVLAN entry, word 1 (family D has no word 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)
@@ -231,6 +237,45 @@ int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid,
return 0;
}
+int rtl8365mb_d_vlan_4k_read(struct realtek_priv *priv, u16 vid,
+ struct rtl8365mb_vlan4k *vlan4k)
+{
+ u16 data[RTL8365MB_D_CVLAN_ENTRY_SIZE];
+ int val;
+ int ret;
+
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
+ RTL8365MB_TABLE_OP_READ, &vid, 0, 0,
+ data, RTL8365MB_D_CVLAN_ENTRY_SIZE);
+ if (ret)
+ return ret;
+
+ /* Unpack table entry */
+ 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_D0_UNTAG_MASK, data[0]);
+ vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val);
+
+ 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]);
+
+ return 0;
+}
+
int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv,
const struct rtl8365mb_vlan4k *vlan4k)
{
@@ -277,6 +322,38 @@ int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv,
data, ARRAY_SIZE(data));
}
+int rtl8365mb_d_vlan_4k_write(struct realtek_priv *priv,
+ const struct rtl8365mb_vlan4k *vlan4k)
+{
+ /* Unknown bits of word 1 are written as zero, same as the vendor
+ * API does for this chip family.
+ */
+ u16 data[RTL8365MB_D_CVLAN_ENTRY_SIZE] = { 0 };
+ u16 vid;
+ int val;
+
+ /* Pack table entry value */
+ val = FIELD_GET(RTL8365MB_CVLAN_MBR_LO_MASK, vlan4k->member);
+ data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, val);
+
+ 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_D_CVLAN_ENTRY_D1_IVL_EN_MASK,
+ vlan4k->ivl_en);
+ 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.
+ */
+
+ vid = vlan4k->vid;
+ return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
+ RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0,
+ data, RTL8365MB_D_CVLAN_ENTRY_SIZE);
+}
+
static int
rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
@@ -314,7 +391,21 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
vlan4k.untag |= BIT(port);
else
vlan4k.untag &= ~BIT(port);
- vlan4k.ivl_en = true; /* always use Independent VLAN Learning */
+
+ /* Family C always uses IVL. On family D, IVL is on while any user
+ * port has VLAN filtering enabled, so that static entries match
+ * learned ones in a VLAN-unaware bridge (SVL).
+ */
+ if (!rtl8365mb_is_d(priv)) {
+ vlan4k.ivl_en = true;
+ } else {
+ struct dsa_port *dp;
+
+ vlan4k.ivl_en = false;
+ dsa_switch_for_each_user_port(dp, ds)
+ if (dsa_port_is_vlan_filtering(dp))
+ vlan4k.ivl_en = true;
+ }
ret = fi->vlan_4k_write(priv, &vlan4k);
if (ret) {
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
index 009b127..012c07d 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
@@ -24,6 +24,10 @@ int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid,
struct rtl8365mb_vlan4k *vlan4k);
int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv,
const struct rtl8365mb_vlan4k *vlan4k);
+int rtl8365mb_d_vlan_4k_read(struct realtek_priv *priv, u16 vid,
+ struct rtl8365mb_vlan4k *vlan4k);
+int rtl8365mb_d_vlan_4k_write(struct realtek_priv *priv,
+ const struct rtl8365mb_vlan4k *vlan4k);
int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port,
u16 *pvid);
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 08/12] net: dsa: realtek: rtl8365mb: use raw VID for PVID on family D
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (6 preceding siblings ...)
2026-11-12 17:37 ` [PATCH net-next v10 07/12] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D Mieczyslaw Nalewaj
@ 2026-11-12 17:38 ` Mieczyslaw Nalewaj
2026-11-12 17:39 ` [PATCH net-next v10 09/12] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
` (3 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:38 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
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.
Also clear the family D PVID registers when the VLAN support is set up.
Unlike on family C, where VLANMC index 0 is the neutral PVID, the
register holds a raw VID, and after a reset it keeps its hardware (or
EEPROM) default. Without clearing it an unconfigured port could report
a stale PVID from rtl8365mb_vlan_port_get_pvid(), and
rtl8365mb_port_vlan_filtering() would accept untagged frames into it.
No functional change for family C.
The family D branch is not reachable until the chip table entry is
added in the last patch.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 9 +
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 205 ++++++++++++++++++++++-
drivers/net/dsa/realtek/rtl8365mb_vlan.h | 1 +
3 files changed, 210 insertions(+), 5 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 611e58c..a13a5e9 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -2420,6 +2420,15 @@ static int rtl8365mb_vlan_setup(struct dsa_switch *ds)
int ret;
dsa_switch_for_each_available_port(dp, ds) {
+ /* Start every port with the neutral PVID */
+ ret = rtl8365mb_vlan_port_reset_pvid(priv, dp->index);
+ if (ret) {
+ dev_err(priv->dev,
+ "Failed to reset PVID on port %d\n",
+ dp->index);
+ return ret;
+ }
+
/* Disable vlan-filtering for all ports */
ret = rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL);
if (ret) {
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index bf1a6a9..7d805c6 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"
@@ -120,6 +124,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) \
@@ -171,7 +180,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
@@ -773,14 +782,51 @@ 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_is_d(priv)) {
+ 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;
}
+/**
+ * rtl8365mb_vlan_port_reset_pvid() - Put a port's PVID in its neutral state
+ * @priv: realtek_priv pointer
+ * @port: the port number
+ *
+ * On family C the neutral PVID is VLANMC index 0, which is the reset value, so
+ * there is nothing to do. On family D the PVID is a raw VID in a per-port
+ * register whose value after reset is not defined by the driver (it may come
+ * from the hardware default or from an EEPROM), so clear it to 0.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int rtl8365mb_vlan_port_reset_pvid(struct realtek_priv *priv, int port)
+{
+ if (!rtl8365mb_is_d(priv))
+ return 0;
+
+ return regmap_update_bits(priv->map,
+ RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_D_VLAN_PVID_CTRL_MASK, 0);
+}
+
/*
* rtl8365mb_vlan_port_get_framefilter() - Get the ingress frame filtering mode
* for a port
@@ -842,6 +888,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
@@ -854,6 +962,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.
@@ -871,6 +982,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_is_d(priv))
+ 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);
@@ -950,6 +1068,77 @@ 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 =
+ RTL8365MB_FRAME_TYPE_ANY_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
@@ -959,6 +1148,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.
@@ -973,6 +1165,9 @@ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
lockdep_assert_held(&priv->vlan_lock);
+ if (rtl8365mb_is_d(priv))
+ return rtl8365mb_vlan_pvid_port_clear_direct(ds, port, vid);
+
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx,
&vlanmc);
if (ret) {
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
index 012c07d..c84b108 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
@@ -31,6 +31,7 @@ int rtl8365mb_d_vlan_4k_write(struct realtek_priv *priv,
int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port,
u16 *pvid);
+int rtl8365mb_vlan_port_reset_pvid(struct realtek_priv *priv, int port);
int
rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv,
int port,
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 09/12] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (7 preceding siblings ...)
2026-11-12 17:38 ` [PATCH net-next v10 08/12] net: dsa: realtek: rtl8365mb: use raw VID for PVID on " Mieczyslaw Nalewaj
@ 2026-11-12 17:39 ` Mieczyslaw Nalewaj
2026-11-12 17:40 ` [PATCH net-next v10 10/12] net: dsa: realtek: rtl8365mb: refactor L2 MAC handling Mieczyslaw Nalewaj
` (2 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:39 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
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.
This patch only prepares the family D paths; they become reachable
when the RTL8367S-VB chip entry is added in the last patch.
On family D rtl8365mb_sds_probe_option() does not read the family C
option register and always sets sds_supported, so the port 6 rate limits
are raised for every family D chip. The only family D chip in this
series, the RTL8367S-VB, has a SerDes interface on port 6.
Nothing on family D programs a SerDes rate for 10 and 100 Mbit/s:
pcs_link_up() does nothing and the MAC force register only sets the MAC
side. It is not known whether the SerDes follows that register, and only
1G SGMII and 2500base-x were tested, so do not advertise 10 and 100 Mbit/s
on the SerDes port of family D.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Tested-by: Stanislaw Pal <kuncy7@gmail.com>
Co-developed-by: Fabiano Tassotti <fabianotassotti@gmail.com>
Signed-off-by: Fabiano Tassotti <fabianotassotti@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 257 ++++++++++++++++++-----
1 file changed, 199 insertions(+), 58 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index a13a5e9..6f9c12f 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -264,7 +264,8 @@
* interface, indexed by the external port number with port 5 (the first
* external port) as the base. Other RTL8367 families index this register
* differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only
- * holds for the RTL8367C-style parts this driver supports.
+ * holds for the RTL8367C-style parts and for family D, where it is the same
+ * (one bit per external port, base port 5), as in the vendor RTL8367D API.
*/
#define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7
#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5)
@@ -286,6 +287,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
@@ -315,8 +317,10 @@
#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d
#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK BIT(4)
-/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
- * which is port 6.
+/* On family C the embedded SerDes can only be muxed to external interface
+ * 1 (MAC8), which is port 6. Family D has it on external interface 0
+ * (MAC6), see RTL8365MB_D_SDS_EXT0_INDEX. RTL8365MB_SDS_EXT_INTERFACE_ID
+ * below is the family C id; the SerDes port is 6 on both families.
*/
#define RTL8365MB_SDS_EXT_INTERFACE_ID 1
#define RTL8365MB_SDS_EXT_INTERFACE_PORT 6
@@ -334,7 +338,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.
*/
@@ -721,6 +740,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),
@@ -909,8 +943,9 @@ struct rtl8365mb_port {
* @mib_lock: prevent concurrent reads of MIB counters
* @ports: per-port data
* @pcs: PCS for the SerDes external interface
- * @sds_supported: SerDes tuning parameters match the chip option, so the
- * SerDes interface modes can be advertised
+ * @sds_supported: SerDes interface modes can be advertised: the tuning
+ * parameters match the chip option (family C), or the chip
+ * is family D, which does not use the option
*
* Private data for this driver.
*/
@@ -1323,7 +1358,8 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
return 0;
}
-static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)
+static int rtl8365mb_sds_write(struct realtek_priv *priv, u8 index,
+ u16 addr, u16 data)
{
int ret;
@@ -1341,10 +1377,13 @@ static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)
*/
return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
- RTL8365MB_SDS_INDACS_CMD_WR_MASK);
+ RTL8365MB_SDS_INDACS_CMD_WR_MASK |
+ FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
+ index));
}
-static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
+static int rtl8365mb_sds_read(struct realtek_priv *priv, u8 index,
+ u16 addr, u16 *data)
{
u32 val;
int ret;
@@ -1354,7 +1393,9 @@ static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
return ret;
ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
- RTL8365MB_SDS_INDACS_CMD_RUN_MASK);
+ RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+ FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK,
+ index));
if (ret)
return ret;
@@ -1394,6 +1435,14 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
int ret;
int i;
+ /* Family D has a fixed SDS13 programming model and does not use the
+ * family C option register to select its tuning table.
+ */
+ if (rtl8365mb_is_d(priv)) {
+ 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];
@@ -1475,25 +1524,44 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
const struct rtl8365mb_jam_tbl_entry *sds_jam;
const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
- struct realtek_priv *priv;
+ 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_is_d(priv);
+ /* 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
@@ -1522,34 +1590,58 @@ 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) {
+ /* The register and field names follow the vendor RTL8367D API,
+ * whose SDS numbering differs from the SDS13 window used
+ * here. As in the vendor sequence the whole 2-bit field
+ * (bits 7:6) is written, so bit 7 is cleared as well.
+ */
+ 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;
@@ -1559,27 +1651,32 @@ 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;
/* Keep SGMII in-band autonegotiation disabled: the link parameters are
- * forced from rtl8365mb_pcs_link_up() instead.
+ * forced from rtl8365mb_pcs_link_up() instead (family C), or from
+ * mac_link_up() (family D).
*/
- 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)
@@ -1604,18 +1701,27 @@ 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;
- /* 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
+ is_d = rtl8365mb_is_d(priv);
+
+ /* In-band autonegotiation is not implemented, so the link parameters
+ * are forced from rtl8365mb_pcs_link_up() on family C, and from
+ * mac_link_up() on family D. The real link state must still be read
+ * from the SerDes itself: the embedded DW8051 microcontroller that
* the vendor firmware uses to poll the SerDes is kept disabled (see
* rtl8365mb_pcs_config()), so the link status register can be read
* directly through the SDS_INDACS window without racing the auto-poll.
*/
- ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status);
+ if (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;
@@ -1626,6 +1732,23 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
if (!state->link)
return;
+ if (is_d) {
+ if (state->interface == PHY_INTERFACE_MODE_2500BASEX) {
+ state->duplex = DUPLEX_FULL;
+ state->speed = SPEED_2500;
+ } else {
+ /* Keep what phylink passed in, but never report the
+ * link up with an unknown speed: mac_link_up() cannot
+ * force the port with it.
+ */
+ if (state->speed == SPEED_UNKNOWN)
+ state->speed = SPEED_1000;
+ if (state->duplex == DUPLEX_UNKNOWN)
+ state->duplex = DUPLEX_FULL;
+ }
+ return;
+ }
+
/* The speed and duplex are forced; read them back from the values
* programmed into the SerDes MISC register.
*/
@@ -1667,6 +1790,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_is_d(priv))
+ 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.
@@ -1931,10 +2060,17 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
}
if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII &&
- mb->sds_supported)
+ mb->sds_supported) {
__set_bit(PHY_INTERFACE_MODE_SGMII,
config->supported_interfaces);
+ /* The SerDes rate is not programmed for 10 and 100 Mbit/s on
+ * family D, see rtl8365mb_pcs_link_up().
+ */
+ if (rtl8365mb_is_d(priv))
+ config->mac_capabilities &= ~(MAC_10 | MAC_100);
+ }
+
if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII &&
mb->sds_supported) {
__set_bit(PHY_INTERFACE_MODE_2500BASEX,
@@ -2013,7 +2149,7 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
/* phylink has no pcs_link_down callback, so on the SerDes path only the
* MAC external interface force is reset here. Clearing the MAC force is
* enough to bring the link down; the SerDes keeps presenting its last
- * forced state until the next pcs_link_up() reprograms it.
+ * forced state (family C) until the next pcs_link_up() reprograms it.
*/
if (phy_interface_mode_is_rgmii(interface) ||
rtl8365mb_interface_is_serdes(interface)) {
@@ -2048,9 +2184,11 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
p = &mb->ports[port];
schedule_delayed_work(&p->mib_work, 0);
- /* The SerDes forced link state is programmed by the PCS in
+ /* On family C the SerDes forced link state is programmed by the PCS in
* rtl8365mb_pcs_link_up(); here only the MAC external interface force
- * is configured, for both RGMII and SerDes.
+ * is configured. On family D pcs_link_up() does nothing, and the MAC
+ * external interface force is the only forced link state, for both
+ * RGMII and SerDes.
*/
if (phy_interface_mode_is_rgmii(interface) ||
rtl8365mb_interface_is_serdes(interface)) {
@@ -2063,18 +2201,21 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
return;
}
- /* The SerDes has its own pause enables; program them from
- * the resolved pause modes, as the vendor driver does when
- * forcing the link on a SerDes external interface. These
- * bits, not the MAC force pause bits, gate pause on the
- * SerDes external interface: flow control testing shows
- * that pause frames are only emitted with the SerDes TXFC
- * bit set, while the MAC force pause bits alone have no
- * effect on this port. This is done here rather than in
- * rtl8365mb_pcs_link_up() because pcs_link_up() carries no
- * pause information.
+ /* Family C: the SerDes has its own pause enables; program
+ * them from the resolved pause modes, as the vendor driver
+ * does when forcing the link on a SerDes external
+ * interface. These bits, not the MAC force pause bits, gate
+ * pause on the SerDes external interface: flow control
+ * testing shows that pause frames are only emitted with the
+ * SerDes TXFC bit set, while the MAC force pause bits alone
+ * have no effect on this port. This is done here rather
+ * than in rtl8365mb_pcs_link_up() because pcs_link_up()
+ * carries no pause information. Family D does not use this
+ * path: its pause bits are set in the forced-mode register
+ * by rtl8365mb_d_ext_config_forcemode().
*/
- if (rtl8365mb_interface_is_serdes(interface)) {
+ if (rtl8365mb_interface_is_serdes(interface) &&
+ !rtl8365mb_is_d(priv)) {
u32 val = 0;
if (tx_pause)
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 10/12] net: dsa: realtek: rtl8365mb: refactor L2 MAC handling
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (8 preceding siblings ...)
2026-11-12 17:39 ` [PATCH net-next v10 09/12] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
@ 2026-11-12 17:40 ` Mieczyslaw Nalewaj
2026-11-12 20:26 ` Linus Walleij
2026-11-12 17:41 ` [PATCH net-next v10 11/12] net: dsa: realtek: rtl8365mb: make L2/LUT access work on family D Mieczyslaw Nalewaj
2026-11-12 17:42 ` [PATCH net-next v10 12/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
11 siblings, 1 reply; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:40 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
Add L2 MAC pack/unpack helpers and use them in the family C
serializers instead of open-coding the byte packing, since the layout
of the MAC address words is the same for both families. The now unused
per-field MAC masks are dropped.
Rename the family C serializers to rtl8365mb_c_l2_*(), export them and
reach them through function pointers in struct rtl8365mb_family_info,
so that a following patch can add the family D layout without touching
the family C code again.
No functional change.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Tested-by: Stanislaw Pal <kuncy7@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/rtl8365mb.h | 7 ++
drivers/net/dsa/realtek/rtl8365mb_l2.c | 125 +++++++++++------------
drivers/net/dsa/realtek/rtl8365mb_l2.h | 12 +++
drivers/net/dsa/realtek/rtl8365mb_main.c | 5 +
4 files changed, 83 insertions(+), 66 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h
index d3d5fb3..244e896 100644
--- a/drivers/net/dsa/realtek/rtl8365mb.h
+++ b/drivers/net/dsa/realtek/rtl8365mb.h
@@ -11,6 +11,8 @@ enum rtl8365mb_family {
struct rtl8365mb_jam_tbl_entry;
struct rtl8365mb_vlan4k;
+struct rtl8365mb_l2_uc;
+struct rtl8365mb_l2_mc;
struct rtl8365mb_family_info {
enum rtl8365mb_family family;
@@ -24,6 +26,11 @@ struct rtl8365mb_family_info {
struct rtl8365mb_vlan4k *vlan4k);
int (*vlan_4k_write)(struct realtek_priv *priv,
const struct rtl8365mb_vlan4k *vlan4k);
+ size_t l2_entry_size;
+ void (*l2_data_to_uc)(const u16 *data, struct rtl8365mb_l2_uc *uc);
+ void (*l2_uc_to_data)(const struct rtl8365mb_l2_uc *uc, u16 *data);
+ void (*l2_data_to_mc)(const u16 *data, struct rtl8365mb_l2_mc *mc);
+ void (*l2_mc_to_data)(const struct rtl8365mb_l2_mc *mc, u16 *data);
};
const struct rtl8365mb_family_info *
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c
index 0494d8a..cd5b333 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_l2.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c
@@ -6,18 +6,11 @@
#include <linux/etherdevice.h>
+#include "rtl8365mb.h"
#include "rtl8365mb_l2.h"
#include "rtl8365mb_table.h"
#include <linux/regmap.h>
-#define RTL8365MB_L2_ENTRY_SIZE 6
-
-#define RTL8365MB_L2_UC_D0_MAC5_MSK GENMASK(7, 0)
-#define RTL8365MB_L2_UC_D0_MAC4_MSK GENMASK(15, 8)
-#define RTL8365MB_L2_UC_D1_MAC3_MSK GENMASK(7, 0)
-#define RTL8365MB_L2_UC_D1_MAC2_MSK GENMASK(15, 8)
-#define RTL8365MB_L2_UC_D2_MAC1_MSK GENMASK(7, 0)
-#define RTL8365MB_L2_UC_D2_MAC0_MSK GENMASK(15, 8)
#define RTL8365MB_L2_UC_D3_VID_MSK GENMASK(11, 0)
#define RTL8365MB_L2_UC_D3_IVL_MSK GENMASK(13, 13)
#define RTL8365MB_L2_UC_D3_PORT_EXT_MSK GENMASK(15, 15)
@@ -36,12 +29,6 @@
#define RTL8365MB_L2_UC_D5_FWD_PRI_MSK GENMASK(4, 4)
#define RTL8365MB_L2_UC_D5_STATIC_MSK GENMASK(5, 5)
-#define RTL8365MB_L2_MC_D0_MAC5_MSK GENMASK(7, 0)
-#define RTL8365MB_L2_MC_D0_MAC4_MSK GENMASK(15, 8)
-#define RTL8365MB_L2_MC_D1_MAC3_MSK GENMASK(7, 0)
-#define RTL8365MB_L2_MC_D1_MAC2_MSK GENMASK(15, 8)
-#define RTL8365MB_L2_MC_D2_MAC1_MSK GENMASK(7, 0)
-#define RTL8365MB_L2_MC_D2_MAC0_MSK GENMASK(15, 8)
#define RTL8365MB_L2_MC_D3_VID_MSK GENMASK(11, 0)
#define RTL8365MB_L2_MC_D3_IVL_MSK GENMASK(13, 13)
#define RTL8365MB_L2_MC_D3_MBR_HI1_MSK GENMASK(15, 14)
@@ -131,16 +118,29 @@ struct rtl8365mb_l2_mc {
bool igmp_asic;
};
-static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc)
+static void rtl8365mb_l2_mac_to_data(const u8 *m, u16 *data)
+{
+ data[0] = (m[4] << 8) | m[5];
+ data[1] = (m[2] << 8) | m[3];
+ data[2] = (m[0] << 8) | m[1];
+}
+
+static void rtl8365mb_l2_data_to_mac(const u16 *data, u8 *m)
+{
+ m[5] = data[0] & 0xff;
+ m[4] = data[0] >> 8;
+ m[3] = data[1] & 0xff;
+ m[2] = data[1] >> 8;
+ m[1] = data[2] & 0xff;
+ m[0] = data[2] >> 8;
+}
+
+void rtl8365mb_c_l2_data_to_uc(const u16 *data,
+ struct rtl8365mb_l2_uc *uc)
{
u32 val;
- uc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MSK, data[0]);
- uc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MSK, data[0]);
- uc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MSK, data[1]);
- uc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MSK, data[1]);
- uc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MSK, data[2]);
- uc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MSK, data[2]);
+ rtl8365mb_l2_data_to_mac(data, uc->key.mac_addr);
uc->key.efid = FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MSK, data[4]);
uc->key.vid = FIELD_GET(RTL8365MB_L2_UC_D3_VID_MSK, data[3]);
uc->key.ivl = FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MSK, data[3]);
@@ -161,23 +161,13 @@ static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc)
uc->is_static = FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MSK, data[5]);
}
-static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data)
+void rtl8365mb_c_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc,
+ u16 *data)
{
u32 val;
- memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2);
- data[0] |=
- FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MSK, uc->key.mac_addr[5]);
- data[0] |=
- FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MSK, uc->key.mac_addr[4]);
- data[1] |=
- FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MSK, uc->key.mac_addr[3]);
- data[1] |=
- FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MSK, uc->key.mac_addr[2]);
- data[2] |=
- FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MSK, uc->key.mac_addr[1]);
- data[2] |=
- FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MSK, uc->key.mac_addr[0]);
+ memset(data, 0, RTL8365MB_C_L2_ENTRY_SIZE * 2);
+ rtl8365mb_l2_mac_to_data(uc->key.mac_addr, data);
data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MSK, uc->key.vid);
data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MSK, uc->key.ivl);
@@ -200,16 +190,12 @@ static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data)
data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MSK, uc->is_static);
}
-static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc)
+void rtl8365mb_c_l2_data_to_mc(const u16 *data,
+ struct rtl8365mb_l2_mc *mc)
{
u32 val;
- mc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC5_MSK, data[0]);
- mc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC4_MSK, data[0]);
- mc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC3_MSK, data[1]);
- mc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC2_MSK, data[1]);
- mc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC1_MSK, data[2]);
- mc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC0_MSK, data[2]);
+ rtl8365mb_l2_data_to_mac(data, mc->key.mac_addr);
/* key.vid,key.fid shares the same memory space */
mc->key.vid = FIELD_GET(RTL8365MB_L2_MC_D3_VID_MSK, data[3]);
mc->key.ivl = FIELD_GET(RTL8365MB_L2_MC_D3_IVL_MSK, data[3]);
@@ -228,17 +214,13 @@ static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc)
mc->igmp_asic = FIELD_GET(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, data[5]);
}
-static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data)
+void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc,
+ u16 *data)
{
u32 val;
- memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2);
- data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC5_MSK, mc->key.mac_addr[5]);
- data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC4_MSK, mc->key.mac_addr[4]);
- data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC3_MSK, mc->key.mac_addr[3]);
- data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC2_MSK, mc->key.mac_addr[2]);
- data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC1_MSK, mc->key.mac_addr[1]);
- data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC0_MSK, mc->key.mac_addr[0]);
+ memset(data, 0, RTL8365MB_C_L2_ENTRY_SIZE * 2);
+ rtl8365mb_l2_mac_to_data(mc->key.mac_addr, data);
data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_VID_MSK, mc->key.vid);
data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_IVL_MSK, mc->key.ivl);
@@ -283,17 +265,20 @@ int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
struct realtek_fdb_entry *entry)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ const struct rtl8365mb_family_info *fi;
struct rtl8365mb_l2_uc uc;
int ret;
+ fi = rtl8365mb_get_family_info(priv);
+
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_READ, addr,
RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT,
- port, data, RTL8365MB_L2_ENTRY_SIZE);
+ port, data, fi->l2_entry_size);
if (ret)
return ret;
- rtl8365mb_l2_data_to_uc(data, &uc);
+ fi->l2_data_to_uc(data, &uc);
ether_addr_copy(entry->mac_addr, uc.key.mac_addr);
entry->vid = uc.key.vid;
@@ -307,6 +292,7 @@ int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
u16 efid, u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ const struct rtl8365mb_family_info *fi;
struct rtl8365mb_l2_uc uc = { 0 };
u16 addr;
int ret;
@@ -326,13 +312,14 @@ int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
uc.is_static = true;
/* age greater than 0 adds/updates entries */
uc.age = 1;
- rtl8365mb_l2_uc_to_data(&uc, data);
+ fi = rtl8365mb_get_family_info(priv);
+ fi->l2_uc_to_data(&uc, data);
/* add the new entry or update an existing one */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
- data, RTL8365MB_L2_ENTRY_SIZE);
+ data, fi->l2_entry_size);
/* Assume the missing new entry as the table is full */
if (ret == -ENOENT)
@@ -347,6 +334,7 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
u16 efid, u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ const struct rtl8365mb_family_info *fi;
struct rtl8365mb_l2_uc uc = { 0 };
u16 addr;
int ret;
@@ -358,7 +346,8 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
uc.key.vid = vid;
/* age 0 deletes the entry */
uc.age = 0;
- rtl8365mb_l2_uc_to_data(&uc, data);
+ fi = rtl8365mb_get_family_info(priv);
+ fi->l2_uc_to_data(&uc, data);
/* it looks like the switch will always add/update the entry,
* even when age is 0 or uc.key did not match an existing entry,
@@ -368,7 +357,7 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
- data, RTL8365MB_L2_ENTRY_SIZE);
+ data, fi->l2_entry_size);
if (ret == -ENOENT) {
dev_dbg(priv->dev, "%s: %pM vid=%d efid=%d missing\n",
@@ -451,6 +440,7 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ const struct rtl8365mb_family_info *fi;
struct rtl8365mb_l2_mc mc = { 0 };
u16 addr;
int ret;
@@ -463,16 +453,17 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
*/
mc.member |= BIT(port);
mc.is_static = 1;
- rtl8365mb_l2_mc_to_data(&mc, data);
+ fi = rtl8365mb_get_family_info(priv);
+ fi->l2_mc_to_data(&mc, data);
/* First look for an existing entry (to get existing port members) */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_READ, &addr,
RTL8365MB_TABLE_L2_METHOD_MAC, 0,
- data, RTL8365MB_L2_ENTRY_SIZE);
+ data, fi->l2_entry_size);
if (!ret) {
/* There is already an entry... */
- rtl8365mb_l2_data_to_mc(data, &mc);
+ fi->l2_data_to_mc(data, &mc);
dev_dbg(priv->dev,
"%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n",
__func__, mac_addr, addr, mc.member, mc.igmpidx,
@@ -487,7 +478,7 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
mc.is_static = 1;
}
- rtl8365mb_l2_mc_to_data(&mc, data);
+ fi->l2_mc_to_data(&mc, data);
} else if (ret == -ENOENT) {
/* New entry, no need to update data again as it already
* includes the member.
@@ -508,7 +499,7 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
- data, RTL8365MB_L2_ENTRY_SIZE);
+ data, fi->l2_entry_size);
/* Assume the missing new entry as the table is full */
if (ret == -ENOENT)
@@ -522,6 +513,7 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ const struct rtl8365mb_family_info *fi;
struct rtl8365mb_l2_mc mc = { 0 };
u16 addr;
int ret;
@@ -529,13 +521,14 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN);
mc.key.vid = vid;
mc.key.ivl = true;
- rtl8365mb_l2_mc_to_data(&mc, data);
+ fi = rtl8365mb_get_family_info(priv);
+ fi->l2_mc_to_data(&mc, data);
/* First look for an existing entry (to get existing port members) */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_READ, &addr,
RTL8365MB_TABLE_L2_METHOD_MAC, 0,
- data, RTL8365MB_L2_ENTRY_SIZE);
+ data, fi->l2_entry_size);
if (ret == -ENOENT) {
dev_dbg(priv->dev, "%s: %pM vid=%d missing\n",
__func__, mac_addr, vid);
@@ -547,7 +540,7 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
/* Return on any other error */
return ret;
- rtl8365mb_l2_data_to_mc(data, &mc);
+ fi->l2_data_to_mc(data, &mc);
dev_dbg(priv->dev,
"%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n",
__func__, mac_addr, addr, mc.member, mc.igmpidx,
@@ -565,12 +558,12 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
mc.fwd_pri = 0;
mc.igmp_asic = 0;
}
- rtl8365mb_l2_mc_to_data(&mc, data);
+ fi->l2_mc_to_data(&mc, data);
/* update the existing entry. */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
- data, RTL8365MB_L2_ENTRY_SIZE);
+ data, fi->l2_entry_size);
return ret;
}
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h
index 9470cf0..a9f9ae4 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_l2.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h
@@ -12,6 +12,18 @@
#include "realtek.h"
+#define RTL8365MB_C_L2_ENTRY_SIZE 6
+/* Highest L2 entry size of any supported chip, used for sizing */
+#define RTL8365MB_L2_ENTRY_SIZE RTL8365MB_C_L2_ENTRY_SIZE
+
+struct rtl8365mb_l2_uc;
+struct rtl8365mb_l2_mc;
+
+void rtl8365mb_c_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc);
+void rtl8365mb_c_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data);
+void rtl8365mb_c_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc);
+void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data);
+
int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
struct realtek_fdb_entry *entry);
int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 6f9c12f..525588e 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -793,6 +793,11 @@ static const struct rtl8365mb_family_info rtl8365mb_family_c = {
.ext_config_forcemode = rtl8365mb_c_ext_config_forcemode,
.vlan_4k_read = rtl8365mb_c_vlan_4k_read,
.vlan_4k_write = rtl8365mb_c_vlan_4k_write,
+ .l2_entry_size = RTL8365MB_C_L2_ENTRY_SIZE,
+ .l2_data_to_uc = rtl8365mb_c_l2_data_to_uc,
+ .l2_uc_to_data = rtl8365mb_c_l2_uc_to_data,
+ .l2_data_to_mc = rtl8365mb_c_l2_data_to_mc,
+ .l2_mc_to_data = rtl8365mb_c_l2_mc_to_data,
};
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 11/12] net: dsa: realtek: rtl8365mb: make L2/LUT access work on family D
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (9 preceding siblings ...)
2026-11-12 17:40 ` [PATCH net-next v10 10/12] net: dsa: realtek: rtl8365mb: refactor L2 MAC handling Mieczyslaw Nalewaj
@ 2026-11-12 17:41 ` Mieczyslaw Nalewaj
2026-11-12 17:42 ` [PATCH net-next v10 12/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:41 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
The L2 lookup table on family D switches (e.g. RTL8367S-VB) differs
from family C in ways that make the current FDB/MDB code misbehave:
- An L2 entry is 5 words instead of 6, with a different field layout
in words 3 and 4 (port, age, static and multicast member bits).
- There is no EFID and no per-bridge FDB isolation. The register at
0x0A32, used as the per-port EFID register on family C, is the LUT
aging timer on family D, so writing an EFID there corrupts it.
- In the table access interface, the SPA field is 3 bits wide and the
address register is 12 bits wide, with no address extension bit.
- Only ports 0..7 are present in the LUT and there is no
FLUSH_PORT_EXT register.
- A VLAN-unaware bridge reaches the driver with VID 0, where the chip
learns in SVL (IVL=0, FID=0). An entry programmed by DSA with IVL set
gets a different key than the learned one for the same MAC, so both
coexist and the learned one keeps winning.
Teach the table and L2 code about these differences. The L2 entry
layout is selected through struct rtl8365mb_family_info, the other
differences with rtl8365mb_is_d(). Family C behavior is unchanged.
Specifically:
- rtl8365mb_table: use the family D port mask and address width, and
read the hit address from status bits [11:0].
- rtl8365mb_l2: add family D unicast/multicast entry (de)serializers,
is selected through struct rtl8365mb_family_info. The IVL bit is set
only for a non-zero VID, so VID 0 produces the SVL key (IVL=0, FID=0)
and static unicast and multicast entries match the learned ones in a
VLAN-unaware bridge. Entries read back with IVL=0 are reported with
VID 0. Use age 6 for static unicast entries as the vendor code does.
Look an entry up before deleting it, so that a missing entry is not
allocated by the delete; as the key has no EFID, an entry found on
another port or one that is not static is left alone.
- rtl8365mb_main: make port_set_efid() a no-op on family D, add a LUT
init step that clears IPMC_HASH and BCAM_DISABLE and writes 3000 to
the aging timer register, as dal_rtl8367d_l2_init() does in the
vendor RTL8367D API (the unit is not documented), and do not
advertise fdb_isolation. Without EFID the bridges cannot be told
apart in the LUT, so offload only one bridge (max_num_bridges = 1).
Further bridges fall back to software forwarding, and traffic
between them is isolated by the port isolation masks only.
Without EFID, FID 0 is shared by every port that is not in a VLAN-aware
bridge: standalone ports, ports of bridges left to software and the
offloaded VLAN-unaware bridge. There is no isolation between them in the
LUT, only the port isolation masks. DSA does not program FDB/MDB
entries of standalone ports here, as fdb_isolation is not set, so those
ports get no host address filtering and their traffic goes to the CPU.
A MAC learned in FID 0 on a bridge port may therefore also be hit by
traffic from a standalone port, or from a software-bridged one, until
the entry ages out or is replaced. This was observed by Gustavo Foletto
Mora: with the hosts on standalone ports of the same switch, unicast to
a host was dropped once its MAC had been learned on the VLAN-unaware
bridge side. With learning off on the bridge ports it was forwarded, and
with a VLAN-aware bridge it did not happen.
Register layouts follow the vendor RTL8367D API; the code itself is an
independent implementation.
Tested by Gustavo Foletto Mora on a TP-Link EX511 v2 (RTL8367S-VB, CPU
port 6 over 1G SGMII to an IPQ5018). With a VLAN-aware bridge: per-VID
FDB readback, static entries replacing learned ones and being deleted
again on every port and VID, the same MAC as a static entry on two
VIDs, MDB entries, VLAN add/delete churn, and moving a port to a second
bridge (handled in software). With a VLAN-unaware bridge: a static entry
with VID 0 replaces the learned one and traffic follows it, a static
entry pointing at the ingress port drops the frames, the MAC relearns on
the original port after the delete; traffic to the bridge MAC no longer
floods to the WAN port; MDB entries limit the flood to the group
members and the flood returns after the delete. Unicast and multicast
forwarding were also checked with loopback cables between user ports
(known and unknown unicast, static entries on the wrong port, MDB, 20k
full-size pings without loss or port errors). MACs of foreign ports
(a veth standing in for a Wi-Fi station) become entries on the CPU port
through assisted learning. Real Wi-Fi stations, RGMII and 2500base-x
were not covered by this test. Leaving a dynamic or foreign-port entry
alone in l2_del_uc() was not exercised.
Nothing points to them until the family D info is added together with
the chip table entry in the last patch. Until then they are not static
and are declared in rtl8365mb_l2.h, so that the build does not warn
about unused functions.
Reported-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Closes: https://github.com/openwrt/openwrt/pull/19644#issuecomment-6708118180
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Tested-by: Stanislaw Pal <kuncy7@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
---
drivers/net/dsa/realtek/realtek.h | 4 +-
drivers/net/dsa/realtek/rtl8365mb_l2.c | 124 +++++++++++++++++++++-
drivers/net/dsa/realtek/rtl8365mb_l2.h | 6 ++
drivers/net/dsa/realtek/rtl8365mb_main.c | 55 +++++++++-
drivers/net/dsa/realtek/rtl8365mb_table.c | 44 ++++++--
drivers/net/dsa/realtek/rtl83xx.c | 5 +-
6 files changed, 221 insertions(+), 17 deletions(-)
diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h
index 1c0ca6d..578490f 100644
--- a/drivers/net/dsa/realtek/realtek.h
+++ b/drivers/net/dsa/realtek/realtek.h
@@ -71,7 +71,9 @@ struct realtek_priv {
* create/update it. l2_(add|del)_uc uses a single table op and,
* internally, it might not need this lock. However, altering FDB
* may still collide, as well as l2_flush, with fdb_dump iterating
- * over FDB.
+ * over FDB. On the RTL8367S-VB (family D), l2_del_uc also looks the
+ * entry up before writing it, as two separate table queries, so it
+ * does depend on this lock.
*/
struct mutex l2_lock;
struct mii_bus *user_mii_bus;
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c
index cd5b333..14acc84 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_l2.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c
@@ -45,6 +45,21 @@
#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7)
#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10)
+/* Family D unicast (auto-learn / static) entry */
+#define RTL8365MB_D_L2_UC_D3_VID_MSK GENMASK(11, 0)
+#define RTL8365MB_D_L2_UC_D3_IVL_MSK BIT(13)
+#define RTL8365MB_D_L2_UC_D3_PORT_LO_MSK GENMASK(15, 14)
+#define RTL8365MB_D_L2_UC_D4_PORT_HI_MSK BIT(0)
+#define RTL8365MB_D_L2_UC_D4_AGE_MSK GENMASK(3, 1)
+#define RTL8365MB_D_L2_UC_D4_STATIC_MSK BIT(6)
+
+/* Family D multicast entry */
+#define RTL8365MB_D_L2_MC_D3_VID_MSK GENMASK(11, 0)
+#define RTL8365MB_D_L2_MC_D3_IVL_MSK BIT(13)
+#define RTL8365MB_D_L2_MC_D3_MBR_LO_MSK GENMASK(15, 14)
+#define RTL8365MB_D_L2_MC_D4_MBR_HI_MSK GENMASK(5, 0)
+#define RTL8365MB_D_L2_MC_D4_STATIC_MSK BIT(6)
+
/* Port flush command registers - writing a 1 to the port's MASK bit will
* initiate the flush procedure. Completion is signalled when the corresponding
* BUSY bit is 0.
@@ -240,6 +255,76 @@ void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc,
data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val);
}
+void rtl8365mb_d_l2_data_to_uc(const u16 *data,
+ struct rtl8365mb_l2_uc *uc)
+{
+ u16 id = FIELD_GET(RTL8365MB_D_L2_UC_D3_VID_MSK, data[3]);
+
+ rtl8365mb_l2_data_to_mac(data, uc->key.mac_addr);
+ uc->key.ivl = FIELD_GET(RTL8365MB_D_L2_UC_D3_IVL_MSK, data[3]);
+ /* D has one shared VID/FID field, selected by IVL */
+ uc->key.vid = uc->key.ivl ? id : 0;
+ uc->key.fid = uc->key.ivl ? 0 : id;
+ uc->key.efid = 0;
+ uc->port = FIELD_GET(RTL8365MB_D_L2_UC_D3_PORT_LO_MSK, data[3]) |
+ (FIELD_GET(RTL8365MB_D_L2_UC_D4_PORT_HI_MSK, data[4]) << 2);
+ uc->age = FIELD_GET(RTL8365MB_D_L2_UC_D4_AGE_MSK, data[4]);
+ uc->is_static = FIELD_GET(RTL8365MB_D_L2_UC_D4_STATIC_MSK, data[4]);
+}
+
+void rtl8365mb_d_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc,
+ u16 *data)
+{
+ /* A VLAN-unaware bridge reaches us with VID 0. The chip learns such
+ * traffic in SVL (IVL=0, FID=0), so use the same key here, otherwise
+ * the static entry never matches the learned one. Only a real VID
+ * selects IVL.
+ */
+ bool ivl = uc->key.ivl && uc->key.vid;
+ u16 id = ivl ? uc->key.vid : uc->key.fid;
+
+ memset(data, 0, RTL8365MB_D_L2_ENTRY_SIZE * 2);
+ rtl8365mb_l2_mac_to_data(uc->key.mac_addr, data);
+ data[3] = FIELD_PREP(RTL8365MB_D_L2_UC_D3_VID_MSK, id) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D3_IVL_MSK, ivl) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D3_PORT_LO_MSK, uc->port & 0x3);
+ data[4] = FIELD_PREP(RTL8365MB_D_L2_UC_D4_PORT_HI_MSK,
+ (uc->port >> 2) & 0x1) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D4_AGE_MSK, uc->age) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D4_STATIC_MSK, uc->is_static);
+}
+
+void rtl8365mb_d_l2_data_to_mc(const u16 *data,
+ struct rtl8365mb_l2_mc *mc)
+{
+ rtl8365mb_l2_data_to_mac(data, mc->key.mac_addr);
+ mc->key.vid = FIELD_GET(RTL8365MB_D_L2_MC_D3_VID_MSK, data[3]);
+ mc->key.ivl = FIELD_GET(RTL8365MB_D_L2_MC_D3_IVL_MSK, data[3]);
+ mc->member = FIELD_GET(RTL8365MB_D_L2_MC_D3_MBR_LO_MSK, data[3]) |
+ (FIELD_GET(RTL8365MB_D_L2_MC_D4_MBR_HI_MSK, data[4]) << 2);
+ mc->is_static = FIELD_GET(RTL8365MB_D_L2_MC_D4_STATIC_MSK, data[4]);
+ mc->igmpidx = 0;
+ mc->priority = 0;
+ mc->fwd_pri = 0;
+ mc->igmp_asic = 0;
+}
+
+void rtl8365mb_d_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc,
+ u16 *data)
+{
+ /* Same as for unicast: VID 0 means a VLAN-unaware bridge, use SVL. */
+ bool ivl = mc->key.ivl && mc->key.vid;
+
+ memset(data, 0, RTL8365MB_D_L2_ENTRY_SIZE * 2);
+ rtl8365mb_l2_mac_to_data(mc->key.mac_addr, data);
+ data[3] = FIELD_PREP(RTL8365MB_D_L2_MC_D3_VID_MSK, mc->key.vid) |
+ FIELD_PREP(RTL8365MB_D_L2_MC_D3_IVL_MSK, ivl) |
+ FIELD_PREP(RTL8365MB_D_L2_MC_D3_MBR_LO_MSK, mc->member & 0x3);
+ data[4] = FIELD_PREP(RTL8365MB_D_L2_MC_D4_MBR_HI_MSK,
+ (mc->member >> 2) & 0x3f) |
+ FIELD_PREP(RTL8365MB_D_L2_MC_D4_STATIC_MSK, mc->is_static);
+}
+
/*
* rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry
* @priv: realtek_priv pointer
@@ -310,8 +395,11 @@ int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
* OS's back. As a trade-off, these will show up as permanent to users.
*/
uc.is_static = true;
- /* age greater than 0 adds/updates entries */
- uc.age = 1;
+ /* age greater than 0 adds/updates entries. D has no EFID, so efid
+ * is not serialized (bridges are isolated by the port isolation
+ * masks only). Vendor code writes age 6 for D.
+ */
+ uc.age = rtl8365mb_is_d(priv) ? 6 : 1;
fi = rtl8365mb_get_family_info(priv);
fi->l2_uc_to_data(&uc, data);
@@ -349,6 +437,38 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
fi = rtl8365mb_get_family_info(priv);
fi->l2_uc_to_data(&uc, data);
+ if (rtl8365mb_is_d(priv)) {
+ struct rtl8365mb_l2_uc found = { 0 };
+
+ /* Do not blind-write: look the entry up first (as the vendor
+ * code does) so a missing entry is not (re)allocated. The
+ * lookup and the write are separate table queries, so this
+ * is not atomic; it relies on FDB operations being
+ * serialized by the caller.
+ */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, &addr,
+ RTL8365MB_TABLE_L2_METHOD_MAC, 0,
+ data, fi->l2_entry_size);
+ if (ret == -ENOENT)
+ return 0;
+ if (ret)
+ return ret;
+
+ /* The key has no EFID on family D, so the entry found may
+ * belong to another port or bridge: leave it alone. DSA only
+ * programs static entries, so leave a dynamic one alone too,
+ * as the hardware may have relearned or aged it since the
+ * lookup.
+ */
+ fi->l2_data_to_uc(data, &found);
+ if (found.port != port || !found.is_static)
+ return 0;
+
+ /* rebuild the write payload: the read overwrote data[] */
+ fi->l2_uc_to_data(&uc, data);
+ }
+
/* it looks like the switch will always add/update the entry,
* even when age is 0 or uc.key did not match an existing entry,
* just to immediately drop it because age is zero. You can still
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h
index a9f9ae4..5bb4e32 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_l2.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h
@@ -13,6 +13,7 @@
#include "realtek.h"
#define RTL8365MB_C_L2_ENTRY_SIZE 6
+#define RTL8365MB_D_L2_ENTRY_SIZE 5
/* Highest L2 entry size of any supported chip, used for sizing */
#define RTL8365MB_L2_ENTRY_SIZE RTL8365MB_C_L2_ENTRY_SIZE
@@ -24,6 +25,11 @@ void rtl8365mb_c_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data);
void rtl8365mb_c_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc);
void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data);
+void rtl8365mb_d_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc);
+void rtl8365mb_d_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data);
+void rtl8365mb_d_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc);
+void rtl8365mb_d_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data);
+
int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
struct realtek_fdb_entry *entry);
int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 525588e..5d822ad 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -435,6 +435,18 @@
#define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport) \
(RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE + (_physport))
+/* Family D LUT configuration */
+#define RTL8365MB_D_LUT_CFG_REG 0x0A30
+#define RTL8365MB_D_LUT_CFG_BCAM_DISABLE_MASK BIT(6)
+#define RTL8365MB_D_LUT_CFG_IPMC_HASH_MASK BIT(4)
+/* On family C this address is the per-port EFID register! */
+#define RTL8365MB_D_LUT_CFG2_REG 0x0A32
+/* Written as a whole register, as dal_rtl8367d_l2_init() does in the
+ * vendor API: the field layout and the unit of this value are not
+ * documented.
+ */
+#define RTL8365MB_D_LUT_AGING_DEFAULT 3000
+
/* Port isolation (forwarding mask) registers */
#define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2
#define RTL8365MB_PORT_ISOLATION_REG(_physport) \
@@ -2650,6 +2662,10 @@ static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port,
u32 efid)
{
+ /* Family D has no EFID; see RTL8365MB_D_LUT_CFG2_REG. */
+ if (rtl8365mb_is_d(priv))
+ return 0;
+
return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port),
RTL8365MB_PORT_EFID_MASK(port),
efid << RTL8365MB_PORT_EFID_OFFSET(port));
@@ -3386,6 +3402,23 @@ static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds,
return rtl8365mb_cpu_config(priv);
}
+static int rtl8365mb_d_lut_init(struct realtek_priv *priv)
+{
+ int ret;
+
+ /* After the jam table: lookup multicast by MAC (not DIP) and keep
+ * the CAM enabled.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_D_LUT_CFG_REG,
+ RTL8365MB_D_LUT_CFG_IPMC_HASH_MASK |
+ RTL8365MB_D_LUT_CFG_BCAM_DISABLE_MASK, 0);
+ if (ret)
+ return ret;
+
+ return regmap_write(priv->map, RTL8365MB_D_LUT_CFG2_REG,
+ RTL8365MB_D_LUT_AGING_DEFAULT);
+}
+
static int rtl8365mb_switch_init(struct realtek_priv *priv)
{
struct rtl8365mb *mb = priv->chip_data;
@@ -3475,6 +3508,15 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
goto out_error;
}
+ if (rtl8365mb_is_d(priv)) {
+ ret = rtl8365mb_d_lut_init(priv);
+ if (ret) {
+ dev_err(priv->dev, "failed to init LUT: %pe\n",
+ ERR_PTR(ret));
+ goto out_error;
+ }
+ }
+
if (mb->sds_supported) {
ret = rtl8365mb_sds_raise_rate_limits(priv);
if (ret) {
@@ -3600,9 +3642,16 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
goto out_teardown_irq;
ds->assisted_learning_on_cpu_port = true;
- ds->fdb_isolation = true;
- /* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */
- ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK);
+ if (!rtl8365mb_is_d(priv)) {
+ ds->fdb_isolation = true;
+ /* The EFID is 3 bits, but EFID 0 is reserved for standalone */
+ ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK);
+ } else {
+ /* No EFID, so bridges cannot be told apart in the LUT: offload
+ * only one, others fall back to software forwarding.
+ */
+ ds->max_num_bridges = 1;
+ }
ds->configure_vlan_while_not_filtering = true;
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/realtek/rtl8365mb_table.c
index f3c8749..6b1323f 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_table.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.c
@@ -5,6 +5,7 @@
*/
#include "rtl8365mb_table.h"
+#include "rtl8365mb.h"
#include <linux/regmap.h>
/* Table access control register */
@@ -16,10 +17,13 @@
/* Should be one of rtl8365mb_table_l2_method enum members */
#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4)
#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8)
+/* Family D: SPA is 3 bits */
+#define RTL8365MB_D_TABLE_CTRL_PORT_MASK GENMASK(10, 8)
/* Table access address register */
#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501
#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0)
+#define RTL8365MB_D_TABLE_ADDR_MASK GENMASK(11, 0)
/* Table status register */
#define RTL8365MB_TABLE_STATUS_REG 0x0502
@@ -29,6 +33,8 @@
#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12)
#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13)
#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14)
+/* Family D: the address is bits [11:0], there is no EXT bit */
+#define RTL8365MB_D_TABLE_STATUS_ADDRESS_MASK GENMASK(11, 0)
/* Table read/write registers */
#define RTL8365MB_TABLE_WRITE_BASE 0x0510
@@ -59,6 +65,7 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
{
bool addr_as_input = true;
bool write_data = false;
+ bool is_d = rtl8365mb_is_d(priv);
int ret = 0;
u32 cmd;
u32 val;
@@ -91,7 +98,13 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC:
break;
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT:
- cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port);
+ if (is_d) {
+ cmd |= FIELD_PREP(RTL8365MB_D_TABLE_CTRL_PORT_MASK,
+ port);
+ } else {
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK,
+ port);
+ }
break;
default:
return -EINVAL;
@@ -146,8 +159,9 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
if (addr_as_input) {
ret = regmap_write(priv->map_nolock,
RTL8365MB_TABLE_ACCESS_ADDR_REG,
- FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK,
- *addr));
+ is_d ?
+ FIELD_PREP(RTL8365MB_D_TABLE_ADDR_MASK, *addr) :
+ FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, *addr));
if (ret)
goto out;
}
@@ -177,13 +191,23 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
}
/* If so, extract the address */
- *addr = 0;
- *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val);
- *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val)
- << 11;
- /* only set if it is a L3 address */
- *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val)
- << 12;
+ if (is_d) {
+ /* Bit 11 is "TYPE" in the register map, but the entry
+ * index (2048 hash + 64 CAM) needs 12 bits; the vendor
+ * code reads [11:0] as well.
+ */
+ *addr = FIELD_GET(RTL8365MB_D_TABLE_STATUS_ADDRESS_MASK,
+ val);
+ } else {
+ *addr = 0;
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK,
+ val);
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK,
+ val) << 11;
+ /* only set if it is a L3 address */
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK,
+ val) << 12;
+ }
}
/* Finally, get the table entry if we were reading */
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index ad42c05..cfe7d42 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -602,7 +602,8 @@ int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
/* Bridge ports use bridge.num as EFID, while standalone ports use
* EFID 0. FDB entries for the CPU port follow the bridge EFID due
- * to assisted learning.
+ * to assisted learning. Chips without an EFID in their L2 table
+ * (RTL8367S-VB) ignore it in l2_add_uc().
*/
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
@@ -654,6 +655,8 @@ int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
/*
* DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while
* DSA_DB_PORT use the default EFID (0), not used by any bridge.
+ * Chips without an EFID in their L2 table (RTL8367S-VB) ignore it in
+ * l2_del_uc().
*/
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v10 12/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
` (10 preceding siblings ...)
2026-11-12 17:41 ` [PATCH net-next v10 11/12] net: dsa: realtek: rtl8365mb: make L2/LUT access work on family D Mieczyslaw Nalewaj
@ 2026-11-12 17:42 ` Mieczyslaw Nalewaj
11 siblings, 0 replies; 17+ messages in thread
From: Mieczyslaw Nalewaj @ 2026-11-12 17:42 UTC (permalink / raw)
To: linusw, andrew, olteanv, kuba, davem, edumazet, pabeni, linux,
luizluca, maxime.chevallier, netdev, linux-kernel, alvin.sipraga,
gusfmora, kuncy7, fabianotassotti, gabonpivovich, contact
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 port 6 and the
MII/TMII/RMII/RGMII interface on port 7, as on family C, but the external
interface indices are one lower (EXT0 and EXT1 instead of EXT1 and EXT2).
The chip reuses the existing initialization jam table.
The chip has 8 ports, not 11, which is described by rtl8365mb_family_d.
This sets ds->num_ports, the IRQ domain size and the bound of every loop
over priv->num_ports for this chip. rtl8365mb_family_d also selects the
family D implementations added in the preceding patches: the force-mode
configuration, the VLAN 4k accessors and the L2 entry (de)serializers.
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, and on a
TP-Link EX511 v2 (RTL8367S-VB, CPU port over 1G SGMII to an IPQ5018).
RGMII mode has not been tested. See the cover letter for details.
Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/realtek/Kconfig | 3 +-
drivers/net/dsa/realtek/rtl8365mb_main.c | 41 ++++++++++++++++++++++--
2 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/drivers/net/dsa/realtek/Kconfig b/drivers/net/dsa/realtek/Kconfig
index d6eb671..98b1727 100644
--- a/drivers/net/dsa/realtek/Kconfig
+++ b/drivers/net/dsa/realtek/Kconfig
@@ -34,7 +34,8 @@ config NET_DSA_REALTEK_RTL8365MB
depends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO
select NET_DSA_TAG_RTL8_4
help
- Select to enable support for Realtek RTL8365MB-VC and RTL8367S.
+ Select to enable support for Realtek RTL8365MB-VC, RTL8367S and
+ RTL8367S-VB.
config NET_DSA_REALTEK_RTL8366RB
tristate "Realtek RTL8366RB switch driver"
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 5d822ad..27ec031 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -89,6 +89,14 @@
* 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, the SerDes, the L2 lookup table (FDB/MDB entry layout, no
+ * EFID, LUT configuration registers) and the table access control register,
+ * so the code paths specific to it are selected with rtl8365mb_is_d() or
+ * through struct rtl8365mb_family_info
*/
#include <linux/bitfield.h>
@@ -117,11 +125,12 @@
#define RTL8365MB_NUM_PHYREGS 32
#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1)
#define RTL8365MB_C_MAX_NUM_PORTS 11
+#define RTL8365MB_D_MAX_NUM_PORTS 8
/* Highest port count of any supported chip, used for sizing */
#define RTL8365MB_MAX_NUM_PORTS RTL8365MB_C_MAX_NUM_PORTS
-/* 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.
+/* 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
@@ -812,6 +821,21 @@ static const struct rtl8365mb_family_info rtl8365mb_family_c = {
.l2_mc_to_data = rtl8365mb_c_l2_mc_to_data,
};
+static const struct rtl8365mb_family_info rtl8365mb_family_d = {
+ .family = RTL8365MB_FAMILY_D,
+ .num_ports = RTL8365MB_D_MAX_NUM_PORTS,
+ .jam_table = rtl8365mb_init_jam_8365mb_vc,
+ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
+ .ext_config_forcemode = rtl8365mb_d_ext_config_forcemode,
+ .vlan_4k_read = rtl8365mb_d_vlan_4k_read,
+ .vlan_4k_write = rtl8365mb_d_vlan_4k_write,
+ .l2_entry_size = RTL8365MB_D_L2_ENTRY_SIZE,
+ .l2_data_to_uc = rtl8365mb_d_l2_data_to_uc,
+ .l2_uc_to_data = rtl8365mb_d_l2_uc_to_data,
+ .l2_data_to_mc = rtl8365mb_d_l2_data_to_mc,
+ .l2_mc_to_data = rtl8365mb_d_l2_mc_to_data,
+};
+
/**
* struct rtl8365mb_chip_info - static chip-specific info
* @name: human-readable chip name
@@ -881,6 +905,17 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
PHY_INTF(RMII) | PHY_INTF(RGMII) },
},
},
+ {
+ .name = "RTL8367S-VB",
+ .chip_id = 0x6642,
+ .chip_ver = 0x0010,
+ .family_info = &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) },
+ },
+ },
};
enum rtl8365mb_stp_state {
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v10 03/12] net: dsa: realtek: rtl8365mb: refactor forcemode handling
2026-11-12 17:33 ` [PATCH net-next v10 03/12] net: dsa: realtek: rtl8365mb: refactor forcemode handling Mieczyslaw Nalewaj
@ 2026-11-12 20:23 ` Linus Walleij
0 siblings, 0 replies; 17+ messages in thread
From: Linus Walleij @ 2026-11-12 20:23 UTC (permalink / raw)
To: Mieczyslaw Nalewaj
Cc: andrew, olteanv, kuba, davem, edumazet, pabeni, linux, luizluca,
maxime.chevallier, netdev, linux-kernel, alvin.sipraga, gusfmora,
kuncy7, fabianotassotti, gabonpivovich, contact
On Thu, Nov 12, 2026 at 6:33 PM Mieczyslaw Nalewaj <namiltd@yahoo.com> wrote:
> Move the external interface force-mode configuration behind struct
> rtl8365mb_family_info, so that family D can provide its own versions
> without is_d() checks in the shared code. The existing code becomes
> the family C implementation and is otherwise unchanged. The
> mac_link_up() and mac_link_down() callbacks call it through the family
> info.
>
> No functional change.
>
> Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
> Tested-by: Stanislaw Pal <kuncy7@gmail.com>
> Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v10 06/12] net: dsa: realtek: rtl8365mb: refactor VLAN 4k handling
2026-11-12 17:36 ` [PATCH net-next v10 06/12] net: dsa: realtek: rtl8365mb: refactor VLAN 4k handling Mieczyslaw Nalewaj
@ 2026-11-12 20:24 ` Linus Walleij
0 siblings, 0 replies; 17+ messages in thread
From: Linus Walleij @ 2026-11-12 20:24 UTC (permalink / raw)
To: Mieczyslaw Nalewaj
Cc: andrew, olteanv, kuba, davem, edumazet, pabeni, linux, luizluca,
maxime.chevallier, netdev, linux-kernel, alvin.sipraga, gusfmora,
kuncy7, fabianotassotti, gabonpivovich, contact
On Thu, Nov 12, 2026 at 6:36 PM Mieczyslaw Nalewaj <namiltd@yahoo.com> wrote:
> Move the family-specific VLAN 4k table accessors behind struct
> rtl8365mb_family_info, so that family D can provide its own versions
> without is_d() checks in the shared code. The existing code becomes the
> family C implementation and is otherwise unchanged: rename the
> functions to rtl8365mb_c_vlan_4k_read() and rtl8365mb_c_vlan_4k_write(),
> export them and call them through the family info.
>
> No functional change.
>
> Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
> Tested-by: Stanislaw Pal <kuncy7@gmail.com>
> Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Looks good to me!
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v10 07/12] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D
2026-11-12 17:37 ` [PATCH net-next v10 07/12] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D Mieczyslaw Nalewaj
@ 2026-11-12 20:25 ` Linus Walleij
0 siblings, 0 replies; 17+ messages in thread
From: Linus Walleij @ 2026-11-12 20:25 UTC (permalink / raw)
To: Mieczyslaw Nalewaj
Cc: andrew, olteanv, kuba, davem, edumazet, pabeni, linux, luizluca,
maxime.chevallier, netdev, linux-kernel, alvin.sipraga, gusfmora,
kuncy7, fabianotassotti, gabonpivovich, contact
On Thu, Nov 12, 2026 at 6:37 PM Mieczyslaw Nalewaj <namiltd@yahoo.com> wrote:
> 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.
>
> IVL_EN (bit 3, versus bit 14 on family C) follows vlan4k->ivl_en. On
> family D, rtl8365mb_vlan_4k_port_set() enables it while any user port
> has VLAN filtering enabled (dsa_port_is_vlan_filtering()), so that
> static and learned FDB/MDB entries use the same key. Family C keeps IVL
> always enabled, as before.
>
> The stored IVL_EN is refreshed on the next port_vlan_add/del for the
> VID, not on a vlan_filtering change.
>
> Reusing the three-word family C layout on family D would read a third
> word that does not exist as a distinct table word on family D's die.
> Whatever data happens to be in that register would be folded into
> member/untag bits [10:8] on read, and written back on every
> read-modify-write cycle in rtl8365mb_vlan_4k_port_set(). Give family D
> its own two-word pack/unpack instead: add rtl8365mb_d_vlan_4k_read() and
> rtl8365mb_d_vlan_4k_write(). The shared code reaches them through the
> vlan_4k_read and vlan_4k_write pointers in struct rtl8365mb_family_info,
> so the pack/unpack code needs no is_d() check.
>
> The two-word layout matches the vendor switch API for this chip
> family, which writes only 0x0510/0x0511 for a CVLAN entry.
>
> Nothing points to them until the family D info is added together with
> the chip table entry in the last patch. Until then they are not static
> and are declared in rtl8365mb_vlan.h, so that the build does not warn
> about unused functions.
>
> Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
> Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v10 10/12] net: dsa: realtek: rtl8365mb: refactor L2 MAC handling
2026-11-12 17:40 ` [PATCH net-next v10 10/12] net: dsa: realtek: rtl8365mb: refactor L2 MAC handling Mieczyslaw Nalewaj
@ 2026-11-12 20:26 ` Linus Walleij
0 siblings, 0 replies; 17+ messages in thread
From: Linus Walleij @ 2026-11-12 20:26 UTC (permalink / raw)
To: Mieczyslaw Nalewaj
Cc: andrew, olteanv, kuba, davem, edumazet, pabeni, linux, luizluca,
maxime.chevallier, netdev, linux-kernel, alvin.sipraga, gusfmora,
kuncy7, fabianotassotti, gabonpivovich, contact
On Thu, Nov 12, 2026 at 6:40 PM Mieczyslaw Nalewaj <namiltd@yahoo.com> wrote:
> Add L2 MAC pack/unpack helpers and use them in the family C
> serializers instead of open-coding the byte packing, since the layout
> of the MAC address words is the same for both families. The now unused
> per-field MAC masks are dropped.
>
> Rename the family C serializers to rtl8365mb_c_l2_*(), export them and
> reach them through function pointers in struct rtl8365mb_family_info,
> so that a following patch can add the family D layout without touching
> the family C code again.
>
> No functional change.
>
> Tested-by: Gustavo Foletto Mora <gusfmora@gmail.com>
> Tested-by: Stanislaw Pal <kuncy7@gmail.com>
> Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-11-12 20:27 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <d9da17af-f49e-4878-a8dd-59a8919e6930.ref@yahoo.com>
2026-11-12 17:31 ` [PATCH net-next v10 00/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
2026-11-12 17:32 ` [PATCH net-next v10 01/12] dt-bindings: net: dsa: realtek: add RTL8367S-VB Mieczyslaw Nalewaj
2026-11-12 17:33 ` [PATCH net-next v10 02/12] net: dsa: realtek: rtl8365mb: add chip family infrastructure Mieczyslaw Nalewaj
2026-11-12 17:33 ` [PATCH net-next v10 03/12] net: dsa: realtek: rtl8365mb: refactor forcemode handling Mieczyslaw Nalewaj
2026-11-12 20:23 ` Linus Walleij
2026-11-12 17:34 ` [PATCH net-next v10 04/12] net: dsa: realtek: rtl8365mb: set speed for family D Mieczyslaw Nalewaj
2026-11-12 17:35 ` [PATCH net-next v10 05/12] net: dsa: realtek: rtl8365mb: set RGMII mode " Mieczyslaw Nalewaj
2026-11-12 17:36 ` [PATCH net-next v10 06/12] net: dsa: realtek: rtl8365mb: refactor VLAN 4k handling Mieczyslaw Nalewaj
2026-11-12 20:24 ` Linus Walleij
2026-11-12 17:37 ` [PATCH net-next v10 07/12] net: dsa: realtek: rtl8365mb: set and get vlan 4k for family D Mieczyslaw Nalewaj
2026-11-12 20:25 ` Linus Walleij
2026-11-12 17:38 ` [PATCH net-next v10 08/12] net: dsa: realtek: rtl8365mb: use raw VID for PVID on " Mieczyslaw Nalewaj
2026-11-12 17:39 ` [PATCH net-next v10 09/12] net: dsa: realtek: rtl8365mb: add family D SDS13 PCS support Mieczyslaw Nalewaj
2026-11-12 17:40 ` [PATCH net-next v10 10/12] net: dsa: realtek: rtl8365mb: refactor L2 MAC handling Mieczyslaw Nalewaj
2026-11-12 20:26 ` Linus Walleij
2026-11-12 17:41 ` [PATCH net-next v10 11/12] net: dsa: realtek: rtl8365mb: make L2/LUT access work on family D Mieczyslaw Nalewaj
2026-11-12 17:42 ` [PATCH net-next v10 12/12] net: dsa: realtek: rtl8365mb: add support for RTL8367S-VB Mieczyslaw Nalewaj
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox