Netdev List
 help / color / mirror / Atom feed
* [PATCH 0/4] net: dsa: Add SoC-e DSA driver
@ 2026-07-29 16:36 Vasilij Strassheim
  2026-07-29 16:36 ` [PATCH 1/4] dt-bindings: vendor-prefixes: Add soce Vasilij Strassheim
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: Vasilij Strassheim @ 2026-07-29 16:36 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
  Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
	Vasilij Strassheim

Add initial support for the DSA driver for SoC-e FPGA-based (IP core)
Ethernet switches.

This first functional driver baseline is kept focused on Managed
Redundant Switch (MRS) which is also runtime-tested (25.01 IP using
24.01 layout) on ZynqMP hardware. It includes basic support for
networking (STP, FDB and Phylink), but not the IEEE 802.1Q VLAN tagging.
This will likely be added in the future.

This work is based on non-publicly available SoC-e DSA driver code, with
permission from the company to contribute it under different authorship.
There are different variants of the SoC-e switch IP, which can be
additionally configured at runtime with proprietary userspace software.
All variants share the same DSA features with a few differences in
register layout. Additional SoC-e switch variants might be introduced
later in separate patch series. 

Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
Vasilij Strassheim (4):
      dt-bindings: vendor-prefixes: Add soce
      dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings
      net: dsa: Add support for SoC-e SDSA tags
      net: dsa: soce: Add initial driver support for MRS switches

 .../bindings/net/dsa/soce,switch-dsa.yaml          | 195 +++++++++++
 .../bindings/net/dsa/soce,switch-ip.yaml           |  54 +++
 .../devicetree/bindings/vendor-prefixes.yaml       |   2 +
 drivers/net/dsa/Kconfig                            |   2 +
 drivers/net/dsa/Makefile                           |   1 +
 drivers/net/dsa/soce/Kconfig                       |  12 +
 drivers/net/dsa/soce/Makefile                      |   6 +
 drivers/net/dsa/soce/soce_common.c                 | 159 +++++++++
 drivers/net/dsa/soce/soce_common.h                 |  36 ++
 drivers/net/dsa/soce/soce_dsa.h                    |  41 +++
 drivers/net/dsa/soce/soce_dsa_core.c               | 354 +++++++++++++++++++
 drivers/net/dsa/soce/soce_mdio.c                   | 381 +++++++++++++++++++++
 drivers/net/dsa/soce/soce_mdio.h                   |  40 +++
 drivers/net/dsa/soce/soce_mrs.h                    |  21 ++
 include/net/dsa.h                                  |   2 +
 net/dsa/Kconfig                                    |   6 +
 net/dsa/Makefile                                   |   1 +
 net/dsa/tag_sdsa.c                                 | 102 ++++++
 18 files changed, 1415 insertions(+)
---
base-commit: 9fb3b84c9577707db14e633ea3c57048380bf822
change-id: 20260729-devel-vstrassheim-soce-dsa-ml-20d6a5adb838

Best regards,
--  
Vasilij Strassheim <v.strassheim@linutronix.de>


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/4] dt-bindings: vendor-prefixes: Add soce
  2026-07-29 16:36 [PATCH 0/4] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
@ 2026-07-29 16:36 ` Vasilij Strassheim
  2026-07-29 16:36 ` [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings Vasilij Strassheim
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 11+ messages in thread
From: Vasilij Strassheim @ 2026-07-29 16:36 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
  Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
	Vasilij Strassheim

Add the soce vendor prefix for System-On-Chip Engineering, S.L.

This prefix is used by the new SoC-e Ethernet switch DT bindings.

Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
 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 396044f368e7..cf7eefff4230 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1553,6 +1553,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 related	[flat|nested] 11+ messages in thread

* [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings
  2026-07-29 16:36 [PATCH 0/4] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
  2026-07-29 16:36 ` [PATCH 1/4] dt-bindings: vendor-prefixes: Add soce Vasilij Strassheim
@ 2026-07-29 16:36 ` Vasilij Strassheim
  2026-07-29 17:11   ` Andrew Lunn
  2026-07-29 18:07   ` Rob Herring (Arm)
  2026-07-29 16:36 ` [PATCH 3/4] net: dsa: Add support for SoC-e SDSA tags Vasilij Strassheim
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 11+ messages in thread
From: Vasilij Strassheim @ 2026-07-29 16:36 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
  Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
	Vasilij Strassheim

Add DT schema documentation for SoC-e FPGA switch devices.
The binding is split into:
 - a switch IP node describing the MMIO register block
 - a DSA MDIO node describing the DSA-facing switch instance
Both nodes are linked through the soce,switch-ip phandle.

Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
 .../bindings/net/dsa/soce,switch-dsa.yaml          | 195 +++++++++++++++++++++
 .../bindings/net/dsa/soce,switch-ip.yaml           |  54 ++++++
 2 files changed, 249 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/dsa/soce,switch-dsa.yaml b/Documentation/devicetree/bindings/net/dsa/soce,switch-dsa.yaml
new file mode 100644
index 000000000000..a04e2838d14e
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/soce,switch-dsa.yaml
@@ -0,0 +1,195 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/soce,switch-dsa.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SoC-e DSA switch MDIO device
+
+maintainers:
+  - SoC-e <info@soc-e.com>
+
+description: |
+  This node is the MDIO-bus device entry for the SoC-e DSA switch driver.
+  It is a lightweight configuration node that lives on the MDIO bus of the
+  SoC Ethernet controller acting as the DSA conduit (CPU port).
+
+  The switch hardware itself (MMIO register interface) is a separate IP core
+  described by a companion node with one of the SoC-e compatible strings,
+  for example:
+    - soce,mrs-<version>
+  See the consolidated IP-core binding:
+    - Documentation/devicetree/bindings/net/dsa/soce,switch-ip.yaml
+  The two nodes are linked via the "soce,switch-ip" phandle property.
+
+  Port sub-nodes follow the DSA port binding documented in dsa-port.yaml.
+  User ports should reference PHY nodes through "phy-handle". These PHY nodes
+  live under the optional "mdio" child node of the switch and are exposed on
+  the switch's synthetic DSA user MDIO bus.
+
+  The PHY-specific properties "soce,mdio-output" and "soce,phy-addr" describe
+  the MDIO bus index and hardware PHY address used by the on-chip MDIO master
+  of the switch IP core.
+
+properties:
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  compatible:
+    const: soce,switch-dsa
+
+  reg:
+    description: MDIO address of this device on the parent MDIO bus.
+    maxItems: 1
+
+  soce,switch-ip:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: |
+      Phandle to the SoC-e switch IP core node that provides the MMIO register
+      window for this switch instance. The referenced node must carry a
+      compatible string documented in
+      Documentation/devicetree/bindings/net/dsa/soce,switch-ip.yaml.
+
+  dsa,member:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    minItems: 2
+    maxItems: 2
+    description:
+      DSA cluster membership; see Documentation/devicetree/bindings/net/dsa/dsa.yaml.
+
+  mdio:
+    $ref: /schemas/net/mdio.yaml#
+    description: |
+      Optional child MDIO bus exposing PHY devices attached to switch user
+      ports. PHY nodes referenced by port "phy-handle" properties should be
+      placed here. The PHY address seen by Linux on this bus is the switch port
+      index, not necessarily the hardware PHY address selected internally by
+      the switch IP.
+    unevaluatedProperties: false
+
+    patternProperties:
+      "^ethernet-phy@[0-9a-f]+$":
+        type: object
+        $ref: /schemas/net/ethernet-phy.yaml#
+        unevaluatedProperties: false
+        description:
+          PHY attached to a switch user port through the synthetic switch MDIO
+          bus.
+        properties:
+          soce,mdio-output:
+            $ref: /schemas/types.yaml#/definitions/uint32
+            description: |
+              Zero-based index of the MDIO bus output of the switch IP core to
+              which this PHY is connected.
+
+          soce,phy-addr:
+            $ref: /schemas/types.yaml#/definitions/uint32
+            description: |
+              Hardware PHY address on the MDIO bus selected by
+              "soce,mdio-output".
+
+        required:
+          - soce,mdio-output
+          - soce,phy-addr
+
+  ports:
+    type: object
+    description: Container node for the switch port sub-nodes.
+    properties:
+      '#address-cells':
+        const: 1
+      '#size-cells':
+        const: 0
+
+    patternProperties:
+      "^port@[0-9a-f]+$":
+        $ref: dsa-port.yaml#
+        unevaluatedProperties: false
+        description: |
+          Switch port. In addition to the standard DSA port properties
+          (label, phy-mode, ethernet, fixed-link) the following
+          vendor-specific properties are used for data ports connected to
+          external PHYs.
+
+    required:
+      - '#address-cells'
+      - '#size-cells'
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - soce,switch-ip
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    /* Excerpt from a Xilinx ZynqMP design (ARM64) */
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        soce_switch: switch@10 {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            compatible = "soce,switch-dsa";
+            reg = <0x10>;
+            soce,switch-ip = <&soce_switch_ip>;
+            dsa,member = <0 0>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0x0>;
+                    label = "PORT_0";
+                    phy-handle = <&switchphy0>;
+                    phy-mode = "rgmii-id";
+                };
+
+                port@1 {
+                    reg = <0x1>;
+                    label = "PORT_1";
+                    phy-handle = <&switchphy1>;
+                    phy-mode = "rgmii-id";
+                };
+
+                port@2 {
+                    reg = <0x2>;
+                    label = "INTERNAL_PORT";
+                    ethernet = <&gem3>;
+                    phy-mode = "gmii";
+                    fixed-link {
+                        speed = <1000>;
+                        full-duplex;
+                    };
+                };
+            };
+
+            mdio {
+              #address-cells = <1>;
+              #size-cells = <0>;
+
+                switchphy0: ethernet-phy@0 {
+                    compatible = "ethernet-phy-ieee802.3-c22";
+                    reg = <0x0>;
+                    soce,mdio-output = <0x00000000>;
+                    soce,phy-addr = <1>;
+                };
+
+                switchphy1: ethernet-phy@1 {
+                    compatible = "ethernet-phy-ieee802.3-c22";
+                    reg = <0x1>;
+                    soce,mdio-output = <0x00000001>;
+                    soce,phy-addr = <1>;
+                };
+            };
+        };
+    };
diff --git a/Documentation/devicetree/bindings/net/dsa/soce,switch-ip.yaml b/Documentation/devicetree/bindings/net/dsa/soce,switch-ip.yaml
new file mode 100644
index 000000000000..ba9bb396235f
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/soce,switch-ip.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/soce,switch-ip.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SoC-e Ethernet Switch IP core
+
+maintainers:
+  - SoC-e <info@soc-e.com>
+
+description: |
+  SoC-e Ethernet switch IP cores are configurable FPGA switch fabrics with
+  Layer-2 bridging features (including STP/MSTP state control and FDB
+  fast-age support). Some variants also provide TSN functionality.
+
+  This binding describes only the MMIO switch IP core node. The Linux DSA
+  driver is bound via a companion MDIO node described in:
+    Documentation/devicetree/bindings/net/dsa/soce,switch-dsa.yaml.
+
+properties:
+  compatible:
+    enum:
+      - soce,mrs-21-01
+      - soce,mrs-23-02
+      - soce,mrs-24-01
+
+  reg:
+    description: |
+      MMIO base address and size of the switch register window.
+      On 64-bit platforms (ARM64), this is typically encoded as two-cell
+      address and two-cell size values.
+    maxItems: 1
+
+  soce,num-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Total number of switch ports (external/user ports plus the CPU uplink
+      port). Must be greater than zero and not exceed the selected IP variant.
+
+required:
+  - compatible
+  - reg
+  - soce,num-ports
+
+additionalProperties: false
+
+examples:
+  - |
+    switch_ip: switch@80020000 {
+        compatible = "soce,mrs-24-01";
+        reg = <0x0 0x80020000 0x0 0x10000>;
+        soce,num-ports = <3>;
+    };

