* [PATCH net-next v3 1/8] dt-bindings: vendor-prefixes: Add soce
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
2026-09-23 10:39 ` [PATCH net-next v3 2/8] dt-bindings: net: Add SoC-e SWIP MDIO controller Vasilij Strassheim
` (6 subsequent siblings)
7 siblings, 0 replies; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim, Krzysztof Kozlowski
Document binding for System-On-Chip Engineering, S.L.
Link: https://soc-e.com/
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index ba2002969373..1c641e1f0ca6 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1581,6 +1581,8 @@ patternProperties:
description: Standard Microsystems Corporation
"^snps,.*":
description: Synopsys, Inc.
+ "^soce,.*":
+ description: System-On-Chip Engineering, S.L.
"^sochip,.*":
description: Shenzhen SoChip Technology Co., Ltd.
"^socionext,.*":
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net-next v3 2/8] dt-bindings: net: Add SoC-e SWIP MDIO controller
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
2026-09-23 10:39 ` [PATCH net-next v3 1/8] dt-bindings: vendor-prefixes: Add soce Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
2026-09-25 22:55 ` Andrew Lunn
2026-09-23 10:39 ` [PATCH net-next v3 3/8] dt-bindings: net: dsa: Add SoC-e SWIP switch Vasilij Strassheim
` (5 subsequent siblings)
7 siblings, 1 reply; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim
Add a binding for the MDIO controller integrated into SoC-e SWIP
Ethernet switch IP cores.
The controller exposes separate register regions for transaction data
and for the shared transaction control and external bus selector
register. It acts as the parent bus for an MDIO mux used to access the
external MDIO interfaces synthesized into the switch core.
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
.../devicetree/bindings/net/soce,swip-mdio.yaml | 47 ++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/soce,swip-mdio.yaml b/Documentation/devicetree/bindings/net/soce,swip-mdio.yaml
new file mode 100644
index 000000000000..3cf4bde0508a
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/soce,swip-mdio.yaml
@@ -0,0 +1,47 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/soce,swip-mdio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SoC-e switch IP MDIO controller
+
+maintainers:
+ - Vasilij Strassheim <v.strassheim@linutronix.de>
+
+description:
+ MDIO controller integrated into SoC-e Ethernet switch FPGA IP cores.
+ The controller accesses multiple external MDIO buses through a mux.
+
+$ref: mdio.yaml#
+
+properties:
+ compatible:
+ const: soce,swip-mdio-23-02
+
+ reg:
+ items:
+ - description: MDIO parameter, write data, and read data registers
+ - description: Shared MDIO transaction control and bus selection register
+
+ reg-names:
+ items:
+ - const: data
+ - const: control
+
+required:
+ - compatible
+ - reg
+ - reg-names
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ mdio@204 {
+ compatible = "soce,swip-mdio-23-02";
+ reg = <0x204 0xc>, <0x200 0x4>;
+ reg-names = "data", "control";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net-next v3 2/8] dt-bindings: net: Add SoC-e SWIP MDIO controller
2026-09-23 10:39 ` [PATCH net-next v3 2/8] dt-bindings: net: Add SoC-e SWIP MDIO controller Vasilij Strassheim
@ 2026-09-25 22:55 ` Andrew Lunn
0 siblings, 0 replies; 17+ messages in thread
From: Andrew Lunn @ 2026-09-25 22:55 UTC (permalink / raw)
To: Vasilij Strassheim
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Russell King, Andrew Lunn, Heiner Kallweit,
devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger
> The controller exposes separate register regions for transaction data
> and for the shared transaction control and external bus selector
> register.
> +examples:
> + - |
> + mdio@204 {
> + compatible = "soce,swip-mdio-23-02";
> + reg = <0x204 0xc>, <0x200 0x4>;
At least in the example, they are not separate?
Andrew
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v3 3/8] dt-bindings: net: dsa: Add SoC-e SWIP switch
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
2026-09-23 10:39 ` [PATCH net-next v3 1/8] dt-bindings: vendor-prefixes: Add soce Vasilij Strassheim
2026-09-23 10:39 ` [PATCH net-next v3 2/8] dt-bindings: net: Add SoC-e SWIP MDIO controller Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
2026-09-25 23:05 ` Andrew Lunn
2026-09-23 10:39 ` [PATCH net-next v3 4/8] net: dsa: Add tag handling for SoC-e switches Vasilij Strassheim
` (4 subsequent siblings)
7 siblings, 1 reply; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim
Add a binding for SoC-e FPGA-based Ethernet switch IP cores.
The compatible identifies a register layout characterized by the core
and feature identification registers at the encoded offsets. The
concrete synthesis-time configuration is then autodetected from these
registers.
Describe the Ethernet ports and the optional integrated MDIO controller
with its generic MMIO mux and child buses.
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
.../devicetree/bindings/net/dsa/soce,swip.yaml | 162 +++++++++++++++++++++
1 file changed, 162 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/dsa/soce,swip.yaml b/Documentation/devicetree/bindings/net/dsa/soce,swip.yaml
new file mode 100644
index 000000000000..d614fb2a29d3
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/soce,swip.yaml
@@ -0,0 +1,162 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/soce,swip.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SoC-e ethernet switch IP core for FPGAs
+
+maintainers:
+ - Vasilij Strassheim <v.strassheim@linutronix.de>
+
+description:
+ SoC-e Ethernet switch IP cores are FPGA-based switches whose features
+ and number of ports are selected at synthesis time. Ports can connect
+ to CPUs, external PHYs, FPGA logic, or other switch cores. An integrated
+ MDIO controller accesses the per-port external buses through a mux.
+
+$ref: dsa.yaml#
+
+properties:
+ compatible:
+ const: soce,swip-00-04-0c-10
+ description:
+ Register layout with the core version register at offset 0x00 and
+ feature identification registers at offsets 0x04, 0x0c, and 0x10.
+ Switch instances using this register layout are autodetected from
+ these registers and use this compatible regardless of their
+ synthesis-time feature and port configuration.
+
+ reg:
+ maxItems: 1
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 1
+
+ ranges: true
+
+ mdio@204:
+ $ref: /schemas/net/soce,swip-mdio.yaml#
+ unevaluatedProperties: false
+ description:
+ Integrated MDIO controller bus on the master side of the mux.
+
+ mdio-mux@202:
+ $ref: /schemas/net/mdio-mux-mmioreg.yaml#
+ unevaluatedProperties: false
+
+ ethernet-ports:
+ type: object
+ patternProperties:
+ '^ethernet-port@[0-9a-f]+$':
+ type: object
+ $ref: dsa-port.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maximum: 30
+ description:
+ Switch port index. Supported switch configurations have
+ up to 31 ports, numbered from 0 through 30.
+
+ phy-mode:
+ enum:
+ - mii
+ - gmii
+ - rmii
+ - rgmii
+ - rgmii-id
+ - rgmii-rxid
+ - rgmii-txid
+
+ required:
+ - reg
+ - phy-mode
+
+required:
+ - compatible
+ - reg
+ - ethernet-ports
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ ethernet-switch@80020000 {
+ compatible = "soce,swip-00-04-0c-10";
+ reg = <0x80020000 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0x0 0x80020000 0x10000>;
+
+ mdio_parent: mdio@204 {
+ compatible = "soce,swip-mdio-23-02";
+ reg = <0x204 0xc>, <0x200 0x4>;
+ reg-names = "data", "control";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ mdio-mux@202 {
+ compatible = "mdio-mux-mmioreg", "mdio-mux";
+ reg = <0x202 0x2>;
+ mux-mask = <0x07ff>;
+ mdio-parent-bus = <&mdio_parent>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ mdio@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ switchphy0: ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <1>;
+ };
+ };
+
+ mdio@1 {
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ switchphy1: ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <1>;
+ };
+ };
+ };
+
+ ethernet-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-port@0 {
+ reg = <0>;
+ phy-handle = <&switchphy0>;
+ phy-mode = "rgmii-id";
+ };
+
+ ethernet-port@1 {
+ reg = <1>;
+ phy-handle = <&switchphy1>;
+ phy-mode = "rgmii-id";
+ };
+
+ ethernet-port@2 {
+ reg = <2>;
+ ethernet = <ð0>;
+ phy-mode = "gmii";
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+ };
+ };
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net-next v3 3/8] dt-bindings: net: dsa: Add SoC-e SWIP switch
2026-09-23 10:39 ` [PATCH net-next v3 3/8] dt-bindings: net: dsa: Add SoC-e SWIP switch Vasilij Strassheim
@ 2026-09-25 23:05 ` Andrew Lunn
0 siblings, 0 replies; 17+ messages in thread
From: Andrew Lunn @ 2026-09-25 23:05 UTC (permalink / raw)
To: Vasilij Strassheim
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Russell King, Andrew Lunn, Heiner Kallweit,
devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger
> + mdio@204:
> + $ref: /schemas/net/soce,swip-mdio.yaml#
> + unevaluatedProperties: false
> + description:
> + Integrated MDIO controller bus on the master side of the mux.
> +
> + mdio-mux@202:
> + $ref: /schemas/net/mdio-mux-mmioreg.yaml#
> + unevaluatedProperties: false
> + mdio_parent: mdio@204 {
> + compatible = "soce,swip-mdio-23-02";
> + reg = <0x204 0xc>, <0x200 0x4>;
> + reg-names = "data", "control";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + };
> +
> + mdio-mux@202 {
> + compatible = "mdio-mux-mmioreg", "mdio-mux";
> + reg = <0x202 0x2>;
So these two overlap? That is pretty unusual, so might be worth a comment somewhere.
Andrew
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v3 4/8] net: dsa: Add tag handling for SoC-e switches
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
` (2 preceding siblings ...)
2026-09-23 10:39 ` [PATCH net-next v3 3/8] dt-bindings: net: dsa: Add SoC-e SWIP switch Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
[not found] ` <20260924104003.A49F31F000FF@smtp.kernel.org>
2026-09-23 10:39 ` [PATCH net-next v3 5/8] net: mdio: Add SoC-e SWIP MDIO controller driver Vasilij Strassheim
` (3 subsequent siblings)
7 siblings, 1 reply; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim
SoC-e switches exchange frames with the conduit using an 8-byte SDSA
header inserted between the source MAC address and the original
EtherType. The header identifies itself with EtherType 0xdcdc and
carries the frame direction and a 10-bit source or destination port.
Add transmit support for FROM_CPU frames and receive support for TO_CPU
frames. Validate the EtherType, frame type, header length, and source
port before accepting received frames.
The SDSA header can also carry an 802.1Q TCI. Encode accelerated VLAN
metadata into the header on transmit and restore it as skb VLAN metadata
on receive.
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 ++
net/dsa/Makefile | 1 +
net/dsa/tag_sdsa.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 167 insertions(+)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 5d12191b6f6f..8287fba8b107 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -62,6 +62,7 @@ struct tc_action;
#define DSA_TAG_PROTO_KSZ8463_VALUE 34
#define DSA_TAG_PROTO_MT7628_VALUE 35
#define DSA_TAG_PROTO_KS8995_VALUE 36
+#define DSA_TAG_PROTO_SDSA_VALUE 37
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -101,6 +102,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE,
DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE,
DSA_TAG_PROTO_KS8995 = DSA_TAG_PROTO_KS8995_VALUE,
+ DSA_TAG_PROTO_SDSA = DSA_TAG_PROTO_SDSA_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 4f44bf3ede23..9b299cada316 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -194,6 +194,12 @@ config NET_DSA_TAG_RZN1_A5PSW
Renesas RZ/N1 embedded switch that uses an 8 byte tag located after
destination MAC address.
+config NET_DSA_TAG_SDSA
+ tristate "Tag driver for SoC-e switches using EtherType SDSA headers"
+ help
+ Say Y or M if you want to enable support for tagging frames for the
+ SoC-e switches.
+
config NET_DSA_TAG_LAN9303
tristate "Tag driver for SMSC/Microchip LAN9303 family of switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index 1f9cc30e9988..a4c8d0e6bc0f 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_NET_DSA_TAG_QCA) += tag_qca.o
obj-$(CONFIG_NET_DSA_TAG_RTL4_A) += tag_rtl4_a.o
obj-$(CONFIG_NET_DSA_TAG_RTL8_4) += tag_rtl8_4.o
obj-$(CONFIG_NET_DSA_TAG_RZN1_A5PSW) += tag_rzn1_a5psw.o
+obj-$(CONFIG_NET_DSA_TAG_SDSA) += tag_sdsa.o
obj-$(CONFIG_NET_DSA_TAG_SJA1105) += tag_sja1105.o
obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
obj-$(CONFIG_NET_DSA_TAG_VSC73XX_8021Q) += tag_vsc73xx_8021q.o
diff --git a/net/dsa/tag_sdsa.c b/net/dsa/tag_sdsa.c
new file mode 100644
index 000000000000..8cc3fa357be4
--- /dev/null
+++ b/net/dsa/tag_sdsa.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020-2026 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ * Author: Vasilij Strassheim <v.strassheim@linutronix.de>
+ */
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/unaligned.h>
+
+#include "tag.h"
+
+#define SDSA_HLEN 8
+
+#define SDSA_NAME "sdsa"
+#define ETH_P_SDSA 0xDCDC
+
+/* SDSA tag byte layout (after the 12-byte MAC header):
+ * Bytes 0-1: SDSA EtherType (0xDCDC)
+ * Bytes 2-3: Reserved
+ * Byte 4: Frame type (bits 7-6), VLAN-info bit (bit 5), port[9:5] (bits 4-0)
+ * Byte 5: Port[4:0] (bits 7-3)
+ * Bytes 6-7: PCP (bits 7-5) / CFI (bit 4) / VID (bits 3-0 + byte 7), only
+ * meaningful when the VLAN-info bit is set.
+ */
+#define SDSA_TAG_FRAME_TYPE_MASK GENMASK(7, 6)
+#define SDSA_TAG_VLAN_BIT BIT(5)
+#define SDSA_TAG_PORT_HI_MASK GENMASK(4, 0)
+#define SDSA_TAG_PORT_LO_MASK GENMASK(7, 3)
+#define SDSA_TAG_PORT_HI_SHIFT 5
+#define SDSA_FRAME_TYPE_TO_CPU 0
+#define SDSA_FRAME_TYPE_FROM_CPU 1
+
+struct sdsa_tag {
+ __be16 ethertype;
+ __be16 reserved;
+ u8 frame_type_port_hi;
+ u8 port_lo;
+ __be16 vlan;
+};
+
+static struct sk_buff *sdsa_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct dsa_port *dp = dsa_user_to_port(dev);
+ struct sdsa_tag *tag;
+ u16 vlan_tci;
+
+ BUILD_BUG_ON(sizeof(*tag) != SDSA_HLEN);
+
+ skb_push(skb, SDSA_HLEN);
+ dsa_alloc_etype_header(skb, SDSA_HLEN);
+
+ tag = dsa_etype_header_pos_tx(skb);
+ tag->ethertype = cpu_to_be16(ETH_P_SDSA);
+ tag->reserved = 0;
+ tag->frame_type_port_hi =
+ FIELD_PREP(SDSA_TAG_FRAME_TYPE_MASK, SDSA_FRAME_TYPE_FROM_CPU) |
+ FIELD_PREP(SDSA_TAG_PORT_HI_MASK,
+ dp->index >> SDSA_TAG_PORT_HI_SHIFT);
+ tag->port_lo = FIELD_PREP(SDSA_TAG_PORT_LO_MASK, dp->index);
+ /* SDSA carries no TPID, so only encode 802.1Q C-tags. */
+ if (skb_vlan_tag_present(skb) &&
+ skb->vlan_proto == htons(ETH_P_8021Q)) {
+ vlan_tci = skb_vlan_tag_get(skb);
+ __vlan_hwaccel_clear_tag(skb);
+ tag->frame_type_port_hi |= SDSA_TAG_VLAN_BIT;
+ tag->vlan = cpu_to_be16(vlan_tci);
+ } else {
+ tag->vlan = 0;
+ }
+
+ return skb;
+}
+
+static struct sk_buff *sdsa_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+ enum skb_drop_reason reason;
+ struct sdsa_tag *tag;
+ u16 dummy_vlan_tci;
+ bool vlan_present;
+ int source_port;
+ u16 encap_proto;
+ u8 frame_type;
+ u16 vlan_tci;
+
+ if (unlikely(!pskb_may_pull(skb, SDSA_HLEN))) {
+ reason = SKB_DROP_REASON_HDR_TRUNC;
+ goto out_drop;
+ }
+
+ tag = dsa_etype_header_pos_rx(skb);
+ if (unlikely(be16_to_cpu(tag->ethertype) != ETH_P_SDSA)) {
+ reason = SKB_DROP_REASON_DEV_HDR;
+ goto out_drop;
+ }
+
+ frame_type = FIELD_GET(SDSA_TAG_FRAME_TYPE_MASK,
+ tag->frame_type_port_hi);
+ if (frame_type != SDSA_FRAME_TYPE_TO_CPU) {
+ reason = SKB_DROP_REASON_DEV_HDR;
+ goto out_drop;
+ }
+
+ vlan_present = tag->frame_type_port_hi & SDSA_TAG_VLAN_BIT;
+ vlan_tci = be16_to_cpu(tag->vlan);
+ encap_proto = get_unaligned_be16((u8 *)tag + SDSA_HLEN);
+
+ source_port = FIELD_GET(SDSA_TAG_PORT_HI_MASK,
+ tag->frame_type_port_hi) <<
+ SDSA_TAG_PORT_HI_SHIFT;
+ source_port |= FIELD_GET(SDSA_TAG_PORT_LO_MASK, tag->port_lo);
+
+ skb->dev = dsa_conduit_find_user(dev, 0, source_port);
+ if (!skb->dev) {
+ reason = SKB_DROP_REASON_DEV_HDR;
+ goto out_drop;
+ }
+
+ skb_pull_rcsum(skb, SDSA_HLEN);
+ dsa_strip_etype_header(skb, SDSA_HLEN);
+ if (vlan_present) {
+ if (encap_proto == ETH_P_8021Q) {
+ skb_push_rcsum(skb, ETH_HLEN);
+ skb_reset_mac_header(skb);
+ if (__skb_vlan_pop(skb, &dummy_vlan_tci)) {
+ reason = SKB_DROP_REASON_NOMEM;
+ goto out_drop;
+ }
+ skb_pull_rcsum(skb, ETH_HLEN);
+ }
+
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan_tci);
+ }
+
+ dsa_default_offload_fwd_mark(skb);
+ return skb;
+
+out_drop:
+ kfree_skb_reason(skb, reason);
+ return NULL;
+}
+
+static const struct dsa_device_ops sdsa_netdev_ops = {
+ .name = SDSA_NAME,
+ .proto = DSA_TAG_PROTO_SDSA,
+ .xmit = sdsa_xmit,
+ .rcv = sdsa_rcv,
+ .needed_headroom = SDSA_HLEN,
+};
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DSA tag driver for SoC-e SDSA protocol");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_SDSA, SDSA_NAME);
+
+module_dsa_tag_driver(sdsa_netdev_ops);
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net-next v3 5/8] net: mdio: Add SoC-e SWIP MDIO controller driver
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
` (3 preceding siblings ...)
2026-09-23 10:39 ` [PATCH net-next v3 4/8] net: dsa: Add tag handling for SoC-e switches Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
2026-09-25 23:10 ` Andrew Lunn
2026-09-23 10:39 ` [PATCH net-next v3 6/8] net: dsa: soce: Add basic support for SoC-e switch IP cores Vasilij Strassheim
` (2 subsequent siblings)
7 siblings, 1 reply; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim
Add a driver for the MDIO controller integrated into SoC-e Ethernet
switch IP cores. Support Clause 22 transactions and the separate address
and data cycles required for Clause 45 accesses.
The transaction control register is shared with the external MDIO bus
selector. The selector occupies bits 26:16 and is managed by a generic
MMIO MDIO mux through the upper 16-bit halfword. Preserve those bits
when starting a transaction.
Check that the controller is idle before programming transaction
registers and poll for completion after each transaction cycle. Return a
timeout instead of waiting indefinitely, and allow a later access to
retry if the hardware becomes idle again.
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
drivers/net/mdio/Kconfig | 7 ++
drivers/net/mdio/Makefile | 1 +
drivers/net/mdio/mdio-soce.c | 239 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 247 insertions(+)
diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig
index d44278f26fab..21f2b516880b 100644
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -189,6 +189,13 @@ config MDIO_REGMAP
regmap. Users willing to use this driver must explicitly select
REGMAP.
+config MDIO_SOCE
+ tristate "SoC-e MDIO controller"
+ depends on OF_MDIO && HAS_IOMEM
+ help
+ This module provides a driver for the MDIO controller integrated
+ into SoC-e Ethernet switch IP cores.
+
config MDIO_THUNDER
tristate "ThunderX SOCs MDIO buses"
depends on 64BIT
diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile
index 048586746026..079b46dea25f 100644
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
obj-$(CONFIG_MDIO_PIC64HPSC) += mdio-pic64hpsc.o
obj-$(CONFIG_MDIO_REALTEK_RTL9300) += mdio-realtek-rtl9300.o
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
+obj-$(CONFIG_MDIO_SOCE) += mdio-soce.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
diff --git a/drivers/net/mdio/mdio-soce.c b/drivers/net/mdio/mdio-soce.c
new file mode 100644
index 000000000000..59c0b3da483e
--- /dev/null
+++ b/drivers/net/mdio/mdio-soce.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020-2026 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ * Author: Vasilij Strassheim <v.strassheim@linutronix.de>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/platform_device.h>
+
+#define SOCE_MDIO_TIMEOUT_US 1000
+
+#define SOCE_MDIO_PARAMS_OFFSET 0x0000
+#define SOCE_MDIO_WRITE_OFFSET 0x0004
+#define SOCE_MDIO_READ_OFFSET 0x0008
+#define SOCE_MDIO_DATA_IOMAP_IDX 0
+#define SOCE_MDIO_CTRL_IOMAP_IDX 1
+
+#define SOCE_MDIO_CTRL_BUS_MASK GENMASK(26, 16)
+#define SOCE_MDIO_CTRL_TRANSTYPE_MASK GENMASK(4, 3)
+#define SOCE_MDIO_CTRL_TRANSTYPE_WRITE 0x1
+#define SOCE_MDIO_CTRL_TRANSTYPE_READ 0x3
+#define SOCE_MDIO_CTRL_CLAUSE BIT(1)
+#define SOCE_MDIO_CTRL_OPSTATUS BIT(0)
+#define SOCE_MDIO_PARAMS_REGDEV_MASK GENMASK(12, 8)
+#define SOCE_MDIO_PARAMS_PHYADDR_MASK GENMASK(4, 0)
+#define SOCE_MDIO_READ_DATA_MASK GENMASK(15, 0)
+
+struct soce_mdio {
+ void __iomem *ctrl;
+ void __iomem *data;
+};
+
+static void __iomem *soce_mdio_iomap(struct device *dev, int index)
+{
+ struct resource res;
+ int ret;
+
+ ret = of_address_to_resource(dev->of_node, index, &res);
+ if (ret)
+ return IOMEM_ERR_PTR(ret);
+
+ return devm_ioremap(dev, res.start, resource_size(&res));
+}
+
+static int soce_mdio_wait_for_idle(struct soce_mdio *priv)
+{
+ void __iomem *ctrl = priv->ctrl;
+ u32 val;
+
+ return readl_poll_timeout(ctrl, val,
+ !(val & SOCE_MDIO_CTRL_OPSTATUS), 10,
+ SOCE_MDIO_TIMEOUT_US);
+}
+
+static void soce_mdio_start(struct soce_mdio *priv, u32 command)
+{
+ void __iomem *ctrl = priv->ctrl;
+
+ /* Keep the currently selected MDIO bus while updating op bits. */
+ command |= readl(ctrl) & SOCE_MDIO_CTRL_BUS_MASK;
+ writel(command, ctrl);
+}
+
+static int soce_mdio_read(struct mii_bus *bus, int phy_addr, int regnum)
+{
+ struct soce_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SOCE_MDIO_PARAMS_REGDEV_MASK, regnum) |
+ FIELD_PREP(SOCE_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SOCE_MDIO_PARAMS_OFFSET);
+
+ command = FIELD_PREP(SOCE_MDIO_CTRL_TRANSTYPE_MASK,
+ SOCE_MDIO_CTRL_TRANSTYPE_READ) |
+ SOCE_MDIO_CTRL_OPSTATUS;
+ soce_mdio_start(priv, command);
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ return readl(data + SOCE_MDIO_READ_OFFSET) & SOCE_MDIO_READ_DATA_MASK;
+}
+
+static int soce_mdio_read_c45(struct mii_bus *bus, int phy_addr, int devad,
+ int regnum)
+{
+ struct soce_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SOCE_MDIO_PARAMS_REGDEV_MASK, devad) |
+ FIELD_PREP(SOCE_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SOCE_MDIO_PARAMS_OFFSET);
+ writel(regnum, data + SOCE_MDIO_WRITE_OFFSET);
+
+ command = SOCE_MDIO_CTRL_CLAUSE | SOCE_MDIO_CTRL_OPSTATUS;
+ soce_mdio_start(priv, command);
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ command = FIELD_PREP(SOCE_MDIO_CTRL_TRANSTYPE_MASK,
+ SOCE_MDIO_CTRL_TRANSTYPE_READ) |
+ SOCE_MDIO_CTRL_CLAUSE | SOCE_MDIO_CTRL_OPSTATUS;
+ soce_mdio_start(priv, command);
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ return readl(data + SOCE_MDIO_READ_OFFSET) & SOCE_MDIO_READ_DATA_MASK;
+}
+
+static int soce_mdio_write(struct mii_bus *bus, int phy_addr, int regnum,
+ u16 val)
+{
+ struct soce_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SOCE_MDIO_PARAMS_REGDEV_MASK, regnum) |
+ FIELD_PREP(SOCE_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SOCE_MDIO_PARAMS_OFFSET);
+ writel(val, data + SOCE_MDIO_WRITE_OFFSET);
+
+ command = FIELD_PREP(SOCE_MDIO_CTRL_TRANSTYPE_MASK,
+ SOCE_MDIO_CTRL_TRANSTYPE_WRITE) |
+ SOCE_MDIO_CTRL_OPSTATUS;
+ soce_mdio_start(priv, command);
+
+ return soce_mdio_wait_for_idle(priv);
+}
+
+static int soce_mdio_write_c45(struct mii_bus *bus, int phy_addr, int devad,
+ int regnum, u16 val)
+{
+ struct soce_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SOCE_MDIO_PARAMS_REGDEV_MASK, devad) |
+ FIELD_PREP(SOCE_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SOCE_MDIO_PARAMS_OFFSET);
+ writel(regnum, data + SOCE_MDIO_WRITE_OFFSET);
+
+ command = SOCE_MDIO_CTRL_CLAUSE | SOCE_MDIO_CTRL_OPSTATUS;
+ soce_mdio_start(priv, command);
+
+ ret = soce_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(val, data + SOCE_MDIO_WRITE_OFFSET);
+
+ command = FIELD_PREP(SOCE_MDIO_CTRL_TRANSTYPE_MASK,
+ SOCE_MDIO_CTRL_TRANSTYPE_WRITE) |
+ SOCE_MDIO_CTRL_CLAUSE | SOCE_MDIO_CTRL_OPSTATUS;
+ soce_mdio_start(priv, command);
+
+ return soce_mdio_wait_for_idle(priv);
+}
+
+static int soce_mdio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct soce_mdio *priv;
+ struct mii_bus *bus;
+
+ bus = devm_mdiobus_alloc_size(dev, sizeof(*priv));
+ if (!bus)
+ return -ENOMEM;
+
+ priv = bus->priv;
+ priv->data = soce_mdio_iomap(dev, SOCE_MDIO_DATA_IOMAP_IDX);
+ if (IS_ERR(priv->data))
+ return PTR_ERR(priv->data);
+
+ priv->ctrl = soce_mdio_iomap(dev, SOCE_MDIO_CTRL_IOMAP_IDX);
+ if (IS_ERR(priv->ctrl))
+ return PTR_ERR(priv->ctrl);
+
+ bus->name = "soce mdio";
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
+ bus->parent = dev;
+ bus->read = soce_mdio_read;
+ bus->write = soce_mdio_write;
+ bus->read_c45 = soce_mdio_read_c45;
+ bus->write_c45 = soce_mdio_write_c45;
+
+ return devm_of_mdiobus_register(dev, bus, dev->of_node);
+}
+
+static const struct of_device_id soce_mdio_of_match[] = {
+ { .compatible = "soce,swip-mdio-23-02" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, soce_mdio_of_match);
+
+static struct platform_driver soce_mdio_driver = {
+ .probe = soce_mdio_probe,
+ .driver = {
+ .name = "soce-mdio",
+ .of_match_table = soce_mdio_of_match,
+ },
+};
+module_platform_driver(soce_mdio_driver);
+
+MODULE_AUTHOR("Vasilij Strassheim <v.strassheim@linutronix.de>");
+MODULE_DESCRIPTION("SoC-e MDIO controller driver");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net-next v3 5/8] net: mdio: Add SoC-e SWIP MDIO controller driver
2026-09-23 10:39 ` [PATCH net-next v3 5/8] net: mdio: Add SoC-e SWIP MDIO controller driver Vasilij Strassheim
@ 2026-09-25 23:10 ` Andrew Lunn
0 siblings, 0 replies; 17+ messages in thread
From: Andrew Lunn @ 2026-09-25 23:10 UTC (permalink / raw)
To: Vasilij Strassheim
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Russell King, Andrew Lunn, Heiner Kallweit,
devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger
> diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile
> index 048586746026..079b46dea25f 100644
> --- a/drivers/net/mdio/Makefile
> +++ b/drivers/net/mdio/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
> obj-$(CONFIG_MDIO_PIC64HPSC) += mdio-pic64hpsc.o
> obj-$(CONFIG_MDIO_REALTEK_RTL9300) += mdio-realtek-rtl9300.o
> obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
> +obj-$(CONFIG_MDIO_SOCE) += mdio-soce.o
> obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
Maybe the indentation is wrong here?
Otherwise:
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v3 6/8] net: dsa: soce: Add basic support for SoC-e switch IP cores
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
` (4 preceding siblings ...)
2026-09-23 10:39 ` [PATCH net-next v3 5/8] net: mdio: Add SoC-e SWIP MDIO controller driver Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
2026-09-25 23:17 ` Andrew Lunn
2026-09-25 23:20 ` Andrew Lunn
2026-09-23 10:39 ` [PATCH net-next v3 7/8] net: dsa: soce: Add VLAN offload support Vasilij Strassheim
2026-09-23 10:39 ` [PATCH net-next v3 8/8] net: dsa: soce: Disable unsupported hardware STP Vasilij Strassheim
7 siblings, 2 replies; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim
Add a DSA driver for SoC-e FPGA-based Ethernet switch IP cores.
Read the core version and synthesis-time feature registers during probe.
Require DSA support and between 3 and 31 implemented ports, without
exceeding the licensed port count. Derive each port's phylink
capabilities from its phy-mode.
Enable "DSA custom rules" tagging for all frames during setup. This
directs ingress traffic from user ports to the CPU port while standalone
ports remain isolated. Enable and disable port ingress and egress
through the DSA port callbacks, and disable tagging again during
teardown so that unbinding the driver does not leave its managed
configuration active.
Populate the integrated MDIO controller and mux child devices used for
external PHY access.
Tested with a SoC-e MRS 25.01 IP core on a Xilinx ZynqMP platform.
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/soce/Kconfig | 14 ++
drivers/net/dsa/soce/Makefile | 4 +
drivers/net/dsa/soce/soce_dsa.h | 26 +++
drivers/net/dsa/soce/soce_dsa_core.c | 343 +++++++++++++++++++++++++++++++++++
6 files changed, 390 insertions(+)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index fe8cd5338fda..879fbede83f0 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -94,6 +94,8 @@ source "drivers/net/dsa/ocelot/Kconfig"
source "drivers/net/dsa/qca/Kconfig"
+source "drivers/net/dsa/soce/Kconfig"
+
source "drivers/net/dsa/sja1105/Kconfig"
source "drivers/net/dsa/xrs700x/Kconfig"
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index 7e637d56b35c..97de3d181440 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -26,4 +26,5 @@ obj-y += ocelot/
obj-y += qca/
obj-y += realtek/
obj-y += sja1105/
+obj-y += soce/
obj-y += xrs700x/
diff --git a/drivers/net/dsa/soce/Kconfig b/drivers/net/dsa/soce/Kconfig
new file mode 100644
index 000000000000..c31b7c5af695
--- /dev/null
+++ b/drivers/net/dsa/soce/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+config NET_DSA_SOCE
+ tristate "SoC-e switches"
+ depends on NET_DSA
+ depends on OF
+ depends on HAS_IOMEM
+ select MDIO_BUS_MUX_MMIOREG
+ select MDIO_SOCE
+ select NET_DSA_TAG_SDSA
+ help
+ This enables support for switches based on SoC-e IP cores.
+ Frames are exchanged with the CPU port using the SDSA DSA tag protocol.
+ The driver supports switch variants whose features and number of ports
+ are selected at synthesis time and detected at runtime.
diff --git a/drivers/net/dsa/soce/Makefile b/drivers/net/dsa/soce/Makefile
new file mode 100644
index 000000000000..2a6d95ef663f
--- /dev/null
+++ b/drivers/net/dsa/soce/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_NET_DSA_SOCE) += soce_dsa.o
+soce_dsa-objs := soce_dsa_core.o
diff --git a/drivers/net/dsa/soce/soce_dsa.h b/drivers/net/dsa/soce/soce_dsa.h
new file mode 100644
index 000000000000..2acd4dbaf958
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_dsa.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020-2026 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ * Author: Vasilij Strassheim <v.strassheim@linutronix.de>
+ */
+
+#ifndef __SOCE_DSA_H
+#define __SOCE_DSA_H
+
+#include <linux/types.h>
+
+#include <net/dsa.h>
+
+#define SOCE_MAX_NUM_PORTS 31
+
+struct soce_dsa_local {
+ void __iomem *base_addr;
+};
+
+struct soce_priv {
+ struct soce_dsa_local local;
+ struct dsa_switch ds;
+};
+
+#endif /* __SOCE_DSA_H */
diff --git a/drivers/net/dsa/soce/soce_dsa_core.c b/drivers/net/dsa/soce/soce_dsa_core.c
new file mode 100644
index 000000000000..d391b11b94ad
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_dsa_core.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020-2026 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ * Author: Vasilij Strassheim <v.strassheim@linutronix.de>
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/phylink.h>
+#include <linux/platform_device.h>
+
+#include <net/dsa.h>
+
+#include "soce_dsa.h"
+
+#define SOCE_MIN_NUM_PORTS 3
+
+#define SOCE_CORE_VERSION_OFFSET 0x0000
+#define SOCE_CORE_VERSION_VERSION_SHIFT 24
+#define SOCE_CORE_VERSION_SUBVERSION_SHIFT 16
+#define SOCE_MIN_CORE_VERSION 0x24
+#define SOCE_MIN_CORE_SUBVERSION 0x01
+
+#define SOCE_LIC_FEATURES_OFFSET 0x0004
+#define SOCE_LIC_FEATURES_NUM_PORTS_MASK GENMASK(31, 27)
+
+#define SOCE_IMPL_FEATURES0_OFFSET 0x000c
+#define SOCE_IMPL_FEATURES0_NUM_PORTS_MASK GENMASK(31, 27)
+#define SOCE_IMPL_FEATURES0_PORT_VLAN BIT(9)
+#define SOCE_IMPL_FEATURES0_DSA BIT(23)
+
+#define SOCE_DSA_REGS_BASE 0x1200
+#define SOCE_TAG_ALL_FRAMES_CTRL_OFFSET (SOCE_DSA_REGS_BASE + 0x001c)
+#define SOCE_TAG_ALL_FRAMES_CTRL_ENABLE BIT(0)
+#define SOCE_CUSTOM_RULES_TAGGING_OFFSET (SOCE_DSA_REGS_BASE + 0x0020)
+#define SOCE_CUSTOM_RULES_TAGGING_ENABLE BIT(0)
+
+#define SOCE_PORTS_REGS_BASE 0x3000
+#define SOCE_PORTS_SELECTOR_OFFSET SOCE_PORTS_REGS_BASE
+#define SOCE_PORTS_SELECTOR_PORT_MASK GENMASK(7, 0)
+#define SOCE_PORTS_CTRL_OFFSET (SOCE_PORTS_REGS_BASE + 0x0004)
+#define SOCE_PORTS_CTRL_INGR_EN BIT(0)
+#define SOCE_PORTS_CTRL_EGR_EN BIT(1)
+
+static void soce_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ phy_interface_t mode;
+ int ret;
+
+ ret = of_get_phy_mode(dp->dn, &mode);
+ if (ret)
+ return;
+
+ if (phy_interface_mode_is_rgmii(mode))
+ phy_interface_set_rgmii(config->supported_interfaces);
+ else
+ __set_bit(mode, config->supported_interfaces);
+
+ config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
+
+ switch (mode) {
+ case PHY_INTERFACE_MODE_MII:
+ config->mac_capabilities |= MAC_10 | MAC_100;
+ break;
+ case PHY_INTERFACE_MODE_GMII:
+ config->mac_capabilities |= MAC_10 | MAC_100 | MAC_1000;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ config->mac_capabilities |= MAC_10FD | MAC_100FD;
+ break;
+ default:
+ if (phy_interface_mode_is_rgmii(mode))
+ config->mac_capabilities |= MAC_10FD | MAC_100FD |
+ MAC_1000FD;
+ break;
+ }
+}
+
+static int soce_sw_validate_core_version(u8 version, u8 subversion)
+{
+ if (version < SOCE_MIN_CORE_VERSION ||
+ (version == SOCE_MIN_CORE_VERSION &&
+ subversion < SOCE_MIN_CORE_SUBVERSION))
+ return -ENODEV;
+
+ return 0;
+}
+
+static void soce_sw_read_core_version(struct soce_dsa_local *local,
+ u8 *version, u8 *subversion,
+ u16 *revision)
+{
+ u32 regval;
+
+ regval = readl(local->base_addr + SOCE_CORE_VERSION_OFFSET);
+ *version = (u8)(regval >> SOCE_CORE_VERSION_VERSION_SHIFT);
+ *subversion = (u8)(regval >> SOCE_CORE_VERSION_SUBVERSION_SHIFT);
+ *revision = (u16)regval;
+}
+
+static int soce_sw_detect_features(struct soce_dsa_local *local,
+ u32 *numports)
+{
+ void __iomem *base = local->base_addr;
+ u32 implemented_numports;
+ u32 licensed_numports;
+ u32 regval;
+
+ regval = readl(base + SOCE_LIC_FEATURES_OFFSET);
+ licensed_numports = FIELD_GET(SOCE_LIC_FEATURES_NUM_PORTS_MASK, regval);
+ if (!licensed_numports || licensed_numports > SOCE_MAX_NUM_PORTS)
+ return -EINVAL;
+
+ regval = readl(base + SOCE_IMPL_FEATURES0_OFFSET);
+ if (!(regval & SOCE_IMPL_FEATURES0_DSA))
+ return -ENODEV;
+
+ implemented_numports =
+ FIELD_GET(SOCE_IMPL_FEATURES0_NUM_PORTS_MASK, regval);
+ if (implemented_numports < SOCE_MIN_NUM_PORTS ||
+ implemented_numports > licensed_numports)
+ return -EINVAL;
+
+ *numports = implemented_numports;
+
+ return 0;
+}
+
+static void soce_sw_enable_tagging(struct soce_dsa_local *local)
+{
+ void __iomem *base = local->base_addr;
+ u32 regval;
+
+ regval = readl(base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
+ regval |= SOCE_TAG_ALL_FRAMES_CTRL_ENABLE;
+ writel(regval, base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
+
+ regval = readl(base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
+ regval |= SOCE_CUSTOM_RULES_TAGGING_ENABLE;
+ writel(regval, base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
+}
+
+static void soce_sw_disable_tagging(struct soce_dsa_local *local)
+{
+ void __iomem *base = local->base_addr;
+ u32 regval;
+
+ regval = readl(base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
+ regval &= ~SOCE_TAG_ALL_FRAMES_CTRL_ENABLE;
+ writel(regval, base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
+
+ regval = readl(base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
+ regval &= ~SOCE_CUSTOM_RULES_TAGGING_ENABLE;
+ writel(regval, base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
+}
+
+static void soce_port_select(struct soce_dsa_local *local, int port)
+{
+ writel(FIELD_PREP(SOCE_PORTS_SELECTOR_PORT_MASK, port),
+ local->base_addr + SOCE_PORTS_SELECTOR_OFFSET);
+}
+
+static void soce_port_set_enabled(struct soce_dsa_local *local, int port,
+ bool enabled)
+{
+ void __iomem *base = local->base_addr;
+ u32 regval;
+
+ soce_port_select(local, port);
+
+ regval = readl(base + SOCE_PORTS_CTRL_OFFSET);
+ if (enabled)
+ regval |= SOCE_PORTS_CTRL_INGR_EN | SOCE_PORTS_CTRL_EGR_EN;
+ else
+ regval &= ~(SOCE_PORTS_CTRL_INGR_EN | SOCE_PORTS_CTRL_EGR_EN);
+ writel(regval, base + SOCE_PORTS_CTRL_OFFSET);
+}
+
+static int soce_port_enable(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct soce_priv *priv = ds->priv;
+
+ soce_port_set_enabled(&priv->local, port, true);
+
+ return 0;
+}
+
+static void soce_port_disable(struct dsa_switch *ds, int port)
+{
+ struct soce_priv *priv = ds->priv;
+
+ soce_port_set_enabled(&priv->local, port, false);
+}
+
+static int soce_setup(struct dsa_switch *ds)
+{
+ struct soce_priv *priv = ds->priv;
+
+ soce_sw_enable_tagging(&priv->local);
+
+ return 0;
+}
+
+static void soce_teardown(struct dsa_switch *ds)
+{
+ struct soce_priv *priv = ds->priv;
+
+ soce_sw_disable_tagging(&priv->local);
+}
+
+static enum dsa_tag_protocol soce_get_tag_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol mprop)
+{
+ return DSA_TAG_PROTO_SDSA;
+}
+
+static const struct dsa_switch_ops soce_switch_ops = {
+ .get_tag_protocol = soce_get_tag_protocol,
+ .setup = soce_setup,
+ .teardown = soce_teardown,
+ .phylink_get_caps = soce_phylink_get_caps,
+ .port_enable = soce_port_enable,
+ .port_disable = soce_port_disable,
+};
+
+static int soce_sw_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct soce_dsa_local *local;
+ struct soce_priv *priv;
+ struct dsa_switch *ds;
+ u8 hw_subversion;
+ u16 hw_revision;
+ u32 hw_numports;
+ u8 hw_version;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ds = &priv->ds;
+ ds->dev = dev;
+ ds->priv = priv;
+
+ local = &priv->local;
+ local->base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(local->base_addr))
+ return PTR_ERR(local->base_addr);
+
+ soce_sw_read_core_version(local, &hw_version, &hw_subversion,
+ &hw_revision);
+
+ ret = soce_sw_detect_features(local, &hw_numports);
+ if (ret) {
+ if (ret == -ENODEV)
+ dev_err(dev, "switch core does not implement DSA\n");
+ else
+ dev_err(dev,
+ "invalid licensed or implemented features register\n");
+ return ret;
+ }
+
+ ret = soce_sw_validate_core_version(hw_version, hw_subversion);
+ if (ret) {
+ dev_err(dev, "unsupported switch core version %.2X.%.2X.%.4X\n",
+ hw_version, hw_subversion, hw_revision);
+ return ret;
+ }
+
+ ds->ops = &soce_switch_ops;
+ ds->num_ports = hw_numports;
+ ret = devm_of_platform_populate(dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to populate child devices\n");
+
+ dev_set_drvdata(dev, priv);
+
+ ret = dsa_register_switch(ds);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register DSA switch\n");
+
+ dev_info(dev,
+ "probed soce switch core version %02x.%02x.%04x with %u ports\n",
+ hw_version, hw_subversion, hw_revision, hw_numports);
+ return 0;
+}
+
+static void soce_sw_remove(struct platform_device *pdev)
+{
+ struct soce_priv *priv = platform_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_unregister_switch(&priv->ds);
+ platform_set_drvdata(pdev, NULL);
+}
+
+static void soce_sw_shutdown(struct platform_device *pdev)
+{
+ struct soce_priv *priv = platform_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_switch_shutdown(&priv->ds);
+ platform_set_drvdata(pdev, NULL);
+}
+
+static const struct of_device_id soce_of_match[] = {
+ { .compatible = "soce,swip-00-04-0c-10" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver soce_driver = {
+ .probe = soce_sw_probe,
+ .remove = soce_sw_remove,
+ .shutdown = soce_sw_shutdown,
+ .driver = {
+ .name = "soce-swip",
+ .of_match_table = soce_of_match,
+ },
+};
+
+module_platform_driver(soce_driver);
+MODULE_DEVICE_TABLE(of, soce_of_match);
+MODULE_AUTHOR("Vasilij Strassheim <v.strassheim@linutronix.de>");
+MODULE_DESCRIPTION("Driver for SoC-e ethernet switch family");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: mdio-soce mdio-mux-mmioreg");
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net-next v3 6/8] net: dsa: soce: Add basic support for SoC-e switch IP cores
2026-09-23 10:39 ` [PATCH net-next v3 6/8] net: dsa: soce: Add basic support for SoC-e switch IP cores Vasilij Strassheim
@ 2026-09-25 23:17 ` Andrew Lunn
2026-09-25 23:20 ` Andrew Lunn
1 sibling, 0 replies; 17+ messages in thread
From: Andrew Lunn @ 2026-09-25 23:17 UTC (permalink / raw)
To: Vasilij Strassheim
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Russell King, Andrew Lunn, Heiner Kallweit,
devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger
> +static void soce_sw_read_core_version(struct soce_dsa_local *local,
> + u8 *version, u8 *subversion,
> + u16 *revision)
> +{
> + u32 regval;
> +
> + regval = readl(local->base_addr + SOCE_CORE_VERSION_OFFSET);
> + *version = (u8)(regval >> SOCE_CORE_VERSION_VERSION_SHIFT);
> + *subversion = (u8)(regval >> SOCE_CORE_VERSION_SUBVERSION_SHIFT);
> + *revision = (u16)regval;
FIELD_GET() would make this more readable.
> +static int soce_sw_detect_features(struct soce_dsa_local *local,
> + u32 *numports)
> +{
> + void __iomem *base = local->base_addr;
> + u32 implemented_numports;
> + u32 licensed_numports;
> + u32 regval;
> +
> + regval = readl(base + SOCE_LIC_FEATURES_OFFSET);
> + licensed_numports = FIELD_GET(SOCE_LIC_FEATURES_NUM_PORTS_MASK, regval);
> + if (!licensed_numports || licensed_numports > SOCE_MAX_NUM_PORTS)
> + return -EINVAL;
> +
> + regval = readl(base + SOCE_IMPL_FEATURES0_OFFSET);
> + if (!(regval & SOCE_IMPL_FEATURES0_DSA))
> + return -ENODEV;
> +
> + implemented_numports =
> + FIELD_GET(SOCE_IMPL_FEATURES0_NUM_PORTS_MASK, regval);
> + if (implemented_numports < SOCE_MIN_NUM_PORTS ||
> + implemented_numports > licensed_numports)
> + return -EINVAL;
Maybe add dev_err() here for all these error cases. It will help
somebody debug why there switch fails to probe.
Andrew
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net-next v3 6/8] net: dsa: soce: Add basic support for SoC-e switch IP cores
2026-09-23 10:39 ` [PATCH net-next v3 6/8] net: dsa: soce: Add basic support for SoC-e switch IP cores Vasilij Strassheim
2026-09-25 23:17 ` Andrew Lunn
@ 2026-09-25 23:20 ` Andrew Lunn
1 sibling, 0 replies; 17+ messages in thread
From: Andrew Lunn @ 2026-09-25 23:20 UTC (permalink / raw)
To: Vasilij Strassheim
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Russell King, Andrew Lunn, Heiner Kallweit,
devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger
On Wed, Sep 23, 2026 at 12:39:33PM +0200, Vasilij Strassheim wrote:
> Add a DSA driver for SoC-e FPGA-based Ethernet switch IP cores.
>
> Read the core version and synthesis-time feature registers during probe.
> Require DSA support and between 3 and 31 implemented ports, without
> exceeding the licensed port count. Derive each port's phylink
> capabilities from its phy-mode.
>
> Enable "DSA custom rules" tagging for all frames during setup. This
> directs ingress traffic from user ports to the CPU port while standalone
> ports remain isolated. Enable and disable port ingress and egress
> through the DSA port callbacks, and disable tagging again during
> teardown so that unbinding the driver does not leave its managed
> configuration active.
>
> Populate the integrated MDIO controller and mux child devices used for
> external PHY access.
>
> Tested with a SoC-e MRS 25.01 IP core on a Xilinx ZynqMP platform.
>
> Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
> ---
> drivers/net/dsa/Kconfig | 2 +
> drivers/net/dsa/Makefile | 1 +
> drivers/net/dsa/soce/Kconfig | 14 ++
> drivers/net/dsa/soce/Makefile | 4 +
> drivers/net/dsa/soce/soce_dsa.h | 26 +++
> drivers/net/dsa/soce/soce_dsa_core.c | 343 +++++++++++++++++++++++++++++++++++
> 6 files changed, 390 insertions(+)
>
> diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
> index fe8cd5338fda..879fbede83f0 100644
> --- a/drivers/net/dsa/Kconfig
> +++ b/drivers/net/dsa/Kconfig
> @@ -94,6 +94,8 @@ source "drivers/net/dsa/ocelot/Kconfig"
>
> source "drivers/net/dsa/qca/Kconfig"
>
> +source "drivers/net/dsa/soce/Kconfig"
> +
> source "drivers/net/dsa/sja1105/Kconfig"
>
> source "drivers/net/dsa/xrs700x/Kconfig"
> diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
> index 7e637d56b35c..97de3d181440 100644
> --- a/drivers/net/dsa/Makefile
> +++ b/drivers/net/dsa/Makefile
> @@ -26,4 +26,5 @@ obj-y += ocelot/
> obj-y += qca/
> obj-y += realtek/
> obj-y += sja1105/
> +obj-y += soce/
> obj-y += xrs700x/
> diff --git a/drivers/net/dsa/soce/Kconfig b/drivers/net/dsa/soce/Kconfig
> new file mode 100644
> index 000000000000..c31b7c5af695
> --- /dev/null
> +++ b/drivers/net/dsa/soce/Kconfig
> @@ -0,0 +1,14 @@
> +# SPDX-License-Identifier: GPL-2.0
> +config NET_DSA_SOCE
> + tristate "SoC-e switches"
> + depends on NET_DSA
> + depends on OF
> + depends on HAS_IOMEM
> + select MDIO_BUS_MUX_MMIOREG
> + select MDIO_SOCE
> + select NET_DSA_TAG_SDSA
> + help
> + This enables support for switches based on SoC-e IP cores.
> + Frames are exchanged with the CPU port using the SDSA DSA tag protocol.
> + The driver supports switch variants whose features and number of ports
> + are selected at synthesis time and detected at runtime.
> diff --git a/drivers/net/dsa/soce/Makefile b/drivers/net/dsa/soce/Makefile
> new file mode 100644
> index 000000000000..2a6d95ef663f
> --- /dev/null
> +++ b/drivers/net/dsa/soce/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-$(CONFIG_NET_DSA_SOCE) += soce_dsa.o
> +soce_dsa-objs := soce_dsa_core.o
> diff --git a/drivers/net/dsa/soce/soce_dsa.h b/drivers/net/dsa/soce/soce_dsa.h
> new file mode 100644
> index 000000000000..2acd4dbaf958
> --- /dev/null
> +++ b/drivers/net/dsa/soce/soce_dsa.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2020-2026 System on Chip engineering, S.L.
> + * Copyright (c) 2026 Linutronix GmbH
> + * Author: Vasilij Strassheim <v.strassheim@linutronix.de>
> + */
> +
> +#ifndef __SOCE_DSA_H
> +#define __SOCE_DSA_H
> +
> +#include <linux/types.h>
> +
> +#include <net/dsa.h>
> +
> +#define SOCE_MAX_NUM_PORTS 31
> +
> +struct soce_dsa_local {
> + void __iomem *base_addr;
> +};
> +
> +struct soce_priv {
> + struct soce_dsa_local local;
> + struct dsa_switch ds;
> +};
> +
> +#endif /* __SOCE_DSA_H */
> diff --git a/drivers/net/dsa/soce/soce_dsa_core.c b/drivers/net/dsa/soce/soce_dsa_core.c
> new file mode 100644
> index 000000000000..d391b11b94ad
> --- /dev/null
> +++ b/drivers/net/dsa/soce/soce_dsa_core.c
> @@ -0,0 +1,343 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020-2026 System on Chip engineering, S.L.
> + * Copyright (c) 2026 Linutronix GmbH
> + * Author: Vasilij Strassheim <v.strassheim@linutronix.de>
> + */
> +
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/of.h>
> +#include <linux/of_net.h>
> +#include <linux/of_platform.h>
> +#include <linux/phy.h>
> +#include <linux/phylink.h>
> +#include <linux/platform_device.h>
> +
> +#include <net/dsa.h>
> +
> +#include "soce_dsa.h"
> +
> +#define SOCE_MIN_NUM_PORTS 3
> +
> +#define SOCE_CORE_VERSION_OFFSET 0x0000
> +#define SOCE_CORE_VERSION_VERSION_SHIFT 24
> +#define SOCE_CORE_VERSION_SUBVERSION_SHIFT 16
> +#define SOCE_MIN_CORE_VERSION 0x24
> +#define SOCE_MIN_CORE_SUBVERSION 0x01
> +
> +#define SOCE_LIC_FEATURES_OFFSET 0x0004
> +#define SOCE_LIC_FEATURES_NUM_PORTS_MASK GENMASK(31, 27)
> +
> +#define SOCE_IMPL_FEATURES0_OFFSET 0x000c
> +#define SOCE_IMPL_FEATURES0_NUM_PORTS_MASK GENMASK(31, 27)
> +#define SOCE_IMPL_FEATURES0_PORT_VLAN BIT(9)
> +#define SOCE_IMPL_FEATURES0_DSA BIT(23)
> +
> +#define SOCE_DSA_REGS_BASE 0x1200
> +#define SOCE_TAG_ALL_FRAMES_CTRL_OFFSET (SOCE_DSA_REGS_BASE + 0x001c)
> +#define SOCE_TAG_ALL_FRAMES_CTRL_ENABLE BIT(0)
> +#define SOCE_CUSTOM_RULES_TAGGING_OFFSET (SOCE_DSA_REGS_BASE + 0x0020)
> +#define SOCE_CUSTOM_RULES_TAGGING_ENABLE BIT(0)
> +
> +#define SOCE_PORTS_REGS_BASE 0x3000
> +#define SOCE_PORTS_SELECTOR_OFFSET SOCE_PORTS_REGS_BASE
> +#define SOCE_PORTS_SELECTOR_PORT_MASK GENMASK(7, 0)
> +#define SOCE_PORTS_CTRL_OFFSET (SOCE_PORTS_REGS_BASE + 0x0004)
> +#define SOCE_PORTS_CTRL_INGR_EN BIT(0)
> +#define SOCE_PORTS_CTRL_EGR_EN BIT(1)
> +
> +static void soce_phylink_get_caps(struct dsa_switch *ds, int port,
> + struct phylink_config *config)
> +{
> + struct dsa_port *dp = dsa_to_port(ds, port);
> + phy_interface_t mode;
> + int ret;
> +
> + ret = of_get_phy_mode(dp->dn, &mode);
> + if (ret)
> + return;
> +
> + if (phy_interface_mode_is_rgmii(mode))
> + phy_interface_set_rgmii(config->supported_interfaces);
> + else
> + __set_bit(mode, config->supported_interfaces);
> +
> + config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
> +
> + switch (mode) {
> + case PHY_INTERFACE_MODE_MII:
> + config->mac_capabilities |= MAC_10 | MAC_100;
> + break;
> + case PHY_INTERFACE_MODE_GMII:
> + config->mac_capabilities |= MAC_10 | MAC_100 | MAC_1000;
> + break;
> + case PHY_INTERFACE_MODE_RMII:
> + config->mac_capabilities |= MAC_10FD | MAC_100FD;
> + break;
> + default:
> + if (phy_interface_mode_is_rgmii(mode))
> + config->mac_capabilities |= MAC_10FD | MAC_100FD |
> + MAC_1000FD;
> + break;
> + }
> +}
> +
> +static int soce_sw_validate_core_version(u8 version, u8 subversion)
> +{
> + if (version < SOCE_MIN_CORE_VERSION ||
> + (version == SOCE_MIN_CORE_VERSION &&
> + subversion < SOCE_MIN_CORE_SUBVERSION))
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static void soce_sw_read_core_version(struct soce_dsa_local *local,
> + u8 *version, u8 *subversion,
> + u16 *revision)
> +{
> + u32 regval;
> +
> + regval = readl(local->base_addr + SOCE_CORE_VERSION_OFFSET);
> + *version = (u8)(regval >> SOCE_CORE_VERSION_VERSION_SHIFT);
> + *subversion = (u8)(regval >> SOCE_CORE_VERSION_SUBVERSION_SHIFT);
> + *revision = (u16)regval;
> +}
> +
> +static int soce_sw_detect_features(struct soce_dsa_local *local,
> + u32 *numports)
> +{
> + void __iomem *base = local->base_addr;
> + u32 implemented_numports;
> + u32 licensed_numports;
> + u32 regval;
> +
> + regval = readl(base + SOCE_LIC_FEATURES_OFFSET);
> + licensed_numports = FIELD_GET(SOCE_LIC_FEATURES_NUM_PORTS_MASK, regval);
> + if (!licensed_numports || licensed_numports > SOCE_MAX_NUM_PORTS)
> + return -EINVAL;
> +
> + regval = readl(base + SOCE_IMPL_FEATURES0_OFFSET);
> + if (!(regval & SOCE_IMPL_FEATURES0_DSA))
> + return -ENODEV;
> +
> + implemented_numports =
> + FIELD_GET(SOCE_IMPL_FEATURES0_NUM_PORTS_MASK, regval);
> + if (implemented_numports < SOCE_MIN_NUM_PORTS ||
> + implemented_numports > licensed_numports)
> + return -EINVAL;
> +
> + *numports = implemented_numports;
> +
> + return 0;
> +}
> +
> +static void soce_sw_enable_tagging(struct soce_dsa_local *local)
> +{
> + void __iomem *base = local->base_addr;
> + u32 regval;
> +
> + regval = readl(base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
> + regval |= SOCE_TAG_ALL_FRAMES_CTRL_ENABLE;
> + writel(regval, base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
> +
> + regval = readl(base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
> + regval |= SOCE_CUSTOM_RULES_TAGGING_ENABLE;
> + writel(regval, base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
> +}
> +
> +static void soce_sw_disable_tagging(struct soce_dsa_local *local)
> +{
> + void __iomem *base = local->base_addr;
> + u32 regval;
> +
> + regval = readl(base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
> + regval &= ~SOCE_TAG_ALL_FRAMES_CTRL_ENABLE;
> + writel(regval, base + SOCE_TAG_ALL_FRAMES_CTRL_OFFSET);
> +
> + regval = readl(base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
> + regval &= ~SOCE_CUSTOM_RULES_TAGGING_ENABLE;
> + writel(regval, base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
> +}
> +
> +static void soce_port_select(struct soce_dsa_local *local, int port)
> +{
> + writel(FIELD_PREP(SOCE_PORTS_SELECTOR_PORT_MASK, port),
> + local->base_addr + SOCE_PORTS_SELECTOR_OFFSET);
> +}
> +
> +static void soce_port_set_enabled(struct soce_dsa_local *local, int port,
> + bool enabled)
> +{
> + void __iomem *base = local->base_addr;
> + u32 regval;
> +
> + soce_port_select(local, port);
> +
> + regval = readl(base + SOCE_PORTS_CTRL_OFFSET);
> + if (enabled)
> + regval |= SOCE_PORTS_CTRL_INGR_EN | SOCE_PORTS_CTRL_EGR_EN;
> + else
> + regval &= ~(SOCE_PORTS_CTRL_INGR_EN | SOCE_PORTS_CTRL_EGR_EN);
> + writel(regval, base + SOCE_PORTS_CTRL_OFFSET);
> +}
> +
> +static int soce_port_enable(struct dsa_switch *ds, int port,
> + struct phy_device *phy)
> +{
> + struct soce_priv *priv = ds->priv;
> +
> + soce_port_set_enabled(&priv->local, port, true);
> +
> + return 0;
> +}
> +
> +static void soce_port_disable(struct dsa_switch *ds, int port)
> +{
> + struct soce_priv *priv = ds->priv;
> +
> + soce_port_set_enabled(&priv->local, port, false);
> +}
> +
> +static int soce_setup(struct dsa_switch *ds)
> +{
> + struct soce_priv *priv = ds->priv;
> +
> + soce_sw_enable_tagging(&priv->local);
Once all the setup is finished, what is the state of the switch?
What we want is that the user ports are isolated from each other, and
can only exchange frames with the CPU. That makes the hardware
basically a port expander, and you do bridging in software. Later
patches can then add offload of bridging, and whatever else the
hardware can do, which Linux can also do in software.
Andrew
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v3 7/8] net: dsa: soce: Add VLAN offload support
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
` (5 preceding siblings ...)
2026-09-23 10:39 ` [PATCH net-next v3 6/8] net: dsa: soce: Add basic support for SoC-e switch IP cores Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
2026-09-25 23:32 ` Andrew Lunn
2026-09-23 10:39 ` [PATCH net-next v3 8/8] net: dsa: soce: Disable unsupported hardware STP Vasilij Strassheim
7 siblings, 1 reply; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim
Add VLAN filtering and membership offload for switch configurations that
implement the Port VLAN synthesis option. Without this feature active,
the switch removes VLAN information on CPU ingress without including the
removed information in the DSA tag. To avoid silent drops and
misbehaviour in this case, reject VLAN operations with a netlink
extended acknowledgment.
If the feature is available, configure ports in Hybrid and C-Port modes
and manage their PVID, ingress filtering and acceptance mode, and custom
egress untagging. Program the per-VID member and untagged port masks
through the hardware selector interface.
The port and VID selectors are shared by all VLAN operations, so
serialize selector and data register sequences with a dedicated mutex.
Track member and untagged masks in software and restore the shadow state
if programming fails.
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
drivers/net/dsa/soce/soce_dsa.h | 11 +
drivers/net/dsa/soce/soce_dsa_core.c | 388 ++++++++++++++++++++++++++++++++++-
2 files changed, 392 insertions(+), 7 deletions(-)
diff --git a/drivers/net/dsa/soce/soce_dsa.h b/drivers/net/dsa/soce/soce_dsa.h
index 2acd4dbaf958..41533a89d680 100644
--- a/drivers/net/dsa/soce/soce_dsa.h
+++ b/drivers/net/dsa/soce/soce_dsa.h
@@ -9,6 +9,7 @@
#define __SOCE_DSA_H
#include <linux/types.h>
+#include <linux/mutex.h>
#include <net/dsa.h>
@@ -18,8 +19,18 @@ struct soce_dsa_local {
void __iomem *base_addr;
};
+struct soce_features {
+ u32 num_ports;
+ bool port_vlan;
+};
+
struct soce_priv {
struct soce_dsa_local local;
+ struct soce_features features;
+ struct mutex vlan_lock; /* Serializes selector-based VLAN accesses */
+ u32 *vlan_members;
+ u32 *vlan_untagged;
+ u16 port_pvid[SOCE_MAX_NUM_PORTS];
struct dsa_switch ds;
};
diff --git a/drivers/net/dsa/soce/soce_dsa_core.c b/drivers/net/dsa/soce/soce_dsa_core.c
index d391b11b94ad..f93ce9da8400 100644
--- a/drivers/net/dsa/soce/soce_dsa_core.c
+++ b/drivers/net/dsa/soce/soce_dsa_core.c
@@ -5,7 +5,10 @@
* Author: Vasilij Strassheim <v.strassheim@linutronix.de>
*/
+#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/of.h>
@@ -48,6 +51,39 @@
#define SOCE_PORTS_CTRL_INGR_EN BIT(0)
#define SOCE_PORTS_CTRL_EGR_EN BIT(1)
+#define SOCE_VLAN_REGS_BASE 0x0d00
+#define SOCE_VLAN_CTRL_OFFSET SOCE_VLAN_REGS_BASE
+#define SOCE_VLAN_CTRL_ENABLE BIT(0)
+#define SOCE_VLAN_RESET_OFFSET (SOCE_VLAN_REGS_BASE + 0x0004)
+#define SOCE_VLAN_RESET_CMD BIT(0)
+#define SOCE_VLAN_PORT_SEL_OFFSET (SOCE_VLAN_REGS_BASE + 0x0010)
+#define SOCE_VLAN_PORT_SEL_PORT_MASK GENMASK(7, 0)
+#define SOCE_VLAN_PORT_MODE_OFFSET (SOCE_VLAN_REGS_BASE + 0x0014)
+#define SOCE_VLAN_PORT_MODE_HYBRID 0x2
+#define SOCE_VLAN_PORT_TYPE_OFFSET (SOCE_VLAN_REGS_BASE + 0x0018)
+#define SOCE_VLAN_PORT_TYPE_UNAWARE 0x0
+#define SOCE_VLAN_PORT_TYPE_C_PORT 0x1
+#define SOCE_VLAN_PORT_VLAN_OFFSET (SOCE_VLAN_REGS_BASE + 0x001c)
+#define SOCE_VLAN_PORT_VLAN_PVID_MASK GENMASK(11, 0)
+#define SOCE_VLAN_PORT_INGR_FILTER_OFFSET (SOCE_VLAN_REGS_BASE + 0x0020)
+#define SOCE_VLAN_PORT_INGR_FILTER_EN BIT(0)
+#define SOCE_VLAN_PORT_INGR_ACCEPT_OFFSET (SOCE_VLAN_REGS_BASE + 0x0024)
+#define SOCE_VLAN_PORT_INGR_ACCEPT_ALL 0x0
+#define SOCE_VLAN_PORT_INGR_ACCEPT_TAGGED_ONLY 0x1
+#define SOCE_VLAN_PORT_EGR_TAG_OFFSET (SOCE_VLAN_REGS_BASE + 0x0028)
+#define SOCE_VLAN_PORT_EGR_TAG_MASK GENMASK(1, 0)
+#define SOCE_VLAN_PORT_EGR_TAG_UNTAG_PORT 0x0
+#define SOCE_VLAN_PORT_EGR_TAG_CUSTOM_UNTAG 0x3
+#define SOCE_VLAN_VID_SEL_OFFSET (SOCE_VLAN_REGS_BASE + 0x002c)
+#define SOCE_VLAN_VID_SEL_VID_MASK GENMASK(11, 0)
+#define SOCE_VLAN_MEMBER_CTRL_OFFSET (SOCE_VLAN_REGS_BASE + 0x0030)
+#define SOCE_VLAN_MEMBER_CTRL_WRITE BIT(0)
+#define SOCE_VLAN_MEMBER_PORTS_OFFSET (SOCE_VLAN_REGS_BASE + 0x0034)
+#define SOCE_VLAN_UNTAG_CTRL_OFFSET (SOCE_VLAN_REGS_BASE + 0x003c)
+#define SOCE_VLAN_UNTAG_CTRL_WRITE BIT(0)
+#define SOCE_VLAN_UNTAG_PORTS_OFFSET (SOCE_VLAN_REGS_BASE + 0x0040)
+#define SOCE_VLAN_CMD_TIMEOUT_US 1000
+
static void soce_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
@@ -107,7 +143,7 @@ static void soce_sw_read_core_version(struct soce_dsa_local *local,
}
static int soce_sw_detect_features(struct soce_dsa_local *local,
- u32 *numports)
+ struct soce_features *features)
{
void __iomem *base = local->base_addr;
u32 implemented_numports;
@@ -123,13 +159,15 @@ static int soce_sw_detect_features(struct soce_dsa_local *local,
if (!(regval & SOCE_IMPL_FEATURES0_DSA))
return -ENODEV;
+ features->port_vlan = regval & SOCE_IMPL_FEATURES0_PORT_VLAN;
+
implemented_numports =
FIELD_GET(SOCE_IMPL_FEATURES0_NUM_PORTS_MASK, regval);
if (implemented_numports < SOCE_MIN_NUM_PORTS ||
implemented_numports > licensed_numports)
return -EINVAL;
- *numports = implemented_numports;
+ features->num_ports = implemented_numports;
return 0;
}
@@ -162,6 +200,178 @@ static void soce_sw_disable_tagging(struct soce_dsa_local *local)
writel(regval, base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
}
+static void soce_vlan_set_enabled(struct soce_dsa_local *local, bool enabled)
+{
+ void __iomem *base = local->base_addr;
+ u32 regval;
+
+ regval = readl(base + SOCE_VLAN_CTRL_OFFSET);
+ if (enabled)
+ regval |= SOCE_VLAN_CTRL_ENABLE;
+ else
+ regval &= ~SOCE_VLAN_CTRL_ENABLE;
+ writel(regval, base + SOCE_VLAN_CTRL_OFFSET);
+}
+
+static int soce_vlan_reset(struct soce_dsa_local *local)
+{
+ void __iomem *base = local->base_addr;
+ u32 regval;
+
+ writel(SOCE_VLAN_RESET_CMD, base + SOCE_VLAN_RESET_OFFSET);
+
+ return readl_poll_timeout(base + SOCE_VLAN_RESET_OFFSET,
+ regval, !(regval & SOCE_VLAN_RESET_CMD), 10,
+ SOCE_VLAN_CMD_TIMEOUT_US);
+}
+
+static int soce_vlan_wait_for_write(struct soce_dsa_local *local, u32 offset,
+ u32 mask)
+{
+ void __iomem *base = local->base_addr;
+ u32 regval;
+
+ return readl_poll_timeout(base + offset, regval,
+ !(regval & mask), 10,
+ SOCE_VLAN_CMD_TIMEOUT_US);
+}
+
+static void soce_vlan_select_port(struct soce_dsa_local *local, int port)
+{
+ writel(FIELD_PREP(SOCE_VLAN_PORT_SEL_PORT_MASK, port),
+ local->base_addr + SOCE_VLAN_PORT_SEL_OFFSET);
+}
+
+static void soce_vlan_select_vid(struct soce_dsa_local *local, u16 vid)
+{
+ writel(FIELD_PREP(SOCE_VLAN_VID_SEL_VID_MASK, vid),
+ local->base_addr + SOCE_VLAN_VID_SEL_OFFSET);
+}
+
+static void soce_vlan_config_port(struct soce_priv *priv, int port,
+ bool vlan_filtering)
+{
+ struct soce_dsa_local *local = &priv->local;
+ void __iomem *base = local->base_addr;
+ u32 egress_tagging;
+ u32 port_type;
+ u32 regval;
+ u32 accept;
+
+ port_type = vlan_filtering ? SOCE_VLAN_PORT_TYPE_C_PORT :
+ SOCE_VLAN_PORT_TYPE_UNAWARE;
+ egress_tagging = vlan_filtering ? SOCE_VLAN_PORT_EGR_TAG_CUSTOM_UNTAG :
+ SOCE_VLAN_PORT_EGR_TAG_UNTAG_PORT;
+
+ soce_vlan_select_port(local, port);
+ writel(SOCE_VLAN_PORT_MODE_HYBRID, base + SOCE_VLAN_PORT_MODE_OFFSET);
+ writel(port_type, base + SOCE_VLAN_PORT_TYPE_OFFSET);
+ writel(FIELD_PREP(SOCE_VLAN_PORT_VLAN_PVID_MASK,
+ priv->port_pvid[port]),
+ base + SOCE_VLAN_PORT_VLAN_OFFSET);
+ writel(vlan_filtering ? SOCE_VLAN_PORT_INGR_FILTER_EN : 0,
+ base + SOCE_VLAN_PORT_INGR_FILTER_OFFSET);
+
+ /* Without a PVID, untagged frames have no VLAN to be classified
+ * into, so only accept tagged frames while filtering.
+ */
+ accept = vlan_filtering && !priv->port_pvid[port] ?
+ SOCE_VLAN_PORT_INGR_ACCEPT_TAGGED_ONLY :
+ SOCE_VLAN_PORT_INGR_ACCEPT_ALL;
+ writel(accept, base + SOCE_VLAN_PORT_INGR_ACCEPT_OFFSET);
+
+ regval = readl(base + SOCE_VLAN_PORT_EGR_TAG_OFFSET);
+ regval &= ~SOCE_VLAN_PORT_EGR_TAG_MASK;
+ regval |= FIELD_PREP(SOCE_VLAN_PORT_EGR_TAG_MASK, egress_tagging);
+ writel(regval, base + SOCE_VLAN_PORT_EGR_TAG_OFFSET);
+}
+
+static int soce_vlan_write_entry(struct soce_priv *priv, u16 vid)
+{
+ struct soce_dsa_local *local = &priv->local;
+ void __iomem *base = local->base_addr;
+ u32 cpu_ports;
+ u32 untagged;
+ u32 members;
+ int ret;
+
+ /* The CPU port must be a tagged member of every active VLAN so
+ * tagged frames can reach the conduit.
+ */
+ cpu_ports = dsa_cpu_ports(&priv->ds);
+ members = priv->vlan_members[vid];
+ if (members)
+ members |= cpu_ports;
+ untagged = priv->vlan_untagged[vid] & ~cpu_ports;
+
+ soce_vlan_select_vid(local, vid);
+ writel(members, base + SOCE_VLAN_MEMBER_PORTS_OFFSET);
+ writel(SOCE_VLAN_MEMBER_CTRL_WRITE,
+ base + SOCE_VLAN_MEMBER_CTRL_OFFSET);
+ ret = soce_vlan_wait_for_write(local, SOCE_VLAN_MEMBER_CTRL_OFFSET,
+ SOCE_VLAN_MEMBER_CTRL_WRITE);
+ if (ret)
+ return ret;
+
+ writel(untagged, base + SOCE_VLAN_UNTAG_PORTS_OFFSET);
+ writel(SOCE_VLAN_UNTAG_CTRL_WRITE,
+ base + SOCE_VLAN_UNTAG_CTRL_OFFSET);
+
+ return soce_vlan_wait_for_write(local, SOCE_VLAN_UNTAG_CTRL_OFFSET,
+ SOCE_VLAN_UNTAG_CTRL_WRITE);
+}
+
+static int soce_vlan_setup(struct dsa_switch *ds)
+{
+ struct soce_priv *priv = ds->priv;
+ struct soce_dsa_local *local;
+ struct dsa_port *dp;
+ int ret;
+
+ local = &priv->local;
+
+ if (!priv->features.port_vlan)
+ return 0;
+
+ ret = soce_vlan_reset(local);
+ if (ret) {
+ dev_err(ds->dev, "failed to reset VLAN configuration: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Default every port to PVID 1, unfiltered, so standalone
+ * forwarding keeps working before any bridge VLAN is configured.
+ */
+ scoped_guard(mutex, &priv->vlan_lock) {
+ dsa_switch_for_each_available_port(dp, ds) {
+ priv->port_pvid[dp->index] = 1;
+ soce_vlan_config_port(priv, dp->index, false);
+ }
+ soce_vlan_set_enabled(local, true);
+ }
+
+ return 0;
+}
+
+static void soce_vlan_teardown(struct soce_priv *priv)
+{
+ struct soce_dsa_local *local = &priv->local;
+ int ret;
+
+ if (!priv->features.port_vlan)
+ return;
+
+ scoped_guard(mutex, &priv->vlan_lock) {
+ ret = soce_vlan_reset(local);
+ if (ret)
+ dev_warn(priv->ds.dev,
+ "failed to reset VLAN configuration during teardown: %d\n",
+ ret);
+ soce_vlan_set_enabled(local, false);
+ }
+}
+
static void soce_port_select(struct soce_dsa_local *local, int port)
{
writel(FIELD_PREP(SOCE_PORTS_SELECTOR_PORT_MASK, port),
@@ -204,6 +414,11 @@ static void soce_port_disable(struct dsa_switch *ds, int port)
static int soce_setup(struct dsa_switch *ds)
{
struct soce_priv *priv = ds->priv;
+ int ret;
+
+ ret = soce_vlan_setup(ds);
+ if (ret)
+ return ret;
soce_sw_enable_tagging(&priv->local);
@@ -213,8 +428,12 @@ static int soce_setup(struct dsa_switch *ds)
static void soce_teardown(struct dsa_switch *ds)
{
struct soce_priv *priv = ds->priv;
+ struct soce_dsa_local *local;
+
+ local = &priv->local;
- soce_sw_disable_tagging(&priv->local);
+ soce_sw_disable_tagging(local);
+ soce_vlan_teardown(priv);
}
static enum dsa_tag_protocol soce_get_tag_protocol(struct dsa_switch *ds,
@@ -224,6 +443,130 @@ static enum dsa_tag_protocol soce_get_tag_protocol(struct dsa_switch *ds,
return DSA_TAG_PROTO_SDSA;
}
+static int soce_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct soce_priv *priv = ds->priv;
+ u32 port_mask = BIT(port);
+ u32 *untagged_ports;
+ u32 old_untagged;
+ u32 old_members;
+ bool untagged;
+ u32 *members;
+ int ret;
+
+ untagged_ports = priv->vlan_untagged;
+ members = priv->vlan_members;
+
+ if (!priv->features.port_vlan) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Port VLAN support is not implemented in the switch core");
+ return -EOPNOTSUPP;
+ }
+
+ if (!vlan->vid)
+ return 0;
+
+ untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+
+ scoped_guard(mutex, &priv->vlan_lock) {
+ old_members = members[vlan->vid];
+ old_untagged = untagged_ports[vlan->vid];
+
+ members[vlan->vid] |= port_mask;
+ if (untagged)
+ untagged_ports[vlan->vid] |= port_mask;
+ else
+ untagged_ports[vlan->vid] &= ~port_mask;
+
+ ret = soce_vlan_write_entry(priv, vlan->vid);
+ if (ret) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "failed to update VLAN hardware tables");
+ dev_err(ds->dev,
+ "failed to add VLAN %u on port %d: %d\n",
+ vlan->vid, port, ret);
+ members[vlan->vid] = old_members;
+ untagged_ports[vlan->vid] = old_untagged;
+ return ret;
+ }
+
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
+ priv->port_pvid[port] = vlan->vid;
+ soce_vlan_config_port(priv, port,
+ dsa_port_is_vlan_filtering(dp));
+ }
+ }
+
+ return ret;
+}
+
+static int soce_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct soce_priv *priv = ds->priv;
+ u32 port_mask = BIT(port);
+ u32 *untagged_ports;
+ u32 old_untagged;
+ u32 old_members;
+ u32 *members;
+ int ret;
+
+ untagged_ports = priv->vlan_untagged;
+ members = priv->vlan_members;
+
+ if (!priv->features.port_vlan)
+ return -EOPNOTSUPP;
+
+ if (!vlan->vid)
+ return 0;
+
+ scoped_guard(mutex, &priv->vlan_lock) {
+ old_members = members[vlan->vid];
+ old_untagged = untagged_ports[vlan->vid];
+ members[vlan->vid] &= ~port_mask;
+ untagged_ports[vlan->vid] &= ~port_mask;
+ ret = soce_vlan_write_entry(priv, vlan->vid);
+ if (ret) {
+ dev_err(ds->dev,
+ "failed to delete VLAN %u from port %d: %d\n",
+ vlan->vid, port, ret);
+ members[vlan->vid] = old_members;
+ untagged_ports[vlan->vid] = old_untagged;
+ return ret;
+ }
+
+ if (priv->port_pvid[port] == vlan->vid) {
+ priv->port_pvid[port] = 0;
+ soce_vlan_config_port(priv, port,
+ dsa_port_is_vlan_filtering(dp));
+ }
+ }
+
+ return ret;
+}
+
+static int soce_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
+{
+ struct soce_priv *priv = ds->priv;
+
+ if (!priv->features.port_vlan) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Port VLAN support is not implemented in the switch core");
+ return -EOPNOTSUPP;
+ }
+
+ scoped_guard(mutex, &priv->vlan_lock)
+ soce_vlan_config_port(priv, port, vlan_filtering);
+
+ return 0;
+}
+
static const struct dsa_switch_ops soce_switch_ops = {
.get_tag_protocol = soce_get_tag_protocol,
.setup = soce_setup,
@@ -231,6 +574,9 @@ static const struct dsa_switch_ops soce_switch_ops = {
.phylink_get_caps = soce_phylink_get_caps,
.port_enable = soce_port_enable,
.port_disable = soce_port_disable,
+ .port_vlan_filtering = soce_port_vlan_filtering,
+ .port_vlan_add = soce_port_vlan_add,
+ .port_vlan_del = soce_port_vlan_del,
};
static int soce_sw_probe(struct platform_device *pdev)
@@ -241,7 +587,6 @@ static int soce_sw_probe(struct platform_device *pdev)
struct dsa_switch *ds;
u8 hw_subversion;
u16 hw_revision;
- u32 hw_numports;
u8 hw_version;
int ret;
@@ -261,7 +606,7 @@ static int soce_sw_probe(struct platform_device *pdev)
soce_sw_read_core_version(local, &hw_version, &hw_subversion,
&hw_revision);
- ret = soce_sw_detect_features(local, &hw_numports);
+ ret = soce_sw_detect_features(local, &priv->features);
if (ret) {
if (ret == -ENODEV)
dev_err(dev, "switch core does not implement DSA\n");
@@ -278,8 +623,35 @@ static int soce_sw_probe(struct platform_device *pdev)
return ret;
}
+ if (priv->features.port_vlan) {
+ ret = devm_mutex_init(dev, &priv->vlan_lock);
+ if (ret)
+ return ret;
+
+ priv->vlan_members =
+ devm_kcalloc(dev, VLAN_N_VID,
+ sizeof(*priv->vlan_members), GFP_KERNEL);
+ if (!priv->vlan_members)
+ return -ENOMEM;
+
+ priv->vlan_untagged =
+ devm_kcalloc(dev, VLAN_N_VID,
+ sizeof(*priv->vlan_untagged), GFP_KERNEL);
+ if (!priv->vlan_untagged)
+ return -ENOMEM;
+ } else {
+ dev_warn(dev,
+ "VLAN-tagged frames received on the CPU port are unsupported\n");
+ }
+
ds->ops = &soce_switch_ops;
- ds->num_ports = hw_numports;
+ ds->num_ports = priv->features.num_ports;
+
+ /* Force VLAN uppers always through the callbacks, so cores without
+ * Port VLAN feature can reject them instead of silently dropping
+ * VLAN frames.
+ */
+ ds->needs_standalone_vlan_filtering = true;
ret = devm_of_platform_populate(dev);
if (ret)
return dev_err_probe(dev, ret,
@@ -294,7 +666,9 @@ static int soce_sw_probe(struct platform_device *pdev)
dev_info(dev,
"probed soce switch core version %02x.%02x.%04x with %u ports\n",
- hw_version, hw_subversion, hw_revision, hw_numports);
+ hw_version, hw_subversion, hw_revision,
+ priv->features.num_ports);
+
return 0;
}
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net-next v3 7/8] net: dsa: soce: Add VLAN offload support
2026-09-23 10:39 ` [PATCH net-next v3 7/8] net: dsa: soce: Add VLAN offload support Vasilij Strassheim
@ 2026-09-25 23:32 ` Andrew Lunn
0 siblings, 0 replies; 17+ messages in thread
From: Andrew Lunn @ 2026-09-25 23:32 UTC (permalink / raw)
To: Vasilij Strassheim
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Russell King, Andrew Lunn, Heiner Kallweit,
devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger
> The port and VID selectors are shared by all VLAN operations, so
> serialize selector and data register sequences with a dedicated mutex.
> Track member and untagged masks in software and restore the shadow state
> if programming fails.
Looking at other features of the switch, how many different mutex are
going to be needed? Is it better to have a single mutex which protects
everything?
Andrew
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v3 8/8] net: dsa: soce: Disable unsupported hardware STP
2026-09-23 10:39 [PATCH net-next v3 0/8] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
` (6 preceding siblings ...)
2026-09-23 10:39 ` [PATCH net-next v3 7/8] net: dsa: soce: Add VLAN offload support Vasilij Strassheim
@ 2026-09-23 10:39 ` Vasilij Strassheim
2026-09-25 23:24 ` Andrew Lunn
7 siblings, 1 reply; 17+ messages in thread
From: Vasilij Strassheim @ 2026-09-23 10:39 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Russell King, Andrew Lunn,
Heiner Kallweit
Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger, Vasilij Strassheim
The driver does not implement hardware STP offloading yet. Switch cores
synthesized with the STP feature enabled may therefore retain their
reset configuration and block frame forwarding.
Detect the synthesized STP feature and disable it during switch setup,
leaving STP state handling to the Linux bridge.
Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
drivers/net/dsa/soce/soce_dsa.h | 1 +
drivers/net/dsa/soce/soce_dsa_core.c | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+)
diff --git a/drivers/net/dsa/soce/soce_dsa.h b/drivers/net/dsa/soce/soce_dsa.h
index 41533a89d680..bb9e65567ec6 100644
--- a/drivers/net/dsa/soce/soce_dsa.h
+++ b/drivers/net/dsa/soce/soce_dsa.h
@@ -22,6 +22,7 @@ struct soce_dsa_local {
struct soce_features {
u32 num_ports;
bool port_vlan;
+ bool stp;
};
struct soce_priv {
diff --git a/drivers/net/dsa/soce/soce_dsa_core.c b/drivers/net/dsa/soce/soce_dsa_core.c
index f93ce9da8400..3b9fde7a547e 100644
--- a/drivers/net/dsa/soce/soce_dsa_core.c
+++ b/drivers/net/dsa/soce/soce_dsa_core.c
@@ -36,6 +36,7 @@
#define SOCE_IMPL_FEATURES0_OFFSET 0x000c
#define SOCE_IMPL_FEATURES0_NUM_PORTS_MASK GENMASK(31, 27)
#define SOCE_IMPL_FEATURES0_PORT_VLAN BIT(9)
+#define SOCE_IMPL_FEATURES0_STP BIT(20)
#define SOCE_IMPL_FEATURES0_DSA BIT(23)
#define SOCE_DSA_REGS_BASE 0x1200
@@ -51,6 +52,10 @@
#define SOCE_PORTS_CTRL_INGR_EN BIT(0)
#define SOCE_PORTS_CTRL_EGR_EN BIT(1)
+#define SOCE_STP_REGS_BASE 0x0f00
+#define SOCE_STP_CTRL_OFFSET SOCE_STP_REGS_BASE
+#define SOCE_STP_CTRL_ENABLE BIT(0)
+
#define SOCE_VLAN_REGS_BASE 0x0d00
#define SOCE_VLAN_CTRL_OFFSET SOCE_VLAN_REGS_BASE
#define SOCE_VLAN_CTRL_ENABLE BIT(0)
@@ -160,6 +165,7 @@ static int soce_sw_detect_features(struct soce_dsa_local *local,
return -ENODEV;
features->port_vlan = regval & SOCE_IMPL_FEATURES0_PORT_VLAN;
+ features->stp = regval & SOCE_IMPL_FEATURES0_STP;
implemented_numports =
FIELD_GET(SOCE_IMPL_FEATURES0_NUM_PORTS_MASK, regval);
@@ -200,6 +206,16 @@ static void soce_sw_disable_tagging(struct soce_dsa_local *local)
writel(regval, base + SOCE_CUSTOM_RULES_TAGGING_OFFSET);
}
+static void soce_stp_disable(struct soce_dsa_local *local)
+{
+ void __iomem *base = local->base_addr;
+ u32 regval;
+
+ regval = readl(base + SOCE_STP_CTRL_OFFSET);
+ regval &= ~SOCE_STP_CTRL_ENABLE;
+ writel(regval, base + SOCE_STP_CTRL_OFFSET);
+}
+
static void soce_vlan_set_enabled(struct soce_dsa_local *local, bool enabled)
{
void __iomem *base = local->base_addr;
@@ -420,6 +436,10 @@ static int soce_setup(struct dsa_switch *ds)
if (ret)
return ret;
+ /* Unconfigured hardware STP blocks frame forwarding. */
+ if (priv->features.stp)
+ soce_stp_disable(&priv->local);
+
soce_sw_enable_tagging(&priv->local);
return 0;
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net-next v3 8/8] net: dsa: soce: Disable unsupported hardware STP
2026-09-23 10:39 ` [PATCH net-next v3 8/8] net: dsa: soce: Disable unsupported hardware STP Vasilij Strassheim
@ 2026-09-25 23:24 ` Andrew Lunn
0 siblings, 0 replies; 17+ messages in thread
From: Andrew Lunn @ 2026-09-25 23:24 UTC (permalink / raw)
To: Vasilij Strassheim
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Russell King, Andrew Lunn, Heiner Kallweit,
devicetree, linux-kernel, netdev, Martin Kaistra,
Benedikt Spranger
On Wed, Sep 23, 2026 at 12:39:35PM +0200, Vasilij Strassheim wrote:
> The driver does not implement hardware STP offloading yet.
What exactly do you mean by STP offload? Can it do the actual
protocol? No DSA switch can do that, we always use the software
implementation. I'm not even sure it is possible to offload the actual
protocol.
Andrew
^ permalink raw reply [flat|nested] 17+ messages in thread