-- 
2.39.5


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/4] net: dsa: Add support for SoC-e SDSA tags
  2026-07-29 16:36 [PATCH 0/4] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
  2026-07-29 16:36 ` [PATCH 1/4] dt-bindings: vendor-prefixes: Add soce Vasilij Strassheim
  2026-07-29 16:36 ` [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings Vasilij Strassheim
@ 2026-07-29 16:36 ` Vasilij Strassheim
  2026-07-29 17:22   ` Andrew Lunn
  2026-07-29 16:36 ` [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches Vasilij Strassheim
  2026-07-29 16:56 ` [PATCH 0/4] net: dsa: Add SoC-e DSA driver Andrew Lunn
  4 siblings, 1 reply; 11+ messages in thread
From: Vasilij Strassheim @ 2026-07-29 16:36 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
  Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
	Vasilij Strassheim

Add a DSA tagger for the SoC-e SDSA header format used between
the conduit and SoC-e switch ports.

The implementation adds TX tag insertion and RX tag parsing and
registers a new DSA tag protocol.

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 | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 111 insertions(+)

diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..744bbb4d54ea 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -59,6 +59,7 @@ struct tc_action;
 #define DSA_TAG_PROTO_MXL_GSW1XX_VALUE		31
 #define DSA_TAG_PROTO_MXL862_VALUE		32
 #define DSA_TAG_PROTO_NETC_VALUE		33
+#define DSA_TAG_PROTO_SDSA_VALUE		34
 
 enum dsa_tag_protocol {
 	DSA_TAG_PROTO_NONE		= DSA_TAG_PROTO_NONE_VALUE,
@@ -95,6 +96,7 @@ enum dsa_tag_protocol {
 	DSA_TAG_PROTO_MXL_GSW1XX	= DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
 	DSA_TAG_PROTO_MXL862		= DSA_TAG_PROTO_MXL862_VALUE,
 	DSA_TAG_PROTO_NETC		= DSA_TAG_PROTO_NETC_VALUE,
+	DSA_TAG_PROTO_SDSA		= DSA_TAG_PROTO_SDSA_VALUE,
 };
 
 struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index d5e725b90d78..1e1b0f2fb726 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -98,6 +98,12 @@ config NET_DSA_TAG_EDSA
 	  Say Y or M if you want to enable support for tagging frames for the
 	  Marvell switches which use EtherType DSA headers.
 
+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_MTK
 	tristate "Tag driver for Mediatek switches"
 	help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index b8c2667cd14a..dc154f10e0a0 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -23,6 +23,7 @@ dsa_core-y += \
 obj-$(CONFIG_NET_DSA_TAG_AR9331) += tag_ar9331.o
 obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o
 obj-$(CONFIG_NET_DSA_TAG_DSA_COMMON) += tag_dsa.o
+obj-$(CONFIG_NET_DSA_TAG_SDSA) += tag_sdsa.o
 obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
 obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
 obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
diff --git a/net/dsa/tag_sdsa.c b/net/dsa/tag_sdsa.c
new file mode 100644
index 000000000000..b32dca1e367c
--- /dev/null
+++ b/net/dsa/tag_sdsa.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+
+#include <linux/bitops.h>
+#include <linux/etherdevice.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_MASK		GENMASK(7, 3)
+
+static struct sk_buff *sdsa_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct dsa_port *dp = dsa_user_to_port(dev);
+	u8 *sdsa_header;
+
+	if (skb_cow_head(skb, SDSA_HLEN) < 0)
+		return NULL;
+
+	skb_push(skb, SDSA_HLEN);
+	dsa_alloc_etype_header(skb, SDSA_HLEN);
+
+	/* Construct the FROM_CPU DSA tag. */
+	sdsa_header = dsa_etype_header_pos_tx(skb);
+	sdsa_header[0] = (ETH_P_SDSA >> 8) & 0xff;
+	sdsa_header[1] = ETH_P_SDSA & 0xff;
+	sdsa_header[2] = 0x00; /* reserved */
+	sdsa_header[3] = 0x00; /* reserved */
+	sdsa_header[4] = FIELD_PREP(SDSA_TAG_FRAME_TYPE_MASK, 1) |
+			 FIELD_PREP(SDSA_TAG_PORT_HI_MASK, dp->index >> 5);
+	sdsa_header[5] = FIELD_PREP(SDSA_TAG_PORT_MASK, dp->index);
+	sdsa_header[6] = 0x00; /* VLAN not supported */
+	sdsa_header[7] = 0x00; /* VLAN not supported */
+
+	return skb;
+}
+
+static struct sk_buff *sdsa_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+	u8 *sdsa_header;
+	int source_port;
+	u8 frame_type;
+
+	if (unlikely(!pskb_may_pull(skb, SDSA_HLEN)))
+		return NULL;
+
+	sdsa_header = dsa_etype_header_pos_rx(skb);
+
+	/* Check that the frame type is TO_CPU. */
+	frame_type = FIELD_GET(SDSA_TAG_FRAME_TYPE_MASK, sdsa_header[4]);
+	if (frame_type != 0)
+		return NULL;
+
+	/* VLAN-tagged traffic is intentionally unsupported yet. */
+	if (sdsa_header[4] & SDSA_TAG_VLAN_BIT)
+		return NULL;
+
+	/* Determine the source port from the two port fields. */
+	source_port = ((sdsa_header[4] & 0x1f) << 5) |
+		      ((sdsa_header[5] & 0xf8) >> 3);
+
+	skb->dev = dsa_conduit_find_user(dev, 0, source_port);
+	if (!skb->dev)
+		return NULL;
+
+	skb_pull_rcsum(skb, SDSA_HLEN);
+	dsa_strip_etype_header(skb, SDSA_HLEN);
+
+	dsa_default_offload_fwd_mark(skb);
+	return skb;
+}
+
+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 related	[flat|nested] 11+ messages in thread

* [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches
  2026-07-29 16:36 [PATCH 0/4] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
                   ` (2 preceding siblings ...)
  2026-07-29 16:36 ` [PATCH 3/4] net: dsa: Add support for SoC-e SDSA tags Vasilij Strassheim
@ 2026-07-29 16:36 ` Vasilij Strassheim
  2026-07-29 17:31   ` Andrew Lunn
  2026-07-29 20:05   ` Andrew Lunn
  2026-07-29 16:56 ` [PATCH 0/4] net: dsa: Add SoC-e DSA driver Andrew Lunn
  4 siblings, 2 replies; 11+ messages in thread
From: Vasilij Strassheim @ 2026-07-29 16:36 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
  Cc: devicetree, linux-kernel, netdev, Martin Kaistra,
	Vasilij Strassheim

Add initial support for SoC-e switches that are based on FPGA IP cores.
This driver provides MDIO access helpers for supported IP register
layouts and common STP/FDB handling.

Managed Redundant Switch (MRS) variant is introduced first with variant
descriptors and register layout mapping. The implementation has been
designed so that additional variants can be added in the future with
minimal effort.

Tested on SoC-e MRS 25.01 hardware using the 24.01 register layout.

Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
---
 drivers/net/dsa/Kconfig              |   2 +
 drivers/net/dsa/Makefile             |   1 +
 drivers/net/dsa/soce/Kconfig         |  12 ++
 drivers/net/dsa/soce/Makefile        |   6 +
 drivers/net/dsa/soce/soce_common.c   | 159 +++++++++++++++
 drivers/net/dsa/soce/soce_common.h   |  36 ++++
 drivers/net/dsa/soce/soce_dsa.h      |  41 ++++
 drivers/net/dsa/soce/soce_dsa_core.c | 354 ++++++++++++++++++++++++++++++++
 drivers/net/dsa/soce/soce_mdio.c     | 381 +++++++++++++++++++++++++++++++++++
 drivers/net/dsa/soce/soce_mdio.h     |  40 ++++
 drivers/net/dsa/soce/soce_mrs.h      |  21 ++
 11 files changed, 1053 insertions(+)

diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 4ab567c5bbaf..6bf5fd48599b 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -82,6 +82,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 d2975badffc0..624b453f576a 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..97d4e4e838d9
--- /dev/null
+++ b/drivers/net/dsa/soce/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+config NET_DSA_SOCE
+	tristate "Soce switches"
+	depends on NET_DSA
+	depends on OF
+	depends on HAS_IOMEM
+	select NET_DSA_TAG_SDSA
+	help
+	  This enables support for switches based on IP core from SoC-E.
+	  Frames are exchanged with the CPU port using the SDSA DSA tag protocol.
+	  The driver supports different variants and versions of switches which
+	  are configurable at runtime.
diff --git a/drivers/net/dsa/soce/Makefile b/drivers/net/dsa/soce/Makefile
new file mode 100644
index 000000000000..a6e74b7d57df
--- /dev/null
+++ b/drivers/net/dsa/soce/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_NET_DSA_SOCE) += soce_dsa.o
+soce_dsa-objs := soce_dsa_core.o
+soce_dsa-objs += soce_mdio.o
+soce_dsa-objs += soce_common.o
diff --git a/drivers/net/dsa/soce/soce_common.c b/drivers/net/dsa/soce/soce_common.c
new file mode 100644
index 000000000000..f7f345a690e4
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_common.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+#include <linux/if_bridge.h>
+#include <linux/bits.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+
+#include "soce_dsa.h"
+#include "soce_mdio.h"
+#include "soce_common.h"
+
+#define SOCE_FAST_AGE_POLL_US		10
+#define SOCE_FAST_AGE_TIMEOUT_US	100000
+
+#define SOCE_STP_PORTS_PER_STATUS_REG	16
+#define SOCE_STP_PORT_STATE_WIDTH	2
+#define SOCE_STP_PORT_STATE_WRITE	BIT(0)
+#define SOCE_STP_MSTID_CIST		0
+
+#define SOCE_STP_PORT_STATE_MASK GENMASK(SOCE_STP_PORT_STATE_WIDTH - 1, 0)
+
+#define SOCE_FDB_CLEAR_UNDER_PROCESS	BIT(7)
+#define SOCE_FDB_CLEAR_FLUSH_MSTID	BIT(4)
+
+static u8 soce_map_stp_state(u8 state)
+{
+	switch (state) {
+	case BR_STATE_DISABLED:
+	case BR_STATE_BLOCKING:
+	case BR_STATE_LISTENING:
+		return 0;
+	case BR_STATE_LEARNING:
+		return 1;
+	case BR_STATE_FORWARDING:
+	default:
+		return 3;
+	}
+}
+
+static void soce_stp_state_set(struct dsa_switch *ds, int port, u8 state,
+			       const struct soce_layout *layout)
+{
+	void __iomem *p_mstp_mstid, *p_mstp_ps_hi, *p_mstp_ps_lo, *p_mstp_pc;
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	u32 ps_hi_n, ps_lo_n;
+	u8 stp_state;
+
+	local = &priv->local;
+	if (!layout || !local->base_addr)
+		return;
+
+	stp_state = soce_map_stp_state(state);
+	p_mstp_pc = local->base_addr + layout->mstp_port_state_control;
+	p_mstp_mstid = local->base_addr + layout->mstp_mstid;
+	p_mstp_ps_lo = local->base_addr + layout->mstp_port_state_lo;
+	p_mstp_ps_hi = local->base_addr + layout->mstp_port_state_hi;
+
+	/* Keep the extra MSTP MSTID write taken from the vendor sequence. */
+	writel(SOCE_STP_MSTID_CIST, p_mstp_mstid);
+
+	ps_lo_n = readl(p_mstp_ps_lo);
+	ps_hi_n = readl(p_mstp_ps_hi);
+
+	if (port >= SOCE_STP_PORTS_PER_STATUS_REG) {
+		port -= SOCE_STP_PORTS_PER_STATUS_REG;
+		ps_hi_n =
+			((ps_hi_n & ~(SOCE_STP_PORT_STATE_MASK
+				      << (port * SOCE_STP_PORT_STATE_WIDTH))) |
+			 (stp_state << (port * SOCE_STP_PORT_STATE_WIDTH)));
+	} else {
+		ps_lo_n =
+			((ps_lo_n & ~(SOCE_STP_PORT_STATE_MASK
+				      << (port * SOCE_STP_PORT_STATE_WIDTH))) |
+			 (stp_state << (port * SOCE_STP_PORT_STATE_WIDTH)));
+	}
+
+	writel(SOCE_STP_MSTID_CIST, p_mstp_mstid);
+	writel(ps_lo_n, p_mstp_ps_lo);
+	writel(ps_hi_n, p_mstp_ps_hi);
+	writel(SOCE_STP_PORT_STATE_WRITE, p_mstp_pc);
+}
+
+static void soce_fast_age(struct dsa_switch *ds, int port,
+			  const struct soce_layout *layout)
+{
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	void __iomem *p_mes_ctrl;
+	u32 val;
+
+	local = &priv->local;
+	if (!layout || !local->base_addr)
+		return;
+
+	p_mes_ctrl = local->base_addr + layout->fdb_ctrl;
+	if (readl_poll_timeout(p_mes_ctrl, val,
+			       !(val & SOCE_FDB_CLEAR_UNDER_PROCESS),
+			       SOCE_FAST_AGE_POLL_US, SOCE_FAST_AGE_TIMEOUT_US))
+		dev_warn(ds->dev, "SoC-e fast_age timeout on port %d\n", port);
+
+	writel(SOCE_FDB_CLEAR_FLUSH_MSTID, p_mes_ctrl);
+}
+
+int soce_setup(struct dsa_switch *ds)
+{
+	return soce_user_mii_bus_register(ds);
+}
+
+void soce_teardown(struct dsa_switch *ds)
+{
+	soce_user_mii_bus_unregister(ds);
+}
+
+int soce_port_enable(struct dsa_switch *ds, int port, struct phy_device *phy)
+{
+	return 0;
+}
+
+void soce_port_disable(struct dsa_switch *ds, int port)
+{
+}
+
+enum dsa_tag_protocol soce_get_tag_protocol(struct dsa_switch *ds, int port,
+					    enum dsa_tag_protocol mprop)
+{
+	return DSA_TAG_PROTO_SDSA;
+}
+
+/* Bridge membership is handled by the switch forwarding model directly. */
+int soce_port_bridge_join(struct dsa_switch *ds, int port,
+			  struct dsa_bridge bridge, bool *tx_fwd_offload,
+			  struct netlink_ext_ack *extack)
+{
+	return 0;
+}
+
+void soce_port_bridge_leave(struct dsa_switch *ds, int port,
+			    struct dsa_bridge bridge)
+{
+}
+
+void soce_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+	struct soce_priv *priv = ds->priv;
+
+	soce_stp_state_set(ds, port, state, priv->local.layout);
+}
+
+void soce_port_fast_age(struct dsa_switch *ds, int port)
+{
+	struct soce_priv *priv = ds->priv;
+
+	soce_fast_age(ds, port, priv->local.layout);
+}
+
diff --git a/drivers/net/dsa/soce/soce_common.h b/drivers/net/dsa/soce/soce_common.h
new file mode 100644
index 000000000000..2423ba167d9f
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_common.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+
+#ifndef __SOCE_COMMON_H
+#define __SOCE_COMMON_H
+
+#include <net/dsa.h>
+
+struct phy_device;
+struct netlink_ext_ack;
+
+struct soce_layout {
+	u32 mstp_port_state_control;
+	u32 mstp_mstid;
+	u32 mstp_port_state_lo;
+	u32 mstp_port_state_hi;
+	u32 fdb_ctrl;
+};
+
+int soce_setup(struct dsa_switch *ds);
+void soce_teardown(struct dsa_switch *ds);
+int soce_port_enable(struct dsa_switch *ds, int port, struct phy_device *phy);
+void soce_port_disable(struct dsa_switch *ds, int port);
+enum dsa_tag_protocol soce_get_tag_protocol(struct dsa_switch *ds, int port,
+					    enum dsa_tag_protocol mprop);
+int soce_port_bridge_join(struct dsa_switch *ds, int port,
+			  struct dsa_bridge bridge, bool *tx_fwd_offload,
+			  struct netlink_ext_ack *extack);
+void soce_port_bridge_leave(struct dsa_switch *ds, int port,
+			    struct dsa_bridge bridge);
+void soce_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
+void soce_port_fast_age(struct dsa_switch *ds, int port);
+
+#endif /* __SOCE_COMMON_H */
diff --git a/drivers/net/dsa/soce/soce_dsa.h b/drivers/net/dsa/soce/soce_dsa.h
new file mode 100644
index 000000000000..edc79d69891d
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_dsa.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+
+#ifndef __SOCE_DSA_H
+#define __SOCE_DSA_H
+
+#define SOCE_MAX_NUM_PORTS 30
+
+struct soce_mdio {
+	int mdio_output;
+	int phy_addr;
+};
+
+struct dsa_switch;
+struct soce_layout;
+
+struct soce_mdio_ops {
+	int (*phy_read)(struct dsa_switch *ds, int port, int regnum);
+	int (*phy_write)(struct dsa_switch *ds, int port, int regnum, u16 val);
+	int (*phy_read_c45)(struct dsa_switch *ds, int port, int devad,
+			    int regnum);
+	int (*phy_write_c45)(struct dsa_switch *ds, int port, int devad,
+			     int regnum, u16 val);
+};
+
+struct soce_dsa_local {
+	void __iomem *base_addr;
+	void __iomem *mdio_master_addr;
+	struct soce_mdio mdio_info[SOCE_MAX_NUM_PORTS];
+	const struct soce_mdio_ops *mdio_ops;
+	const struct soce_layout *layout;
+};
+
+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..b88d00c56d71
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_dsa_core.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <net/dsa.h>
+
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/if_bridge.h>
+#include <linux/mdio.h>
+#include <linux/of_mdio.h>
+#include <linux/etherdevice.h>
+#include <linux/phylink.h>
+
+#include "soce_dsa.h"
+#include "soce_common.h"
+#include "soce_mrs.h"
+#include "soce_mdio.h"
+
+static void soce_phylink_get_caps(struct dsa_switch *ds, int port,
+				  struct phylink_config *config)
+{
+	if (dsa_is_cpu_port(ds, port)) {
+		__set_bit(PHY_INTERFACE_MODE_GMII,
+			  config->supported_interfaces);
+		config->mac_capabilities = MAC_10 | MAC_100 | MAC_1000 |
+					   MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
+		return;
+	}
+
+	phy_interface_set_rgmii(config->supported_interfaces);
+	config->mac_capabilities = MAC_10 | MAC_100 | MAC_1000 | MAC_SYM_PAUSE |
+				   MAC_ASYM_PAUSE;
+}
+
+static int soce_phy_read(struct dsa_switch *ds, int port, int regnum)
+{
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+
+	local = &priv->local;
+	if (!local->mdio_ops || !local->mdio_ops->phy_read)
+		return 0xffff;
+
+	return local->mdio_ops->phy_read(ds, port, regnum);
+}
+
+static int soce_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val)
+{
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+
+	local = &priv->local;
+	if (!local->mdio_ops || !local->mdio_ops->phy_write)
+		return -EOPNOTSUPP;
+
+	return local->mdio_ops->phy_write(ds, port, regnum, val);
+}
+
+static const struct soce_mdio_ops soce_mdio_ops_20_03 = {
+	.phy_read	= soce_mdio_20_03_read,
+	.phy_write	= soce_mdio_20_03_write,
+};
+
+static const struct soce_mdio_ops soce_mdio_ops_c22_c45 = {
+	.phy_read	= soce_mdio_23_02_read,
+	.phy_write	= soce_mdio_23_02_write,
+	.phy_read_c45	= soce_mdio_23_02_read_c45,
+	.phy_write_c45	= soce_mdio_23_02_write_c45,
+};
+
+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,
+	.phy_read		= soce_phy_read,
+	.phy_write		= soce_phy_write,
+	.port_enable		= soce_port_enable,
+	.port_disable		= soce_port_disable,
+	.port_bridge_join	= soce_port_bridge_join,
+	.port_bridge_leave	= soce_port_bridge_leave,
+	.port_stp_state_set	= soce_port_stp_state_set,
+	.port_fast_age		= soce_port_fast_age,
+};
+
+struct soce_variant_desc {
+	const char *compatible;
+	u32 mdio_master_offset;
+	const struct soce_layout *layout;
+	const struct soce_mdio_ops *mdio_ops;
+};
+
+static const struct soce_layout soce_mrs_21_01_layout = {
+	.mstp_port_state_control = SOCE_MRS_21_01_MSTP_PORT_STATE_CONTROL_REG,
+	.mstp_mstid		= SOCE_MRS_21_01_MSTP_MSTID_OFFSET,
+	.mstp_port_state_lo	= SOCE_MRS_21_01_MSTP_PORT_STATE_OFFSET_LO,
+	.mstp_port_state_hi	= SOCE_MRS_21_01_MSTP_PORT_STATE_OFFSET_HI,
+	.fdb_ctrl		= SOCE_MRS_21_01_FDB_CTRL_OFFSET,
+};
+
+static const struct soce_layout soce_mrs_24_01_layout = {
+	.mstp_port_state_control = SOCE_MRS_24_01_MSTP_PORT_STATE_CONTROL_REG,
+	.mstp_mstid		= SOCE_MRS_24_01_MSTP_MSTID_OFFSET,
+	.mstp_port_state_lo	= SOCE_MRS_24_01_MSTP_PORT_STATE_OFFSET_LO,
+	.mstp_port_state_hi	= SOCE_MRS_24_01_MSTP_PORT_STATE_OFFSET_HI,
+	.fdb_ctrl		= SOCE_MRS_24_01_FDB_CTRL_OFFSET,
+};
+
+static const struct soce_variant_desc soce_variants[] = {
+	{
+		.compatible		= "soce,mrs-21-01",
+		.mdio_master_offset	= SOCE_MRS_21_01_MDIO_MASTER_OFFSET,
+		.layout			= &soce_mrs_21_01_layout,
+		.mdio_ops		= &soce_mdio_ops_20_03,
+	},
+	{
+		.compatible		= "soce,mrs-23-02",
+		.mdio_master_offset	= SOCE_MRS_21_01_MDIO_MASTER_OFFSET,
+		.layout			= &soce_mrs_24_01_layout,
+		.mdio_ops		= &soce_mdio_ops_c22_c45,
+	},
+	{
+		.compatible		= "soce,mrs-24-01",
+		.mdio_master_offset	= SOCE_MRS_21_01_MDIO_MASTER_OFFSET,
+		.layout			= &soce_mrs_24_01_layout,
+		.mdio_ops		= &soce_mdio_ops_c22_c45,
+	},
+	{ /* sentinel */ },
+};
+
+static const struct soce_variant_desc *
+soce_match_variant(const char *compatible)
+{
+	int i;
+
+	for (i = 0; soce_variants[i].compatible; i++) {
+		if (!strcmp(compatible, soce_variants[i].compatible))
+			return &soce_variants[i];
+	}
+
+	return NULL;
+}
+
+static int soce_sw_parse_port_mdio_config(struct soce_dsa_local *local,
+					  struct device *dev,
+					  struct device_node *port_node,
+					  u32 port)
+{
+	struct device_node *phy_node;
+	u32 val;
+	int ret;
+
+	if (of_find_property(port_node, "ethernet", NULL) ||
+	    of_find_property(port_node, "link", NULL)) {
+		if (of_find_property(port_node, "phy-handle", NULL)) {
+			dev_err(dev,
+				"phy-handle not allowed on CPU/DSA port %u\n",
+				port);
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	phy_node = of_parse_phandle(port_node, "phy-handle", 0);
+	if (!phy_node) {
+		if (of_phy_is_fixed_link(port_node))
+			return 0;
+
+		dev_err(dev, "user port %u requires phy-handle or fixed-link\n",
+			port);
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32(phy_node, "soce,mdio-output", &val);
+	if (ret) {
+		dev_err(dev,
+			"missing soce,mdio-output in %pOF referenced by port %u\n",
+			phy_node, port);
+		goto out_put_phy;
+	}
+	local->mdio_info[port].mdio_output = val;
+
+	ret = of_property_read_u32(phy_node, "soce,phy-addr", &val);
+	if (ret) {
+		dev_err(dev,
+			"missing soce,phy-addr in %pOF referenced by port %u\n",
+			phy_node, port);
+		goto out_put_phy;
+	}
+	local->mdio_info[port].phy_addr = val;
+
+out_put_phy:
+	of_node_put(phy_node);
+	return ret;
+}
+
+static int soce_sw_parse_port_mdio(struct soce_priv *priv, struct device *dev,
+				   u32 numports)
+{
+	struct soce_dsa_local *local = &priv->local;
+	struct device_node *ports_node;
+	struct device_node *port_node;
+	u32 port;
+	int ret;
+
+	ports_node = of_get_child_by_name(dev->of_node, "ports");
+	if (!ports_node) {
+		dev_err(dev, "missing ports node\n");
+		return -EINVAL;
+	}
+
+	for_each_available_child_of_node(ports_node, port_node) {
+		if (of_property_read_u32(port_node, "reg", &port))
+			continue;
+
+		if (port >= numports || port >= SOCE_MAX_NUM_PORTS) {
+			dev_warn(dev,
+				 "ignoring invalid port index %u in %pOF\n",
+				 port, port_node);
+			continue;
+		}
+
+		ret = soce_sw_parse_port_mdio_config(local, dev, port_node,
+						     port);
+		if (ret) {
+			of_node_put(port_node);
+			of_node_put(ports_node);
+			return ret;
+		}
+	}
+
+	of_node_put(ports_node);
+	return 0;
+}
+
+static int soce_sw_probe(struct mdio_device *mdiodev)
+{
+	const struct soce_variant_desc *variant;
+	struct device *dev = &mdiodev->dev;
+	struct device_node *switch_node;
+	struct soce_dsa_local *local;
+	const char *soce_compatible;
+	struct soce_priv *priv;
+	u32 numports;
+	int ret;
+
+	priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
+	if (!priv->ds)
+		return -ENOMEM;
+
+	priv->ds->dev = dev;
+	priv->ds->priv = priv;
+	local = &priv->local;
+
+	switch_node = of_parse_phandle(dev->of_node, "soce,switch-ip", 0);
+	if (!switch_node) {
+		dev_err(dev, "missing or invalid switch IP reference\n");
+		return -EINVAL;
+	}
+
+	ret = of_property_read_string(switch_node, "compatible",
+				      &soce_compatible);
+	if (ret) {
+		dev_err(dev, "missing compatible property in %pOF\n",
+			switch_node);
+		of_node_put(switch_node);
+		return -EINVAL;
+	}
+
+	variant = soce_match_variant(soce_compatible);
+	if (!variant) {
+		dev_err(dev, "unsupported compatible '%s' in %pOF\n",
+			soce_compatible, switch_node);
+		of_node_put(switch_node);
+		return -ENODEV;
+	}
+
+	ret = of_property_read_u32(switch_node, "soce,num-ports", &numports);
+	if (ret) {
+		dev_err(dev, "missing soce,num-ports in %pOF\n", switch_node);
+		of_node_put(switch_node);
+		return -EINVAL;
+	}
+	if (numports == 0 || numports > SOCE_MAX_NUM_PORTS) {
+		dev_err(dev, "invalid soce,num-ports %u (max %d)\n", numports,
+			SOCE_MAX_NUM_PORTS);
+		of_node_put(switch_node);
+		return -EINVAL;
+	}
+
+	local->base_addr = devm_of_iomap(dev, switch_node, 0, NULL);
+	if (IS_ERR(local->base_addr)) {
+		dev_err(dev, "failed to map switch register space for %pOF\n",
+			switch_node);
+		of_node_put(switch_node);
+		return PTR_ERR(local->base_addr);
+	}
+
+	priv->ds->ops = &soce_switch_ops;
+	local->mdio_ops = variant->mdio_ops;
+	local->mdio_master_addr =
+		local->base_addr + variant->mdio_master_offset;
+	local->layout = variant->layout;
+	of_node_put(switch_node);
+
+	priv->ds->num_ports = numports;
+	ret = soce_sw_parse_port_mdio(priv, dev, numports);
+	if (ret)
+		return ret;
+
+	dev_set_drvdata(&mdiodev->dev, priv);
+
+	dev_info(dev, "soce %s SDSA driver probed.\n", soce_compatible);
+
+	return dsa_register_switch(priv->ds);
+}
+
+static void soce_sw_remove(struct mdio_device *mdiodev)
+{
+	struct soce_priv *priv = dev_get_drvdata(&mdiodev->dev);
+
+	dsa_unregister_switch(priv->ds);
+}
+
+static const struct of_device_id soce_of_match[] = {
+	{ .compatible = "soce,switch-dsa" },
+	{ /* sentinel */ },
+};
+
+static struct mdio_driver soce_mdio_driver = {
+	.mdiodrv.driver = {
+		.name = "soce-switch-dsa",
+		.of_match_table = soce_of_match,
+	},
+	.probe  = soce_sw_probe,
+	.remove = soce_sw_remove,
+};
+
+mdio_module_driver(soce_mdio_driver);
+MODULE_AUTHOR("Vasilij Strassheim <v.strassheim@linutronix.de>");
+MODULE_DESCRIPTION("Driver for SoC-e ethernet switch family");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/soce/soce_mdio.c b/drivers/net/dsa/soce/soce_mdio.c
new file mode 100644
index 000000000000..241fc373de8b
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_mdio.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+
+#include <linux/mdio.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/bits.h>
+#include <net/dsa.h>
+#include "soce_dsa.h"
+#include "soce_mdio.h"
+
+#define SOCE_MDIO_TIMEOUT 1000
+
+/* MDIO 20.03 control word layout:
+ * [31:27] PHY address, [26:22] register address, [21] operation,
+ * [20] start/operation in progress, [7:0] MDIO bus selection.
+ */
+#define SOCE_MDIO_20_03_CTRL_PHY_ADDR_SHIFT	27
+#define SOCE_MDIO_20_03_CTRL_REG_ADDR_SHIFT	22
+#define SOCE_MDIO_20_03_CTRL_OP_SHIFT		21
+#define SOCE_MDIO_20_03_CTRL_START_SHIFT	20
+#define SOCE_MDIO_20_03_CTRL_OP_WRITE		0U
+
+#define SOCE_MDIO_20_03_CTRL_OP_READ	BIT(SOCE_MDIO_20_03_CTRL_OP_SHIFT)
+#define SOCE_MDIO_20_03_CTRL_START	BIT(SOCE_MDIO_20_03_CTRL_START_SHIFT)
+
+static int soce_user_phy_read(struct mii_bus *bus, int addr, int reg)
+{
+	struct dsa_switch *ds = bus->priv;
+
+	if (ds->phys_mii_mask & BIT(addr))
+		return ds->ops->phy_read(ds, addr, reg);
+
+	return 0xffff;
+}
+
+static int soce_user_phy_write(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+	struct dsa_switch *ds = bus->priv;
+
+	if (ds->phys_mii_mask & BIT(addr))
+		return ds->ops->phy_write(ds, addr, reg, val);
+
+	return 0;
+}
+
+static int soce_user_phy_read_c45(struct mii_bus *bus, int addr, int devad,
+				  int regnum)
+{
+	struct dsa_switch *ds = bus->priv;
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+
+	if (!(ds->phys_mii_mask & BIT(addr)))
+		return 0xffff;
+
+	local = &priv->local;
+	if (!local->mdio_ops || !local->mdio_ops->phy_read_c45)
+		return -EOPNOTSUPP;
+
+	return local->mdio_ops->phy_read_c45(ds, addr, devad, regnum);
+}
+
+static int soce_user_phy_write_c45(struct mii_bus *bus, int addr, int devad,
+				   int regnum, u16 val)
+{
+	struct dsa_switch *ds = bus->priv;
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+
+	if (!(ds->phys_mii_mask & BIT(addr)))
+		return 0;
+
+	local = &priv->local;
+	if (!local->mdio_ops || !local->mdio_ops->phy_write_c45)
+		return -EOPNOTSUPP;
+
+	return local->mdio_ops->phy_write_c45(ds, addr, devad, regnum, val);
+}
+
+int soce_user_mii_bus_register(struct dsa_switch *ds)
+{
+	struct device_node *mdio_np;
+	int err;
+
+	if (ds->user_mii_bus)
+		return 0;
+
+	ds->user_mii_bus = mdiobus_alloc();
+	if (!ds->user_mii_bus)
+		return -ENOMEM;
+
+	ds->user_mii_bus->priv = ds;
+	ds->user_mii_bus->name = "soce user mii";
+	ds->user_mii_bus->read = soce_user_phy_read;
+	ds->user_mii_bus->write = soce_user_phy_write;
+	ds->user_mii_bus->read_c45 = soce_user_phy_read_c45;
+	ds->user_mii_bus->write_c45 = soce_user_phy_write_c45;
+	snprintf(ds->user_mii_bus->id, MII_BUS_ID_SIZE, "dsa-%d.%d",
+		 ds->dst->index, ds->index);
+	ds->user_mii_bus->parent = ds->dev;
+	ds->user_mii_bus->phy_mask = ~ds->phys_mii_mask;
+
+	mdio_np = of_get_child_by_name(ds->dev->of_node, "mdio");
+	err = of_mdiobus_register(ds->user_mii_bus, mdio_np);
+	of_node_put(mdio_np);
+
+	if (err) {
+		mdiobus_free(ds->user_mii_bus);
+		ds->user_mii_bus = NULL;
+		return err;
+	}
+
+	return 0;
+}
+
+void soce_user_mii_bus_unregister(struct dsa_switch *ds)
+{
+	if (!ds || !ds->user_mii_bus)
+		return;
+
+	mdiobus_unregister(ds->user_mii_bus);
+	mdiobus_free(ds->user_mii_bus);
+	ds->user_mii_bus = NULL;
+}
+
+static int soce_mdio_20_03_wait_for_idle(struct dsa_switch *ds)
+{
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	void __iomem *ctrl;
+	u32 val;
+
+	local = &priv->local;
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+	return readl_poll_timeout(ctrl, val,
+				  !(val & SOCE_MDIO_20_03_CTRL_START), 10,
+				  SOCE_MDIO_TIMEOUT * 1000);
+}
+
+int soce_mdio_20_03_read(struct dsa_switch *ds, int portnumber, int regnum)
+{
+	struct soce_priv *priv = ds->priv;
+	void __iomem *ctrl, *read_reg;
+	struct soce_dsa_local *local;
+	struct soce_mdio *mdio;
+	u32 regvalue;
+	int ret;
+
+	local = &priv->local;
+	mdio = &local->mdio_info[portnumber];
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+	read_reg = local->mdio_master_addr + SOCE_MDIO_20_03_READ_OFFSET;
+
+	regvalue = (mdio->phy_addr << SOCE_MDIO_20_03_CTRL_PHY_ADDR_SHIFT) |
+		   (regnum << SOCE_MDIO_20_03_CTRL_REG_ADDR_SHIFT) |
+		   SOCE_MDIO_20_03_CTRL_OP_READ |
+		   SOCE_MDIO_20_03_CTRL_START |
+		   mdio->mdio_output;
+	writel(regvalue, ctrl);
+
+	ret = soce_mdio_20_03_wait_for_idle(ds);
+	if (ret)
+		return ret;
+
+	return readl(read_reg) & 0xffff;
+}
+
+int soce_mdio_20_03_write(struct dsa_switch *ds, int portnumber, int regnum,
+			  u16 val)
+{
+	struct soce_priv *priv = ds->priv;
+	void __iomem *ctrl, *write_reg;
+	struct soce_dsa_local *local;
+	struct soce_mdio *mdio;
+	u32 regvalue;
+
+	local = &priv->local;
+	mdio = &local->mdio_info[portnumber];
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+	write_reg = local->mdio_master_addr + SOCE_MDIO_20_03_WRITE_OFFSET;
+
+	/* Write data first */
+	writel(val, write_reg);
+
+	/* Then write control */
+	regvalue = (mdio->phy_addr << SOCE_MDIO_20_03_CTRL_PHY_ADDR_SHIFT) |
+		   (regnum << SOCE_MDIO_20_03_CTRL_REG_ADDR_SHIFT) |
+		   SOCE_MDIO_20_03_CTRL_OP_WRITE |
+		   SOCE_MDIO_20_03_CTRL_START |
+		   mdio->mdio_output;
+	writel(regvalue, ctrl);
+
+	return soce_mdio_20_03_wait_for_idle(ds);
+}
+
+static int soce_mdio_23_02_wait_for_idle(struct dsa_switch *ds)
+{
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	void __iomem *ctrl;
+	u32 val;
+
+	local = &priv->local;
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+
+	return readl_poll_timeout(ctrl, val,
+		!(val & (0x1 << SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET)), 10,
+		SOCE_MDIO_TIMEOUT * 1000);
+}
+
+static int soce_mdio_23_02_read_c22(struct dsa_switch *ds, int portnumber,
+				    int regnum)
+{
+	void __iomem *ctrl, *params, *read_reg;
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	struct soce_mdio *mdio;
+	u32 regvalue;
+	int ret;
+
+	local = &priv->local;
+	mdio = &local->mdio_info[portnumber];
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+	params = local->mdio_master_addr + SOCE_MDIO_23_02_PARAMS_OFFSET;
+	read_reg = local->mdio_master_addr + SOCE_MDIO_23_02_READ_OFFSET;
+
+	regvalue = (regnum << SOCE_MDIO_23_02_CTRL_REGADDRDEVTYPE_OFFSET) +
+		   (mdio->phy_addr << SOCE_MDIO_23_02_CTRL_PHYADDR_OFFSET);
+	writel(regvalue, params);
+
+	regvalue = ((mdio->mdio_output << SOCE_MDIO_23_02_CTRL_BUS_OFFSET) +
+		    (0x3 << SOCE_MDIO_23_02_CTRL_TRANSTYPE_OFFSET) +
+		    (0x0 << SOCE_MDIO_23_02_CTRL_CLAUSE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET));
+	writel(regvalue, ctrl);
+
+	ret = soce_mdio_23_02_wait_for_idle(ds);
+	if (ret)
+		return ret;
+
+	return readl(read_reg) & 0xffff;
+}
+
+int soce_mdio_23_02_read_c45(struct dsa_switch *ds, int portnumber, int devad,
+			     int regnum)
+{
+	void __iomem *ctrl, *params, *read_reg, *write_reg;
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	struct soce_mdio *mdio;
+	u32 regvalue;
+	int ret;
+
+	local = &priv->local;
+	mdio = &local->mdio_info[portnumber];
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+	params = local->mdio_master_addr + SOCE_MDIO_23_02_PARAMS_OFFSET;
+	write_reg = local->mdio_master_addr + SOCE_MDIO_23_02_WRITE_OFFSET;
+	read_reg = local->mdio_master_addr + SOCE_MDIO_23_02_READ_OFFSET;
+
+	regvalue = (devad << SOCE_MDIO_23_02_CTRL_REGADDRDEVTYPE_OFFSET) +
+		   (mdio->phy_addr << SOCE_MDIO_23_02_CTRL_PHYADDR_OFFSET);
+	writel(regvalue, params);
+
+	writel(regnum, write_reg);
+
+	/* address cycle */
+	regvalue = ((mdio->mdio_output << SOCE_MDIO_23_02_CTRL_BUS_OFFSET) +
+		    (0x0 << SOCE_MDIO_23_02_CTRL_TRANSTYPE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_CLAUSE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET));
+	writel(regvalue, ctrl);
+
+	ret = soce_mdio_23_02_wait_for_idle(ds);
+	if (ret)
+		return ret;
+
+	/* read cycle */
+	regvalue = ((mdio->mdio_output << SOCE_MDIO_23_02_CTRL_BUS_OFFSET) +
+		    (0x3 << SOCE_MDIO_23_02_CTRL_TRANSTYPE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_CLAUSE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET));
+	writel(regvalue, ctrl);
+
+	ret = soce_mdio_23_02_wait_for_idle(ds);
+	if (ret)
+		return ret;
+
+	return readl(read_reg) & 0xffff;
+}
+
+int soce_mdio_23_02_read(struct dsa_switch *ds, int portnumber, int regnum)
+{
+	return soce_mdio_23_02_read_c22(ds, portnumber, regnum);
+}
+
+static int soce_mdio_23_02_write_c22(struct dsa_switch *ds, int portnumber,
+				     int regnum, u16 val)
+{
+	void __iomem *ctrl, *params, *write_reg;
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	struct soce_mdio *mdio;
+	u32 regvalue;
+
+	local = &priv->local;
+	mdio = &local->mdio_info[portnumber];
+	params = local->mdio_master_addr + SOCE_MDIO_23_02_PARAMS_OFFSET;
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+	write_reg = local->mdio_master_addr + SOCE_MDIO_23_02_WRITE_OFFSET;
+
+	regvalue = (regnum << SOCE_MDIO_23_02_CTRL_REGADDRDEVTYPE_OFFSET) +
+		   (mdio->phy_addr << SOCE_MDIO_23_02_CTRL_PHYADDR_OFFSET);
+	writel(regvalue, params);
+
+	writel(val, write_reg);
+
+	regvalue = ((mdio->mdio_output << SOCE_MDIO_23_02_CTRL_BUS_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_TRANSTYPE_OFFSET) +
+		    (0x0 << SOCE_MDIO_23_02_CTRL_CLAUSE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET));
+	writel(regvalue, ctrl);
+
+	return soce_mdio_23_02_wait_for_idle(ds);
+}
+
+int soce_mdio_23_02_write_c45(struct dsa_switch *ds, int portnumber, int devad,
+			      int regnum, u16 val)
+{
+	void __iomem *ctrl, *params, *write_reg;
+	struct soce_priv *priv = ds->priv;
+	struct soce_dsa_local *local;
+	struct soce_mdio *mdio;
+	u32 regvalue;
+	int ret;
+
+	local = &priv->local;
+	mdio = &local->mdio_info[portnumber];
+	ctrl = local->mdio_master_addr + SOCE_MDIO_CTRL_OFFSET;
+	params = local->mdio_master_addr + SOCE_MDIO_23_02_PARAMS_OFFSET;
+	write_reg = local->mdio_master_addr + SOCE_MDIO_23_02_WRITE_OFFSET;
+
+	regvalue = (devad << SOCE_MDIO_23_02_CTRL_REGADDRDEVTYPE_OFFSET) +
+		   (mdio->phy_addr << SOCE_MDIO_23_02_CTRL_PHYADDR_OFFSET);
+	writel(regvalue, params);
+
+	writel(regnum, write_reg);
+
+	/* address cycle */
+	regvalue = ((mdio->mdio_output << SOCE_MDIO_23_02_CTRL_BUS_OFFSET) +
+		    (0x0 << SOCE_MDIO_23_02_CTRL_TRANSTYPE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_CLAUSE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET));
+	writel(regvalue, ctrl);
+
+	ret = soce_mdio_23_02_wait_for_idle(ds);
+	if (ret)
+		return ret;
+
+	writel(val, write_reg);
+
+	/* write cycle */
+	regvalue = ((mdio->mdio_output << SOCE_MDIO_23_02_CTRL_BUS_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_TRANSTYPE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_CLAUSE_OFFSET) +
+		    (0x1 << SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET));
+	writel(regvalue, ctrl);
+
+	return soce_mdio_23_02_wait_for_idle(ds);
+}
+
+int soce_mdio_23_02_write(struct dsa_switch *ds, int portnumber, int regnum,
+			  u16 val)
+{
+	return soce_mdio_23_02_write_c22(ds, portnumber, regnum, val);
+}
diff --git a/drivers/net/dsa/soce/soce_mdio.h b/drivers/net/dsa/soce/soce_mdio.h
new file mode 100644
index 000000000000..a71158ed06b3
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_mdio.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+
+#ifndef __SOCE_MDIO_H
+#define __SOCE_MDIO_H
+
+#define SOCE_MDIO_CTRL_OFFSET 0x0000
+
+/* for older versions */
+#define SOCE_MDIO_20_03_WRITE_OFFSET	0x0004
+#define SOCE_MDIO_20_03_READ_OFFSET	0x0008
+
+/* for newer versions */
+#define SOCE_MDIO_23_02_PARAMS_OFFSET		0x0004
+#define SOCE_MDIO_23_02_WRITE_OFFSET		0x0008
+#define SOCE_MDIO_23_02_READ_OFFSET		0x000c
+#define SOCE_MDIO_23_02_CTRL_BUS_OFFSET		16
+#define SOCE_MDIO_23_02_CTRL_TRANSTYPE_OFFSET	3
+#define SOCE_MDIO_23_02_CTRL_CLAUSE_OFFSET	1
+#define SOCE_MDIO_23_02_CTRL_OPSTATUS_OFFSET	0
+
+#define SOCE_MDIO_23_02_CTRL_REGADDRDEVTYPE_OFFSET	8
+#define SOCE_MDIO_23_02_CTRL_PHYADDR_OFFSET		0
+
+int soce_mdio_20_03_read(struct dsa_switch *ds, int portnumber, int regnum);
+int soce_mdio_20_03_write(struct dsa_switch *ds, int portnumber, int regnum,
+			  u16 val);
+
+int soce_mdio_23_02_read(struct dsa_switch *ds, int portnumber, int regnum);
+int soce_mdio_23_02_write(struct dsa_switch *ds, int portnumber, int regnum,
+			  u16 val);
+int soce_mdio_23_02_read_c45(struct dsa_switch *ds, int portnumber, int devad,
+			     int regnum);
+int soce_mdio_23_02_write_c45(struct dsa_switch *ds, int portnumber, int devad,
+			      int regnum, u16 val);
+int soce_user_mii_bus_register(struct dsa_switch *ds);
+void soce_user_mii_bus_unregister(struct dsa_switch *ds);
+#endif /* __SOCE_MDIO_H */
diff --git a/drivers/net/dsa/soce/soce_mrs.h b/drivers/net/dsa/soce/soce_mrs.h
new file mode 100644
index 000000000000..cf2b29f1297e
--- /dev/null
+++ b/drivers/net/dsa/soce/soce_mrs.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2020 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ */
+
+#ifndef __SOCE_MRS_H
+#define __SOCE_MRS_H
+
+#define SOCE_MRS_21_01_MSTP_PORT_STATE_CONTROL_REG	0x0D04
+#define SOCE_MRS_21_01_MSTP_MSTID_OFFSET		0x0D08
+#define SOCE_MRS_21_01_MSTP_PORT_STATE_OFFSET_LO	0x0D0C
+#define SOCE_MRS_21_01_MSTP_PORT_STATE_OFFSET_HI	0x0D10
+#define SOCE_MRS_24_01_MSTP_PORT_STATE_CONTROL_REG	0x0F04
+#define SOCE_MRS_24_01_MSTP_MSTID_OFFSET		0x0F08
+#define SOCE_MRS_24_01_MSTP_PORT_STATE_OFFSET_LO	0x0F0C
+#define SOCE_MRS_24_01_MSTP_PORT_STATE_OFFSET_HI	0x0F10
+#define SOCE_MRS_21_01_FDB_CTRL_OFFSET			0x0A0C
+#define SOCE_MRS_24_01_FDB_CTRL_OFFSET			0x0C0C
+#define SOCE_MRS_21_01_MDIO_MASTER_OFFSET		0x0200
+
+#endif /* __SOCE_MRS_H */

-- 
2.39.5


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 0/4] net: dsa: Add SoC-e DSA driver
  2026-07-29 16:36 [PATCH 0/4] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
                   ` (3 preceding siblings ...)
  2026-07-29 16:36 ` [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches Vasilij Strassheim
@ 2026-07-29 16:56 ` Andrew Lunn
  4 siblings, 0 replies; 11+ messages in thread
From: Andrew Lunn @ 2026-07-29 16:56 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, devicetree, linux-kernel, netdev,
	Martin Kaistra

On Wed, Jul 29, 2026 at 06:36:53PM +0200, Vasilij Strassheim wrote:
> Add initial support for the DSA driver for SoC-e FPGA-based (IP core)
> Ethernet switches.

Let me start with a dumb question. Is this a true soft core switch,
which is synthesised and running in the FPGA? Or is it hardware at the
edge of the FPGA SoC?

If it is synthesised, do we have to deal with different synthesis
options? Are there registers which indicate what these options are?

Where as if it is fixed hardware in the SoC we don't need to worry so
much about variations, it is fixed at the SoC level.

> There are different variants of the SoC-e switch IP, which can be
> additionally configured at runtime with proprietary userspace software.

What sort of configuration?

     Andrew

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings
  2026-07-29 16:36 ` [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings Vasilij Strassheim
@ 2026-07-29 17:11   ` Andrew Lunn
  2026-07-29 18:07   ` Rob Herring (Arm)
  1 sibling, 0 replies; 11+ messages in thread
From: Andrew Lunn @ 2026-07-29 17:11 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, devicetree, linux-kernel, netdev,
	Martin Kaistra

> +description: |
> +  This node is the MDIO-bus device entry for the SoC-e DSA switch driver.
> +  It is a lightweight configuration node that lives on the MDIO bus of the
> +  SoC Ethernet controller acting as the DSA conduit (CPU port).

Must it be the conduit interfaces MDIO bus? There are boards which
exist using Marvell switches where the MDIO bus of the conduit
interface is not a Linux MDIO bus, because it is an intel i210, and
intel likes reinventing the wheel and does not use the Linux MDIO bus
infrastructure. So the MDIO interface of the switch is connected to
two GPIOs and a Linux bit bashing MDIO bus is used.

> +  soce,num-ports:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: |
> +      Total number of switch ports (external/user ports plus the CPU uplink
> +      port). Must be greater than zero and not exceed the selected IP variant.

Why is this needed if we already have a description of the ports?

    Andrew

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 3/4] net: dsa: Add support for SoC-e SDSA tags
  2026-07-29 16:36 ` [PATCH 3/4] net: dsa: Add support for SoC-e SDSA tags Vasilij Strassheim
@ 2026-07-29 17:22   ` Andrew Lunn
  0 siblings, 0 replies; 11+ messages in thread
From: Andrew Lunn @ 2026-07-29 17:22 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, devicetree, linux-kernel, netdev,
	Martin Kaistra

> @@ -98,6 +98,12 @@ config NET_DSA_TAG_EDSA
>  	  Say Y or M if you want to enable support for tagging frames for the
>  	  Marvell switches which use EtherType DSA headers.
>  
> +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.
> +

These entries are sorted, so it probably should be between
NET_DSA_TAG_RZN1_A5PSW and NET_DSA_TAG_LAN9303.

> @@ -23,6 +23,7 @@ dsa_core-y += \
>  obj-$(CONFIG_NET_DSA_TAG_AR9331) += tag_ar9331.o
>  obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o
>  obj-$(CONFIG_NET_DSA_TAG_DSA_COMMON) += tag_dsa.o
> +obj-$(CONFIG_NET_DSA_TAG_SDSA) += tag_sdsa.o
>  obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o

Also sorted, and this is the wrong spot.

> +#define SDSA_HLEN	8
> +
> +#define SDSA_NAME	"sdsa"

Does SDSA mean anything? Or have you taken net/dsa/tag_dsa.c, and just
changed edsa to sdsa?

> +static struct sk_buff *sdsa_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> +	struct dsa_port *dp = dsa_user_to_port(dev);
> +	u8 *sdsa_header;
> +
> +	if (skb_cow_head(skb, SDSA_HLEN) < 0)
> +		return NULL;
> +
> +	skb_push(skb, SDSA_HLEN);
> +	dsa_alloc_etype_header(skb, SDSA_HLEN);
> +
> +	/* Construct the FROM_CPU DSA tag. */

Is FROM_CPU a concept for this device? Are there other types of tag?

> +	sdsa_header = dsa_etype_header_pos_tx(skb);
> +	sdsa_header[0] = (ETH_P_SDSA >> 8) & 0xff;
> +	sdsa_header[1] = ETH_P_SDSA & 0xff;
> +	sdsa_header[2] = 0x00; /* reserved */
> +	sdsa_header[3] = 0x00; /* reserved */
> +	sdsa_header[4] = FIELD_PREP(SDSA_TAG_FRAME_TYPE_MASK, 1) |
> +			 FIELD_PREP(SDSA_TAG_PORT_HI_MASK, dp->index >> 5);
> +	sdsa_header[5] = FIELD_PREP(SDSA_TAG_PORT_MASK, dp->index);
> +	sdsa_header[6] = 0x00; /* VLAN not supported */
> +	sdsa_header[7] = 0x00; /* VLAN not supported */
> +
> +	return skb;
> +}
> +
> +static struct sk_buff *sdsa_rcv(struct sk_buff *skb, struct net_device *dev)
> +{
> +	u8 *sdsa_header;
> +	int source_port;
> +	u8 frame_type;
> +
> +	if (unlikely(!pskb_may_pull(skb, SDSA_HLEN)))
> +		return NULL;
> +
> +	sdsa_header = dsa_etype_header_pos_rx(skb);
> +
> +	/* Check that the frame type is TO_CPU. */
> +	frame_type = FIELD_GET(SDSA_TAG_FRAME_TYPE_MASK, sdsa_header[4]);
> +	if (frame_type != 0)

#define for TO_CPU?

	Andrew

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches
  2026-07-29 16:36 ` [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches Vasilij Strassheim
@ 2026-07-29 17:31   ` Andrew Lunn
  2026-07-29 20:05   ` Andrew Lunn
  1 sibling, 0 replies; 11+ messages in thread
From: Andrew Lunn @ 2026-07-29 17:31 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, devicetree, linux-kernel, netdev,
	Martin Kaistra

> +static int soce_sw_probe(struct mdio_device *mdiodev)
> +{
> +	const struct soce_variant_desc *variant;
> +	struct device *dev = &mdiodev->dev;
> +	struct device_node *switch_node;
> +	struct soce_dsa_local *local;
> +	const char *soce_compatible;
> +	struct soce_priv *priv;
> +	u32 numports;
> +	int ret;
> +
> +	priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
> +	if (!priv->ds)
> +		return -ENOMEM;
> +
> +	priv->ds->dev = dev;
> +	priv->ds->priv = priv;
> +	local = &priv->local;
> +
> +	switch_node = of_parse_phandle(dev->of_node, "soce,switch-ip", 0);
> +	if (!switch_node) {
> +		dev_err(dev, "missing or invalid switch IP reference\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = of_property_read_string(switch_node, "compatible",
> +				      &soce_compatible);
> +	if (ret) {
> +		dev_err(dev, "missing compatible property in %pOF\n",
> +			switch_node);
> +		of_node_put(switch_node);
> +		return -EINVAL;
> +	}
> +
> +	variant = soce_match_variant(soce_compatible);
> +	if (!variant) {
> +		dev_err(dev, "unsupported compatible '%s' in %pOF\n",
> +			soce_compatible, switch_node);
> +		of_node_put(switch_node);
> +		return -ENODEV;
> +	}
> +
> +	ret = of_property_read_u32(switch_node, "soce,num-ports", &numports);
> +	if (ret) {
> +		dev_err(dev, "missing soce,num-ports in %pOF\n", switch_node);
> +		of_node_put(switch_node);
> +		return -EINVAL;
> +	}
> +	if (numports == 0 || numports > SOCE_MAX_NUM_PORTS) {
> +		dev_err(dev, "invalid soce,num-ports %u (max %d)\n", numports,
> +			SOCE_MAX_NUM_PORTS);
> +		of_node_put(switch_node);
> +		return -EINVAL;
> +	}
> +
> +	local->base_addr = devm_of_iomap(dev, switch_node, 0, NULL);
> +	if (IS_ERR(local->base_addr)) {
> +		dev_err(dev, "failed to map switch register space for %pOF\n",
> +			switch_node);
> +		of_node_put(switch_node);
> +		return PTR_ERR(local->base_addr);
> +	}
> +
> +	priv->ds->ops = &soce_switch_ops;
> +	local->mdio_ops = variant->mdio_ops;
> +	local->mdio_master_addr =
> +		local->base_addr + variant->mdio_master_offset;
> +	local->layout = variant->layout;
> +	of_node_put(switch_node);
> +
> +	priv->ds->num_ports = numports;
> +	ret = soce_sw_parse_port_mdio(priv, dev, numports);
> +	if (ret)
> +		return ret;
> +
> +	dev_set_drvdata(&mdiodev->dev, priv);
> +
> +	dev_info(dev, "soce %s SDSA driver probed.\n", soce_compatible);
> +
> +	return dsa_register_switch(priv->ds);
> +}

Looking at this struct mdio_device *mdiodev is not used, other than
for ->dev. Is this device actually on an MDIO bus?

	Andrew

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings
  2026-07-29 16:36 ` [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings Vasilij Strassheim
  2026-07-29 17:11   ` Andrew Lunn
@ 2026-07-29 18:07   ` Rob Herring (Arm)
  1 sibling, 0 replies; 11+ messages in thread
From: Rob Herring (Arm) @ 2026-07-29 18:07 UTC (permalink / raw)
  To: Vasilij Strassheim
  Cc: Russell King, Paolo Abeni, netdev, Conor Dooley, Vladimir Oltean,
	Simon Horman, David S. Miller, Martin Kaistra, linux-kernel,
	Krzysztof Kozlowski, Andrew Lunn, Eric Dumazet, devicetree,
	Jakub Kicinski


On Wed, 29 Jul 2026 18:36:55 +0200, Vasilij Strassheim wrote:
> Add DT schema documentation for SoC-e FPGA switch devices.
> The binding is split into:
>  - a switch IP node describing the MMIO register block
>  - a DSA MDIO node describing the DSA-facing switch instance
> Both nodes are linked through the soce,switch-ip phandle.
> 
> Signed-off-by: Vasilij Strassheim <v.strassheim@linutronix.de>
> ---
>  .../bindings/net/dsa/soce,switch-dsa.yaml          | 195 +++++++++++++++++++++
>  .../bindings/net/dsa/soce,switch-ip.yaml           |  54 ++++++
>  2 files changed, 249 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/net/dsa/soce,switch-ip.example.dtb: switch@80020000 (soce,mrs-24-01): reg: [[0, 2147614720], [0, 65536]] is too long
	from schema $id: http://devicetree.org/schemas/net/dsa/soce,switch-ip.yaml

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260729-devel-vstrassheim-soce-dsa-ml-v1-2-be569dae1b20@linutronix.de

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches
  2026-07-29 16:36 ` [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches Vasilij Strassheim
  2026-07-29 17:31   ` Andrew Lunn
@ 2026-07-29 20:05   ` Andrew Lunn
  1 sibling, 0 replies; 11+ messages in thread
From: Andrew Lunn @ 2026-07-29 20: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, devicetree, linux-kernel, netdev,
	Martin Kaistra

> +static u8 soce_map_stp_state(u8 state)
> +{
> +	switch (state) {
> +	case BR_STATE_DISABLED:
> +	case BR_STATE_BLOCKING:
> +	case BR_STATE_LISTENING:
> +		return 0;
> +	case BR_STATE_LEARNING:
> +		return 1;
> +	case BR_STATE_FORWARDING:
> +	default:
> +		return 3;

#defines for 0, 1 and 3.

What does 2 mean?

Since you don't implement bridge offload, i'm not sure having STP
states makes any sense.

> +static void soce_fast_age(struct dsa_switch *ds, int port,
> +			  const struct soce_layout *layout)
> +{
> +	struct soce_priv *priv = ds->priv;
> +	struct soce_dsa_local *local;
> +	void __iomem *p_mes_ctrl;
> +	u32 val;

Does aging make any sense without bridge offload?

> +/* Bridge membership is handled by the switch forwarding model directly. */
> +int soce_port_bridge_join(struct dsa_switch *ds, int port,
> +			  struct dsa_bridge bridge, bool *tx_fwd_offload,
> +			  struct netlink_ext_ack *extack)
> +{
> +	return 0;
> +}
> +
> +void soce_port_bridge_leave(struct dsa_switch *ds, int port,
> +			    struct dsa_bridge bridge)
> +{
> +}

If you don't support bridge offload, you should not need these.

> +static void soce_phylink_get_caps(struct dsa_switch *ds, int port,
> +				  struct phylink_config *config)
> +{
> +	if (dsa_is_cpu_port(ds, port)) {
> +		__set_bit(PHY_INTERFACE_MODE_GMII,

Is that really true? What is part of the IP core, and what is to do
with integration of the core into the system as a whole? It seems like
RGMII is also supported, so why cannot i instantiate a RGMII MII blob
for the CPU port as well?

> +			  config->supported_interfaces);
> +		config->mac_capabilities = MAC_10 | MAC_100 | MAC_1000 |
> +					   MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
> +		return;
> +	}
> +
> +	phy_interface_set_rgmii(config->supported_interfaces);

And flipping that around, if the core supports GMII, could i
instantiate a GMII user port?

> +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,
> +	.phy_read		= soce_phy_read,
> +	.phy_write		= soce_phy_write,

phy_read and phy_write are legacy. Instantiate a Linux MDIO bus and
don't list anything here.

> +static const struct soce_variant_desc *
> +soce_match_variant(const char *compatible)
> +{
> +	int i;
> +
> +	for (i = 0; soce_variants[i].compatible; i++) {
> +		if (!strcmp(compatible, soce_variants[i].compatible))
> +			return &soce_variants[i];
> +	}
> +
> +	return NULL;

This kind of looks like a reinvention of of_device_get_match_data()

> +static int soce_sw_parse_port_mdio_config(struct soce_dsa_local *local,
> +					  struct device *dev,
> +					  struct device_node *port_node,
> +					  u32 port)
> +{
> +	struct device_node *phy_node;
> +	u32 val;
> +	int ret;
> +
> +	if (of_find_property(port_node, "ethernet", NULL) ||
> +	    of_find_property(port_node, "link", NULL)) {
> +		if (of_find_property(port_node, "phy-handle", NULL)) {
> +			dev_err(dev,
> +				"phy-handle not allowed on CPU/DSA port %u\n",
> +				port);
> +			return -EINVAL;
> +		}
> +
> +		return 0;
> +	}

Why is all of this parsing of DT needed? These should all be standard
properties which the DSA core handles.

This currently has the feel of being a "vendor crap" driver. Please
spend some time to clean this driver up, bring it up to Mainline
standards.

	Andrew

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-07-29 20:05 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 16:36 [PATCH 0/4] net: dsa: Add SoC-e DSA driver Vasilij Strassheim
2026-07-29 16:36 ` [PATCH 1/4] dt-bindings: vendor-prefixes: Add soce Vasilij Strassheim
2026-07-29 16:36 ` [PATCH 2/4] dt-bindings: net: dsa: Add SoC-e switch IP and DSA bindings Vasilij Strassheim
2026-07-29 17:11   ` Andrew Lunn
2026-07-29 18:07   ` Rob Herring (Arm)
2026-07-29 16:36 ` [PATCH 3/4] net: dsa: Add support for SoC-e SDSA tags Vasilij Strassheim
2026-07-29 17:22   ` Andrew Lunn
2026-07-29 16:36 ` [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches Vasilij Strassheim
2026-07-29 17:31   ` Andrew Lunn
2026-07-29 20:05   ` Andrew Lunn
2026-07-29 16:56 ` [PATCH 0/4] net: dsa: Add SoC-e DSA driver Andrew Lunn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox