All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/3] net: ethernet: renesas: Add Ethernet Switch driver
@ 2022-09-22  5:28 Yoshihiro Shimoda
  2022-09-22  5:28 ` [PATCH v3 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch Yoshihiro Shimoda
                   ` (2 more replies)
  0 siblings, 3 replies; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-22  5:28 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt
  Cc: netdev, devicetree, linux-renesas-soc, Yoshihiro Shimoda

This patch series is based on next-20220915.
Add minimal support for R-Car S4-8 Etherent Switch. This hardware
supports a lot of features. But, this driver only supports it as
act as an ethernet controller for now.

Changes from v2:
 https://lore.kernel.org/all/20220921084745.3355107-1-yoshihiro.shimoda.uh@renesas.com/
 - Separate patcheas into each subsystem.
 - Add spin lock protection for multiple registers access in patch [3/3].

Yoshihiro Shimoda (3):
  dt-bindings: net: renesas: Document Renesas Ethernet Switch
  net: ethernet: renesas: Add Ethernet Switch driver
  net: ethernet: renesas: rswitch: Add R-Car Gen4 gPTP support

 .../bindings/net/renesas,etherswitch.yaml     |  286 +++
 drivers/net/ethernet/renesas/Kconfig          |   11 +
 drivers/net/ethernet/renesas/Makefile         |    4 +
 drivers/net/ethernet/renesas/rcar_gen4_ptp.c  |  181 ++
 drivers/net/ethernet/renesas/rcar_gen4_ptp.h  |   72 +
 drivers/net/ethernet/renesas/rswitch.c        | 1779 +++++++++++++++++
 drivers/net/ethernet/renesas/rswitch.h        |  967 +++++++++
 7 files changed, 3300 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/renesas,etherswitch.yaml
 create mode 100644 drivers/net/ethernet/renesas/rcar_gen4_ptp.c
 create mode 100644 drivers/net/ethernet/renesas/rcar_gen4_ptp.h
 create mode 100644 drivers/net/ethernet/renesas/rswitch.c
 create mode 100644 drivers/net/ethernet/renesas/rswitch.h

-- 
2.25.1


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

* [PATCH v3 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch
  2022-09-22  5:28 [PATCH v3 0/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
@ 2022-09-22  5:28 ` Yoshihiro Shimoda
  2022-09-22  7:39   ` Krzysztof Kozlowski
  2022-09-22  5:28 ` [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
  2022-09-22  5:28 ` [PATCH v3 3/3] net: ethernet: renesas: rswitch: Add R-Car Gen4 gPTP support Yoshihiro Shimoda
  2 siblings, 1 reply; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-22  5:28 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt
  Cc: netdev, devicetree, linux-renesas-soc, Yoshihiro Shimoda

Document Renesas Etherent Switch for R-Car S4-8 (r8a779f0).

Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
 .../bindings/net/renesas,etherswitch.yaml     | 286 ++++++++++++++++++
 1 file changed, 286 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/renesas,etherswitch.yaml

diff --git a/Documentation/devicetree/bindings/net/renesas,etherswitch.yaml b/Documentation/devicetree/bindings/net/renesas,etherswitch.yaml
new file mode 100644
index 000000000000..988d14f5c54e
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/renesas,etherswitch.yaml
@@ -0,0 +1,286 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/renesas,etherswitch.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas Ethernet Switch
+
+maintainers:
+  - Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
+
+properties:
+  compatible:
+    const: renesas,r8a779f0-ether-switch
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: base
+      - const: secure_base
+
+  interrupts:
+    maxItems: 47
+
+  interrupt-names:
+    items:
+      - const: mfwd_error
+      - const: race_error
+      - const: coma_error
+      - const: gwca0_error
+      - const: gwca1_error
+      - const: etha0_error
+      - const: etha1_error
+      - const: etha2_error
+      - const: gptp0_status
+      - const: gptp1_status
+      - const: mfwd_status
+      - const: race_status
+      - const: coma_status
+      - const: gwca0_status
+      - const: gwca1_status
+      - const: etha0_status
+      - const: etha1_status
+      - const: etha2_status
+      - const: rmac0_status
+      - const: rmac1_status
+      - const: rmac2_status
+      - const: gwca0_rxtx0
+      - const: gwca0_rxtx1
+      - const: gwca0_rxtx2
+      - const: gwca0_rxtx3
+      - const: gwca0_rxtx4
+      - const: gwca0_rxtx5
+      - const: gwca0_rxtx6
+      - const: gwca0_rxtx7
+      - const: gwca1_rxtx0
+      - const: gwca1_rxtx1
+      - const: gwca1_rxtx2
+      - const: gwca1_rxtx3
+      - const: gwca1_rxtx4
+      - const: gwca1_rxtx5
+      - const: gwca1_rxtx6
+      - const: gwca1_rxtx7
+      - const: gwca0_rxts0
+      - const: gwca0_rxts1
+      - const: gwca1_rxts0
+      - const: gwca1_rxts1
+      - const: rmac0_mdio
+      - const: rmac1_mdio
+      - const: rmac2_mdio
+      - const: rmac0_phy
+      - const: rmac1_phy
+      - const: rmac2_phy
+
+  clocks:
+    maxItems: 2
+
+  clock-names:
+    items:
+      - const: fck
+      - const: tsn
+
+  resets:
+    maxItems: 2
+
+  reset-names:
+    items:
+      - const: rswitch2
+      - const: tsn
+
+  iommus:
+    maxItems: 16
+
+  power-domains:
+    maxItems: 1
+
+  ethernet-ports:
+    type: object
+
+    properties:
+      '#address-cells':
+        description: Port number of ETHA (TSNA).
+        const: 1
+      '#size-cells':
+        const: 0
+
+    additionalProperties: false
+
+    patternProperties:
+      "^port@[0-9a-f]+$":
+        type: object
+
+        $ref: "/schemas/net/ethernet-controller.yaml#"
+        unevaluatedProperties: false
+
+        properties:
+          reg:
+            description:
+              Port number of ETHA (TSNA).
+
+          phy-handle:
+            description:
+              Phandle of an Ethernet PHY.
+
+          phy-mode:
+            description:
+              This specifies the interface used by the Ethernet PHY.
+            enum:
+              - mii
+              - sgmii
+              - usxgmii
+
+          phys:
+            maxItems: 1
+            description:
+              Phandle of an Ethernet SERDES.
+
+          mdio:
+            $ref: "/schemas/net/mdio.yaml#"
+            unevaluatedProperties: false
+
+        required:
+          - phy-handle
+          - phy-mode
+          - phys
+          - mdio
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - interrupt-names
+  - clocks
+  - clock-names
+  - resets
+  - power-domains
+  - ethernet-ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/r8a779f0-cpg-mssr.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/power/r8a779f0-sysc.h>
+
+    ethernet@e6880000 {
+            compatible = "renesas,r8a779f0-ether-switch";
+            reg = <0xe6880000 0x20000>, <0xe68c0000 0x20000>;
+            reg-names = "base", "secure_base";
+            interrupts = <GIC_SPI 256 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 257 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 258 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 259 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 260 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 261 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 262 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 263 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 266 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 267 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 268 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 269 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 270 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 271 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 272 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 273 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 274 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 276 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 277 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 278 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 280 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 281 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 282 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 283 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 284 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 285 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 286 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 287 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 288 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 289 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 290 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 291 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 292 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 293 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 294 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 295 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 296 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 297 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 298 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 299 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 300 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 301 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 304 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 305 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 306 IRQ_TYPE_LEVEL_HIGH>;
+            interrupt-names = "mfwd_error", "race_error",
+                              "coma_error", "gwca0_error",
+                              "gwca1_error", "etha0_error",
+                              "etha1_error", "etha2_error",
+                              "gptp0_status", "gptp1_status",
+                              "mfwd_status", "race_status",
+                              "coma_status", "gwca0_status",
+                              "gwca1_status", "etha0_status",
+                              "etha1_status", "etha2_status",
+                              "rmac0_status", "rmac1_status",
+                              "rmac2_status",
+                              "gwca0_rxtx0", "gwca0_rxtx1",
+                              "gwca0_rxtx2", "gwca0_rxtx3",
+                              "gwca0_rxtx4", "gwca0_rxtx5",
+                              "gwca0_rxtx6", "gwca0_rxtx7",
+                              "gwca1_rxtx0", "gwca1_rxtx1",
+                              "gwca1_rxtx2", "gwca1_rxtx3",
+                              "gwca1_rxtx4", "gwca1_rxtx5",
+                              "gwca1_rxtx6", "gwca1_rxtx7",
+                              "gwca0_rxts0", "gwca0_rxts1",
+                              "gwca1_rxts0", "gwca1_rxts1",
+                              "rmac0_mdio", "rmac1_mdio",
+                              "rmac2_mdio",
+                              "rmac0_phy", "rmac1_phy",
+                              "rmac2_phy";
+            clocks = <&cpg CPG_MOD 1506>, <&cpg CPG_MOD 1505>;
+            clock-names = "fck", "tsn";
+            power-domains = <&sysc R8A779F0_PD_ALWAYS_ON>;
+            resets = <&cpg 1506>, <&cpg 1505>;
+            reset-names = "rswitch2", "tsn";
+
+            ethernet-ports {
+                    #address-cells = <1>;
+                    #size-cells = <0>;
+                    port@0 {
+                            reg = <0>;
+                            phy-handle = <&eth_phy0>;
+                            phy-mode = "sgmii";
+                            phys = <&eth_serdes 0>;
+                            mdio {
+                                    #address-cells = <1>;
+                                    #size-cells = <0>;
+                            };
+                    };
+                    port@1 {
+                            reg = <1>;
+                            phy-handle = <&eth_phy1>;
+                            phy-mode = "sgmii";
+                            phys = <&eth_serdes 1>;
+                            mdio {
+                                    #address-cells = <1>;
+                                    #size-cells = <0>;
+                            };
+                    };
+                    port@2 {
+                            reg = <2>;
+                            phy-handle = <&eth_phy2>;
+                            phy-mode = "sgmii";
+                            phys = <&eth_serdes 2>;
+                            mdio {
+                                    #address-cells = <1>;
+                                    #size-cells = <0>;
+                            };
+                    };
+            };
+    };
-- 
2.25.1


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

* [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-22  5:28 [PATCH v3 0/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
  2022-09-22  5:28 ` [PATCH v3 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch Yoshihiro Shimoda
@ 2022-09-22  5:28 ` Yoshihiro Shimoda
  2022-09-23  2:43   ` Jakub Kicinski
  2022-09-23 13:11   ` Andrew Lunn
  2022-09-22  5:28 ` [PATCH v3 3/3] net: ethernet: renesas: rswitch: Add R-Car Gen4 gPTP support Yoshihiro Shimoda
  2 siblings, 2 replies; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-22  5:28 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt
  Cc: netdev, devicetree, linux-renesas-soc, Yoshihiro Shimoda

Add Renesas Ethernet Switch driver for R-Car S4-8 to be used as an
ethernet controller.

Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
 drivers/net/ethernet/renesas/Kconfig   |   11 +
 drivers/net/ethernet/renesas/Makefile  |    4 +
 drivers/net/ethernet/renesas/rswitch.c | 1631 ++++++++++++++++++++++++
 drivers/net/ethernet/renesas/rswitch.h |  965 ++++++++++++++
 4 files changed, 2611 insertions(+)
 create mode 100644 drivers/net/ethernet/renesas/rswitch.c
 create mode 100644 drivers/net/ethernet/renesas/rswitch.h

diff --git a/drivers/net/ethernet/renesas/Kconfig b/drivers/net/ethernet/renesas/Kconfig
index 8008b2f45934..4ae170056ac8 100644
--- a/drivers/net/ethernet/renesas/Kconfig
+++ b/drivers/net/ethernet/renesas/Kconfig
@@ -42,4 +42,15 @@ config RAVB
 	  This driver supports the following SoCs:
 		- R8A779x.
 
+config RENESAS_ETHER_SWITCH
+	tristate "Renesas Ethernet Switch support"
+	depends on ARCH_RENESAS || COMPILE_TEST
+	select CRC32
+	select MII
+	select PHYLIB
+	help
+	  Renesas Ethernet Switch device driver.
+	  This driver supports the following SoCs:
+		- R8A779Fx.
+
 endif # NET_VENDOR_RENESAS
diff --git a/drivers/net/ethernet/renesas/Makefile b/drivers/net/ethernet/renesas/Makefile
index f21ab8c02af0..32845ba53970 100644
--- a/drivers/net/ethernet/renesas/Makefile
+++ b/drivers/net/ethernet/renesas/Makefile
@@ -8,3 +8,7 @@ obj-$(CONFIG_SH_ETH) += sh_eth.o
 ravb-objs := ravb_main.o ravb_ptp.o
 
 obj-$(CONFIG_RAVB) += ravb.o
+
+rswitch_drv-objs := rswitch.o
+
+obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch_drv.o
diff --git a/drivers/net/ethernet/renesas/rswitch.c b/drivers/net/ethernet/renesas/rswitch.c
new file mode 100644
index 000000000000..2723dffad93a
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rswitch.c
@@ -0,0 +1,1631 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Renesas Ethernet Switch device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/etherdevice.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/phy/phy.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "rswitch.h"
+
+static int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected)
+{
+	u32 val;
+
+	return readl_poll_timeout_atomic(addr + offs, val, (val & mask) == expected,
+					 1, RSWITCH_TIMEOUT_US);
+}
+
+static void rswitch_modify(void __iomem *addr, enum rswitch_reg reg, u32 clear, u32 set)
+{
+	iowrite32((ioread32(addr + reg) & ~clear) | set, addr + reg);
+}
+
+/* Common Agent block (COMA) */
+static void rswitch_reset(struct rswitch_private *priv)
+{
+	iowrite32(RRC_RR, priv->addr + RRC);
+	iowrite32(RRC_RR_CLR, priv->addr + RRC);
+}
+
+static void rswitch_clock_enable(struct rswitch_private *priv)
+{
+	iowrite32(GENMASK(RSWITCH_NUM_HW - 1, 0) | RCEC_RCE, priv->addr + RCEC);
+}
+
+static void rswitch_clock_disable(struct rswitch_private *priv)
+{
+	iowrite32(RCDC_RCD, priv->addr + RCDC);
+}
+
+static bool rswitch_agent_clock_is_enabled(void __iomem *coma_addr, int port)
+{
+	u32 val = ioread32(coma_addr + RCEC);
+
+	if (val & RCEC_RCE)
+		return (val & BIT(port)) ? true : false;
+	else
+		return false;
+}
+
+static void rswitch_agent_clock_ctrl(void __iomem *coma_addr, int port, int enable)
+{
+	u32 val;
+
+	if (enable) {
+		val = ioread32(coma_addr + RCEC);
+		iowrite32(val | RCEC_RCE | BIT(port), coma_addr + RCEC);
+	} else {
+		val = ioread32(coma_addr + RCDC);
+		iowrite32(val | BIT(port), coma_addr + RCDC);
+	}
+}
+
+static int rswitch_bpool_config(struct rswitch_private *priv)
+{
+	u32 val;
+
+	val = ioread32(priv->addr + CABPIRM);
+	if (val & CABPIRM_BPR)
+		return 0;
+
+	iowrite32(CABPIRM_BPIOG, priv->addr + CABPIRM);
+
+	return rswitch_reg_wait(priv->addr, CABPIRM, CABPIRM_BPR, CABPIRM_BPR);
+}
+
+/* R-Switch-2 block (TOP) */
+static void rswitch_top_init(struct rswitch_private *priv)
+{
+	int i;
+
+	for (i = 0; i < RSWITCH_MAX_NUM_CHAINS; i++)
+		iowrite32((i / 16) << (GWCA_INDEX * 8), priv->addr + TPEMIMC7(i));
+}
+
+/* Forwarding engine block (MFWD) */
+static void rswitch_fwd_init(struct rswitch_private *priv)
+{
+	int i;
+
+	for (i = 0; i < RSWITCH_NUM_HW; i++) {
+		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
+		iowrite32(0, priv->addr + FWPBFC(i));
+	}
+
+	for (i = 0; i < RSWITCH_NUM_ETHA; i++) {
+		iowrite32(priv->rdev[i]->rx_chain->index,
+			  priv->addr + FWPBFCSDC(GWCA_INDEX, i));
+		iowrite32(BIT(priv->gwca.index), priv->addr + FWPBFC(i));
+	}
+	iowrite32(GENMASK(RSWITCH_NUM_ETHA - 1, 0), priv->addr + FWPBFC(3));
+}
+
+/* Gateway CPU agent block (GWCA) */
+static int rswitch_gwca_change_mode(struct rswitch_private *priv,
+				    enum rswitch_gwca_mode mode)
+{
+	int ret;
+
+	if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
+		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
+
+	iowrite32(mode, priv->addr + GWMC);
+
+	ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, mode);
+
+	if (mode == GWMC_OPC_DISABLE)
+		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 0);
+
+	return ret;
+}
+
+static int rswitch_gwca_mcast_table_reset(struct rswitch_private *priv)
+{
+	iowrite32(GWMTIRM_MTIOG, priv->addr + GWMTIRM);
+
+	return rswitch_reg_wait(priv->addr, GWMTIRM, GWMTIRM_MTR, GWMTIRM_MTR);
+}
+
+static int rswitch_gwca_axi_ram_reset(struct rswitch_private *priv)
+{
+	iowrite32(GWARIRM_ARIOG, priv->addr + GWARIRM);
+
+	return rswitch_reg_wait(priv->addr, GWARIRM, GWARIRM_ARR, GWARIRM_ARR);
+}
+
+static void rswitch_gwca_set_rate_limit(struct rswitch_private *priv, int rate)
+{
+	u32 gwgrlulc, gwgrlc;
+
+	switch (rate) {
+	case 1000:
+		gwgrlulc = 0x0000005f;
+		gwgrlc = 0x00010260;
+		break;
+	default:
+		dev_err(&priv->pdev->dev, "%s: This rate is not supported (%d)\n", __func__, rate);
+		return;
+	}
+
+	iowrite32(gwgrlulc, priv->addr + GWGRLULC);
+	iowrite32(gwgrlc, priv->addr + GWGRLC);
+}
+
+static bool rswitch_is_any_data_irq(struct rswitch_private *priv, u32 *dis, bool tx)
+{
+	u32 *mask = tx ? priv->gwca.tx_irq_bits : priv->gwca.rx_irq_bits;
+	int i;
+
+	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
+		if (dis[i] & mask[i])
+			return true;
+	}
+
+	return false;
+}
+
+static void rswitch_get_data_irq_status(struct rswitch_private *priv, u32 *dis)
+{
+	int i;
+
+	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
+		dis[i] = ioread32(priv->addr + GWDIS(i));
+		dis[i] &= ioread32(priv->addr + GWDIE(i));
+	}
+}
+
+static void rswitch_enadis_data_irq(struct rswitch_private *priv, int index, bool enable)
+{
+	u32 offs = enable ? GWDIE(index / 32) : GWDID(index / 32);
+
+	iowrite32(BIT(index % 32), priv->addr + offs);
+}
+
+static void rswitch_ack_data_irq(struct rswitch_private *priv, int index)
+{
+	u32 offs = GWDIS(index / 32);
+
+	iowrite32(BIT(index % 32), priv->addr + offs);
+}
+
+static bool rswitch_is_chain_rxed(struct rswitch_gwca_chain *c)
+{
+	struct rswitch_ext_ts_desc *desc;
+	int entry;
+
+	entry = c->dirty % c->num_ring;
+	desc = &c->ts_ring[entry];
+
+	if ((desc->die_dt & DT_MASK) != DT_FEMPTY)
+		return true;
+
+	return false;
+}
+
+static int rswitch_gwca_chain_alloc_skb(struct rswitch_gwca_chain *c,
+					int start, int num)
+{
+	int i, index;
+
+	for (i = start; i < (start + num); i++) {
+		index = i % c->num_ring;
+		if (c->skb[index])
+			continue;
+		c->skb[index] = dev_alloc_skb(PKT_BUF_SZ + RSWITCH_ALIGN - 1);
+		if (!c->skb[index])
+			goto err;
+		skb_reserve(c->skb[index], NET_IP_ALIGN);
+	}
+
+	return 0;
+
+err:
+	for (i--; i >= start; i--) {
+		index = i % c->num_ring;
+		dev_kfree_skb(c->skb[index]);
+		c->skb[index] = NULL;
+	}
+
+	return -ENOMEM;
+}
+
+static void rswitch_gwca_chain_free(struct net_device *ndev,
+				    struct rswitch_gwca_chain *c)
+{
+	int i;
+
+	if (c->gptp) {
+		dma_free_coherent(ndev->dev.parent,
+				  sizeof(struct rswitch_ext_ts_desc) *
+				  (c->num_ring + 1), c->ts_ring, c->ring_dma);
+		c->ts_ring = NULL;
+	} else {
+		dma_free_coherent(ndev->dev.parent,
+				  sizeof(struct rswitch_ext_desc) *
+				  (c->num_ring + 1), c->ring, c->ring_dma);
+		c->ring = NULL;
+	}
+
+	if (!c->dir_tx) {
+		for (i = 0; i < c->num_ring; i++)
+			dev_kfree_skb(c->skb[i]);
+	}
+
+	kfree(c->skb);
+	c->skb = NULL;
+}
+
+static int rswitch_gwca_chain_alloc(struct net_device *ndev,
+				    struct rswitch_private *priv,
+				    struct rswitch_gwca_chain *c,
+				    bool dir_tx, bool gptp, int num_ring)
+{
+	int i, bit;
+
+	c->dir_tx = dir_tx;
+	c->gptp = gptp;
+	c->num_ring = num_ring;
+	c->ndev = ndev;
+
+	c->skb = kcalloc(c->num_ring, sizeof(*c->skb), GFP_KERNEL);
+	if (!c->skb)
+		return -ENOMEM;
+
+	if (!dir_tx)
+		rswitch_gwca_chain_alloc_skb(c, 0, c->num_ring);
+
+	if (gptp)
+		c->ts_ring = dma_alloc_coherent(ndev->dev.parent,
+						sizeof(struct rswitch_ext_ts_desc) *
+						(c->num_ring + 1), &c->ring_dma, GFP_KERNEL);
+	else
+		c->ring = dma_alloc_coherent(ndev->dev.parent,
+					     sizeof(struct rswitch_ext_desc) *
+					     (c->num_ring + 1), &c->ring_dma, GFP_KERNEL);
+	if (!c->ts_ring && !c->ring)
+		goto out;
+
+	i = c->index / 32;
+	bit = BIT(c->index % 32);
+	if (dir_tx)
+		priv->gwca.tx_irq_bits[i] |= bit;
+	else
+		priv->gwca.rx_irq_bits[i] |= bit;
+
+	return 0;
+
+out:
+	rswitch_gwca_chain_free(ndev, c);
+
+	return -ENOMEM;
+}
+
+static int rswitch_gwca_chain_format(struct net_device *ndev,
+				     struct rswitch_private *priv,
+				     struct rswitch_gwca_chain *c)
+{
+	int tx_ring_size = sizeof(struct rswitch_ext_desc) * c->num_ring;
+	struct rswitch_ext_desc *ring;
+	struct rswitch_desc *desc;
+	dma_addr_t dma_addr;
+	int i;
+
+	memset(c->ring, 0, tx_ring_size);
+	for (i = 0, ring = c->ring; i < c->num_ring; i++, ring++) {
+		if (!c->dir_tx) {
+			dma_addr = dma_map_single(ndev->dev.parent,
+						  c->skb[i]->data, PKT_BUF_SZ,
+						  DMA_FROM_DEVICE);
+			if (dma_mapping_error(ndev->dev.parent, dma_addr))
+				goto err;
+
+			ring->info_ds = cpu_to_le16(PKT_BUF_SZ);
+			ring->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
+			ring->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
+			ring->die_dt = DT_FEMPTY | DIE;
+		} else {
+			ring->die_dt = DT_EEMPTY | DIE;
+		}
+	}
+	ring->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	ring->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+	ring->die_dt = DT_LINKFIX;
+
+	desc = &priv->desc_bat[c->index];
+	desc->die_dt = DT_LINKFIX;
+	desc->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	desc->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+
+	iowrite32(GWDCC_BALR | (c->dir_tx ? GWDCC_DQT : 0) | GWDCC_EDE,
+		  priv->addr + GWDCC_OFFS(c->index));
+
+	return 0;
+
+err:
+	if (!c->dir_tx) {
+		for (i--, ring = c->ring; i >= 0; i--, ring++) {
+			dma_addr = le32_to_cpu(ring->dptrl) |
+				   ((__le64)le32_to_cpu(ring->dptrh) << 32);
+			dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
+					 DMA_FROM_DEVICE);
+		}
+	}
+
+	return -ENOMEM;
+}
+
+static int rswitch_gwca_chain_ts_fill(struct net_device *ndev,
+				      struct rswitch_gwca_chain *c,
+				      int start, int num)
+{
+	struct rswitch_ext_ts_desc *ring;
+	dma_addr_t dma_addr;
+	int i, index;
+
+	for (i = start; i < (start + num); i++) {
+		index = i % c->num_ring;
+		ring = &c->ts_ring[index];
+		if (!c->dir_tx) {
+			dma_addr = dma_map_single(ndev->dev.parent,
+						  c->skb[index]->data, PKT_BUF_SZ,
+						  DMA_FROM_DEVICE);
+			if (dma_mapping_error(ndev->dev.parent, dma_addr))
+				goto err;
+
+			ring->info_ds = cpu_to_le16(PKT_BUF_SZ);
+			ring->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
+			ring->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
+			dma_wmb();
+			ring->die_dt = DT_FEMPTY | DIE;
+		} else {
+			ring->die_dt = DT_EEMPTY | DIE;
+		}
+	}
+
+	return 0;
+
+err:
+	if (!c->dir_tx) {
+		for (i--; i >= start; i--) {
+			index = (i + start) % c->num_ring;
+			ring = &c->ts_ring[index];
+			dma_addr = le32_to_cpu(ring->dptrl) |
+				   ((__le64)le32_to_cpu(ring->dptrh) << 32);
+			dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
+					 DMA_FROM_DEVICE);
+		}
+	}
+
+	return -ENOMEM;
+}
+
+static int rswitch_gwca_chain_ts_format(struct net_device *ndev,
+					struct rswitch_private *priv,
+					struct rswitch_gwca_chain *c)
+{
+	int tx_ts_ring_size = sizeof(struct rswitch_ext_ts_desc) * c->num_ring;
+	struct rswitch_ext_ts_desc *ring;
+	struct rswitch_desc *desc;
+	int err;
+
+	memset(c->ts_ring, 0, tx_ts_ring_size);
+	err = rswitch_gwca_chain_ts_fill(ndev, c, 0, c->num_ring);
+	if (err < 0)
+		return err;
+
+	ring = &c->ts_ring[c->num_ring];	/* Last */
+	ring->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	ring->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+	ring->die_dt = DT_LINKFIX;
+
+	desc = &priv->desc_bat[c->index];
+	desc->die_dt = DT_LINKFIX;
+	desc->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	desc->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+
+	iowrite32(GWDCC_BALR | (c->dir_tx ? GWDCC_DQT : 0) | GWDCC_ETS | GWDCC_EDE,
+		  priv->addr + GWDCC_OFFS(c->index));
+
+	return 0;
+}
+
+static int rswitch_gwca_desc_alloc(struct rswitch_private *priv)
+{
+	int i, num_chains = priv->gwca.num_chains;
+	struct device *dev = &priv->pdev->dev;
+
+	priv->desc_bat_size = sizeof(struct rswitch_desc) * num_chains;
+	priv->desc_bat = dma_alloc_coherent(dev, priv->desc_bat_size,
+					    &priv->desc_bat_dma, GFP_KERNEL);
+	if (!priv->desc_bat)
+		return -ENOMEM;
+	for (i = 0; i < num_chains; i++)
+		priv->desc_bat[i].die_dt = DT_EOS;
+
+	return 0;
+}
+
+static void rswitch_gwca_desc_free(struct rswitch_private *priv)
+{
+	if (priv->desc_bat)
+		dma_free_coherent(&priv->pdev->dev, priv->desc_bat_size,
+				  priv->desc_bat, priv->desc_bat_dma);
+	priv->desc_bat = NULL;
+}
+
+static struct rswitch_gwca_chain *rswitch_gwca_get(struct rswitch_private *priv)
+{
+	struct rswitch_gwca_chain *c;
+	int index;
+
+	index = find_first_zero_bit(priv->gwca.used, priv->gwca.num_chains);
+	if (index >= priv->gwca.num_chains)
+		return NULL;
+	set_bit(index, priv->gwca.used);
+	c = &priv->gwca.chains[index];
+	memset(c, 0, sizeof(*c));
+	c->index = index;
+
+	return c;
+}
+
+static void rswitch_gwca_put(struct rswitch_private *priv,
+			     struct rswitch_gwca_chain *c)
+{
+	clear_bit(c->index, priv->gwca.used);
+}
+
+static int rswitch_txdmac_alloc(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_private *priv = rdev->priv;
+	int err;
+
+	rdev->tx_chain = rswitch_gwca_get(priv);
+	if (!rdev->tx_chain)
+		return -EBUSY;
+
+	err = rswitch_gwca_chain_alloc(ndev, priv, rdev->tx_chain, true, false,
+				       TX_RING_SIZE);
+	if (err < 0) {
+		rswitch_gwca_put(priv, rdev->tx_chain);
+		return err;
+	}
+
+	return 0;
+}
+
+static void rswitch_txdmac_free(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	rswitch_gwca_chain_free(ndev, rdev->tx_chain);
+	rswitch_gwca_put(rdev->priv, rdev->tx_chain);
+}
+
+static int rswitch_txdmac_init(struct rswitch_private *priv, int index)
+{
+	struct rswitch_device *rdev = priv->rdev[index];
+
+	return rswitch_gwca_chain_format(rdev->ndev, priv, rdev->tx_chain);
+}
+
+static int rswitch_rxdmac_alloc(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_private *priv = rdev->priv;
+	int err;
+
+	rdev->rx_chain = rswitch_gwca_get(priv);
+	if (!rdev->rx_chain)
+		return -EBUSY;
+
+	err = rswitch_gwca_chain_alloc(ndev, priv, rdev->rx_chain, false, true,
+				       RX_RING_SIZE);
+	if (err < 0) {
+		rswitch_gwca_put(priv, rdev->rx_chain);
+		return err;
+	}
+
+	return 0;
+}
+
+static void rswitch_rxdmac_free(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	rswitch_gwca_chain_free(ndev, rdev->rx_chain);
+	rswitch_gwca_put(rdev->priv, rdev->rx_chain);
+}
+
+static int rswitch_rxdmac_init(struct rswitch_private *priv, int index)
+{
+	struct rswitch_device *rdev = priv->rdev[index];
+	struct net_device *ndev = rdev->ndev;
+
+	return rswitch_gwca_chain_ts_format(ndev, priv, rdev->rx_chain);
+}
+
+static int rswitch_gwca_hw_init(struct rswitch_private *priv)
+{
+	int i, err;
+
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_CONFIG);
+	if (err < 0)
+		return err;
+
+	err = rswitch_gwca_mcast_table_reset(priv);
+	if (err < 0)
+		return err;
+	err = rswitch_gwca_axi_ram_reset(priv);
+	if (err < 0)
+		return err;
+
+	iowrite32(GWVCC_VEM_SC_TAG, priv->addr + GWVCC);
+	iowrite32(0, priv->addr + GWTTFC);
+	iowrite32(lower_32_bits(priv->desc_bat_dma), priv->addr + GWDCBAC1);
+	iowrite32(upper_32_bits(priv->desc_bat_dma), priv->addr + GWDCBAC0);
+	rswitch_gwca_set_rate_limit(priv, priv->gwca.speed);
+
+	for (i = 0; i < RSWITCH_NUM_NDEV; i++) {
+		err = rswitch_rxdmac_init(priv, i);
+		if (err < 0)
+			return err;
+		err = rswitch_txdmac_init(priv, i);
+		if (err < 0)
+			return err;
+	}
+
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	return rswitch_gwca_change_mode(priv, GWMC_OPC_OPERATION);
+}
+
+static int rswitch_gwca_hw_deinit(struct rswitch_private *priv)
+{
+	int err;
+
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_RESET);
+	if (err < 0)
+		return err;
+
+	return rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+}
+
+static int rswitch_gwca_halt(struct rswitch_private *priv)
+{
+	int err;
+
+	priv->gwca_halt = true;
+	err = rswitch_gwca_hw_deinit(priv);
+	dev_err(&priv->pdev->dev, "halted (%d)\n", err);
+
+	return err;
+}
+
+static bool rswitch_rx(struct net_device *ndev, int *quota)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_gwca_chain *c = rdev->rx_chain;
+	int entry = c->cur % c->num_ring;
+	struct rswitch_ext_ts_desc *desc;
+	int limit, boguscnt, num, ret;
+	struct sk_buff *skb;
+	dma_addr_t dma_addr;
+	u16 pkt_len;
+
+	boguscnt = min_t(int, c->dirty + c->num_ring - c->cur, *quota);
+	limit = boguscnt;
+
+	desc = &c->ts_ring[entry];
+	while ((desc->die_dt & DT_MASK) != DT_FEMPTY) {
+		dma_rmb();
+		pkt_len = le16_to_cpu(desc->info_ds) & RX_DS;
+		if (--boguscnt < 0)
+			break;
+		skb = c->skb[entry];
+		c->skb[entry] = NULL;
+		dma_addr = le32_to_cpu(desc->dptrl) | ((__le64)le32_to_cpu(desc->dptrh) << 32);
+		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ, DMA_FROM_DEVICE);
+		skb_put(skb, pkt_len);
+		skb->protocol = eth_type_trans(skb, ndev);
+		netif_receive_skb(skb);
+		rdev->ndev->stats.rx_packets++;
+		rdev->ndev->stats.rx_bytes += pkt_len;
+
+		entry = (++c->cur) % c->num_ring;
+		desc = &c->ts_ring[entry];
+	}
+
+	num = c->cur - c->dirty;
+	ret = rswitch_gwca_chain_alloc_skb(c, c->dirty, num);
+	if (ret < 0)
+		goto err;
+	ret = rswitch_gwca_chain_ts_fill(ndev, c, c->dirty, num);
+	if (ret < 0)
+		goto err;
+	c->dirty += num;
+
+	*quota -= limit - (++boguscnt);
+
+	return boguscnt <= 0;
+
+err:
+	rswitch_gwca_halt(rdev->priv);
+
+	return 0;
+}
+
+static int rswitch_tx_free(struct net_device *ndev, bool free_txed_only)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_gwca_chain *c = rdev->tx_chain;
+	struct rswitch_ext_desc *desc;
+	dma_addr_t dma_addr;
+	struct sk_buff *skb;
+	int free_num = 0;
+	int entry, size;
+
+	for (; c->cur - c->dirty > 0; c->dirty++) {
+		entry = c->dirty % c->num_ring;
+		desc = &c->ring[entry];
+		if (free_txed_only && (desc->die_dt & DT_MASK) != DT_FEMPTY)
+			break;
+
+		dma_rmb();
+		size = le16_to_cpu(desc->info_ds) & TX_DS;
+		skb = c->skb[entry];
+		if (skb) {
+			dma_addr = le32_to_cpu(desc->dptrl) |
+				   ((__le64)le32_to_cpu(desc->dptrh) << 32);
+			dma_unmap_single(ndev->dev.parent, dma_addr,
+					 size, DMA_TO_DEVICE);
+			dev_kfree_skb_any(c->skb[entry]);
+			c->skb[entry] = NULL;
+			free_num++;
+		}
+		desc->die_dt = DT_EEMPTY;
+		rdev->ndev->stats.tx_packets++;
+		rdev->ndev->stats.tx_bytes += size;
+	}
+
+	return free_num;
+}
+
+static int rswitch_poll(struct napi_struct *napi, int budget)
+{
+	struct net_device *ndev = napi->dev;
+	struct rswitch_private *priv;
+	struct rswitch_device *rdev;
+	int quota = budget;
+
+	rdev = netdev_priv(ndev);
+	priv = rdev->priv;
+
+retry:
+	rswitch_tx_free(ndev, true);
+
+	if (rswitch_rx(ndev, &quota))
+		goto out;
+	else if (rdev->priv->gwca_halt)
+		goto err;
+	else if (rswitch_is_chain_rxed(rdev->rx_chain))
+		goto retry;
+
+	netif_wake_subqueue(ndev, 0);
+
+	napi_complete(napi);
+
+	rswitch_enadis_data_irq(priv, rdev->tx_chain->index, true);
+	rswitch_enadis_data_irq(priv, rdev->rx_chain->index, true);
+
+out:
+	return budget - quota;
+
+err:
+	napi_complete(napi);
+
+	return 0;
+}
+
+static void rswitch_queue_interrupt(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	if (napi_schedule_prep(&rdev->napi)) {
+		rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
+		rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
+		__napi_schedule(&rdev->napi);
+	}
+}
+
+static irqreturn_t rswitch_data_irq(struct rswitch_private *priv, u32 *dis)
+{
+	struct rswitch_gwca_chain *c;
+	int i, index, bit;
+
+	for (i = 0; i < priv->gwca.num_chains; i++) {
+		c = &priv->gwca.chains[i];
+		index = c->index / 32;
+		bit = BIT(c->index % 32);
+		if (!(dis[index] & bit))
+			continue;
+
+		rswitch_ack_data_irq(priv, c->index);
+		rswitch_queue_interrupt(c->ndev);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t rswitch_gwca_irq(int irq, void *dev_id)
+{
+	struct rswitch_private *priv = dev_id;
+	u32 dis[RSWITCH_NUM_IRQ_REGS];
+	irqreturn_t ret = IRQ_NONE;
+
+	rswitch_get_data_irq_status(priv, dis);
+
+	if (rswitch_is_any_data_irq(priv, dis, true) ||
+	    rswitch_is_any_data_irq(priv, dis, false))
+		ret = rswitch_data_irq(priv, dis);
+
+	return ret;
+}
+
+static int rswitch_gwca_request_irqs(struct rswitch_private *priv)
+{
+	char *resource_name, *irq_name;
+	int i, ret, irq;
+
+	for (i = 0; i < GWCA_NUM_IRQS; i++) {
+		resource_name = kasprintf(GFP_KERNEL, GWCA_IRQ_RESOURCE_NAME, i);
+		if (!resource_name)
+			return -ENOMEM;
+
+		irq = platform_get_irq_byname(priv->pdev, resource_name);
+		kfree(resource_name);
+		if (irq < 0)
+			return irq;
+
+		irq_name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL,
+					  GWCA_IRQ_NAME, i);
+		if (!irq_name)
+			return -ENOMEM;
+
+		ret = devm_request_irq(&priv->pdev->dev, irq, rswitch_gwca_irq,
+				       0, irq_name, priv);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* Ethernet TSN Agent block (ETHA) and Ethernet MAC IP block (RMAC) */
+static int rswitch_etha_change_mode(struct rswitch_etha *etha,
+				    enum rswitch_etha_mode mode)
+{
+	int ret;
+
+	if (!rswitch_agent_clock_is_enabled(etha->coma_addr, etha->index))
+		rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 1);
+
+	iowrite32(mode, etha->addr + EAMC);
+
+	ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, mode);
+
+	if (mode == EAMC_OPC_DISABLE)
+		rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 0);
+
+	return ret;
+}
+
+static void rswitch_etha_read_mac_address(struct rswitch_etha *etha)
+{
+	u32 mrmac0 = ioread32(etha->addr + MRMAC0);
+	u32 mrmac1 = ioread32(etha->addr + MRMAC1);
+	u8 *mac = &etha->mac_addr[0];
+
+	mac[0] = (mrmac0 >>  8) & 0xFF;
+	mac[1] = (mrmac0 >>  0) & 0xFF;
+	mac[2] = (mrmac1 >> 24) & 0xFF;
+	mac[3] = (mrmac1 >> 16) & 0xFF;
+	mac[4] = (mrmac1 >>  8) & 0xFF;
+	mac[5] = (mrmac1 >>  0) & 0xFF;
+}
+
+static void rswitch_etha_write_mac_address(struct rswitch_etha *etha, const u8 *mac)
+{
+	iowrite32((mac[0] << 8) | mac[1], etha->addr + MRMAC0);
+	iowrite32((mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5],
+		  etha->addr + MRMAC1);
+}
+
+static bool rswitch_etha_wait_link_verification(struct rswitch_etha *etha)
+{
+	iowrite32(MLVC_PLV, etha->addr + MLVC);
+
+	return rswitch_reg_wait(etha->addr, MLVC, MLVC_PLV, 0);
+}
+
+static void rswitch_rmac_setting(struct rswitch_etha *etha, const u8 *mac)
+{
+	u32 val;
+
+	rswitch_etha_write_mac_address(etha, mac);
+
+	switch (etha->speed) {
+	case 100:
+		val = MPIC_LSC_100M;
+		break;
+	case 1000:
+		val = MPIC_LSC_1G;
+		break;
+	case 2500:
+		val = MPIC_LSC_2_5G;
+		break;
+	default:
+		return;
+	}
+
+	iowrite32(MPIC_PIS_GMII | val, etha->addr + MPIC);
+}
+
+static void rswitch_etha_enable_mii(struct rswitch_etha *etha)
+{
+	rswitch_modify(etha->addr, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK,
+		       MPIC_PSMCS(0x05) | MPIC_PSMHT(0x06));
+	rswitch_modify(etha->addr, MPSM, 0, MPSM_MFF_C45);
+}
+
+static int rswitch_etha_hw_init(struct rswitch_etha *etha, const u8 *mac)
+{
+	int err;
+
+	err = rswitch_etha_change_mode(etha, EAMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	err = rswitch_etha_change_mode(etha, EAMC_OPC_CONFIG);
+	if (err < 0)
+		return err;
+
+	iowrite32(EAVCC_VEM_SC_TAG, etha->addr + EAVCC);
+	rswitch_rmac_setting(etha, mac);
+	rswitch_etha_enable_mii(etha);
+
+	err = rswitch_etha_wait_link_verification(etha);
+	if (err < 0)
+		return err;
+
+	err = rswitch_etha_change_mode(etha, EAMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+
+	return rswitch_etha_change_mode(etha, EAMC_OPC_OPERATION);
+}
+
+static int rswitch_etha_set_access(struct rswitch_etha *etha, bool read,
+				   int phyad, int devad, int regad, int data)
+{
+	int pop = read ? MDIO_READ_C45 : MDIO_WRITE_C45;
+	u32 val;
+	int ret;
+
+	if (devad == 0xffffffff)
+		return -ENODEV;
+
+	writel(MMIS1_CLEAR_FLAGS, etha->addr + MMIS1);
+
+	val = MPSM_PSME | MPSM_MFF_C45;
+	iowrite32((regad << 16) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
+
+	ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
+	if (ret)
+		return ret;
+
+	rswitch_modify(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
+
+	if (read) {
+		writel((pop << 13) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
+
+		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
+		if (ret)
+			return ret;
+
+		ret = (ioread32(etha->addr + MPSM) & MPSM_PRD_MASK) >> 16;
+
+		rswitch_modify(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
+	} else {
+		iowrite32((data << 16) | (pop << 13) | (devad << 8) | (phyad << 3) | val,
+			  etha->addr + MPSM);
+
+		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PWACS, MMIS1_PWACS);
+	}
+
+	return ret;
+}
+
+static int rswitch_etha_mii_read(struct mii_bus *bus, int addr, int regnum)
+{
+	struct rswitch_etha *etha = bus->priv;
+	int mode, devad, regad;
+
+	mode = regnum & MII_ADDR_C45;
+	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
+	regad = regnum & MII_REGADDR_C45_MASK;
+
+	/* Not support Clause 22 access method */
+	if (!mode)
+		return -EOPNOTSUPP;
+
+	return rswitch_etha_set_access(etha, true, addr, devad, regad, 0);
+}
+
+static int rswitch_etha_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+	struct rswitch_etha *etha = bus->priv;
+	int mode, devad, regad;
+
+	mode = regnum & MII_ADDR_C45;
+	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
+	regad = regnum & MII_REGADDR_C45_MASK;
+
+	/* Not support Clause 22 access method */
+	if (!mode)
+		return -EOPNOTSUPP;
+
+	return rswitch_etha_set_access(etha, false, addr, devad, regad, val);
+}
+
+/* Call of_node_put(port) after done */
+static struct device_node *rswitch_get_port_node(struct rswitch_device *rdev)
+{
+	struct device_node *ports, *port;
+	int err = 0;
+	u32 index;
+
+	ports = of_get_child_by_name(rdev->ndev->dev.parent->of_node,
+				     "ethernet-ports");
+	if (!ports)
+		return NULL;
+
+	for_each_child_of_node(ports, port) {
+		err = of_property_read_u32(port, "reg", &index);
+		if (err < 0) {
+			port = NULL;
+			goto out;
+		}
+		if (index == rdev->etha->index)
+			break;
+	}
+
+out:
+	of_node_put(ports);
+
+	return port;
+}
+
+/* Call of_node_put(phy) after done */
+static struct device_node *rswitch_get_phy_node(struct rswitch_device *rdev)
+{
+	struct device_node *port, *phy = NULL;
+
+	port = rswitch_get_port_node(rdev);
+	if (!port)
+		return NULL;
+
+	phy = of_parse_phandle(port, "phy-handle", 0);
+	of_node_put(port);
+
+	return phy;
+}
+
+/* Call of_node_put(mdio) after done */
+static struct device_node *rswitch_get_mdio_node(struct rswitch_device *rdev)
+{
+	struct device_node *port, *mdio;
+
+	port = rswitch_get_port_node(rdev);
+	if (!port)
+		return NULL;
+
+	mdio = of_get_child_by_name(port, "mdio");
+	of_node_put(port);
+
+	return mdio;
+}
+
+static int rswitch_etha_get_params(struct rswitch_device *rdev)
+{
+	struct device_node *port;
+	int err;
+
+	port = rswitch_get_port_node(rdev);
+	if (!port)
+		return -ENODEV;
+
+	err = of_get_phy_mode(port, &rdev->etha->phy_interface);
+	of_node_put(port);
+
+	switch (rdev->etha->phy_interface) {
+	case PHY_INTERFACE_MODE_MII:
+		rdev->etha->speed = SPEED_100;
+		break;
+	case PHY_INTERFACE_MODE_SGMII:
+		rdev->etha->speed = SPEED_1000;
+		break;
+	case PHY_INTERFACE_MODE_USXGMII:
+		rdev->etha->speed = SPEED_2500;
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+
+	return err;
+}
+
+static int rswitch_mii_register(struct rswitch_device *rdev)
+{
+	struct device_node *mdio_np;
+	struct mii_bus *mii_bus;
+	int err;
+
+	mii_bus = mdiobus_alloc();
+	if (!mii_bus)
+		return -ENOMEM;
+
+	mii_bus->name = "rswitch_mii";
+	sprintf(mii_bus->id, "etha%d", rdev->etha->index);
+	mii_bus->priv = rdev->etha;
+	mii_bus->read = rswitch_etha_mii_read;
+	mii_bus->write = rswitch_etha_mii_write;
+	mii_bus->parent = &rdev->ndev->dev;
+
+	mdio_np = rswitch_get_mdio_node(rdev);
+	err = of_mdiobus_register(mii_bus, mdio_np);
+	if (err < 0) {
+		mdiobus_free(mii_bus);
+		goto out;
+	}
+
+	rdev->etha->mii = mii_bus;
+
+out:
+	of_node_put(mdio_np);
+
+	return err;
+}
+
+static void rswitch_mii_unregister(struct rswitch_device *rdev)
+{
+	if (rdev->etha && rdev->etha->mii) {
+		mdiobus_unregister(rdev->etha->mii);
+		mdiobus_free(rdev->etha->mii);
+		rdev->etha->mii = NULL;
+	}
+}
+
+static void rswitch_adjust_link(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct phy_device *phydev = ndev->phydev;
+
+	/* Current hardware cannot change speed at runtime */
+	if (phydev->link != rdev->etha->link) {
+		phy_print_status(phydev);
+		rdev->etha->link = phydev->link;
+	}
+}
+
+static void rswitch_phy_remove_link_mode(struct rswitch_device *rdev,
+					 struct phy_device *phydev)
+{
+	/* Current hardware cannot change speed at runtime */
+	switch (rdev->etha->speed) {
+	case SPEED_2500:
+		phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Full_BIT);
+		fallthrough;
+	case SPEED_1000:
+		phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Full_BIT);
+		fallthrough;
+	case SPEED_100:
+		phy_set_max_speed(phydev, rdev->etha->speed);
+		break;
+	default:
+		break;
+	}
+}
+
+static int rswitch_phy_init(struct rswitch_device *rdev, struct device_node *phy)
+{
+	struct phy_device *phydev;
+	int err = 0;
+
+	phydev = of_phy_connect(rdev->ndev, phy, rswitch_adjust_link, 0,
+				rdev->etha->phy_interface);
+	if (!phydev) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	/* The hardware supports 100, 1000 and 2500Mbps with full-duplex */
+	phy_set_max_speed(phydev, SPEED_2500);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Full_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
+
+	rswitch_phy_remove_link_mode(rdev, phydev);
+
+	phy_attached_info(phydev);
+
+out:
+	return err;
+}
+
+static void rswitch_phy_deinit(struct rswitch_device *rdev)
+{
+	if (rdev->ndev->phydev) {
+		phy_disconnect(rdev->ndev->phydev);
+		rdev->ndev->phydev = NULL;
+	}
+}
+
+static int rswitch_serdes_init(struct rswitch_device *rdev)
+{
+	struct device_node *port = rswitch_get_port_node(rdev);
+	struct phy *serdes;
+	int err;
+
+	serdes = devm_of_phy_get(&rdev->priv->pdev->dev, port, NULL);
+	of_node_put(port);
+	if (IS_ERR(serdes))
+		return PTR_ERR(serdes);
+
+	err = phy_set_mode_ext(serdes, PHY_MODE_ETHERNET,
+			       rdev->etha->phy_interface);
+	if (err < 0)
+		return err;
+	err = phy_set_speed(serdes, rdev->etha->speed);
+	if (err < 0)
+		return err;
+
+	return phy_init(serdes);
+}
+
+static int rswitch_open(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct device_node *phy;
+	int err = 0;
+
+	if (rdev->etha) {
+		if (!rdev->etha->operated) {
+			err = rswitch_etha_hw_init(rdev->etha, ndev->dev_addr);
+			if (err < 0)
+				return err;
+			rdev->etha->operated = true;
+		}
+
+		err = rswitch_mii_register(rdev);
+		if (err < 0)
+			return err;
+		phy = rswitch_get_phy_node(rdev);
+		if (!phy) {
+			err = -ENODEV;
+			goto err_phy_node;
+		}
+		err = rswitch_phy_init(rdev, phy);
+		of_node_put(phy);
+		if (err < 0)
+			goto err_phy_init;
+
+		phy_start(ndev->phydev);
+
+		err = rswitch_serdes_init(rdev);
+		if (err < 0)
+			goto err_serdes_init;
+	}
+
+	napi_enable(&rdev->napi);
+	netif_start_queue(ndev);
+
+	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, true);
+	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, true);
+
+	return err;
+
+err_serdes_init:
+	phy_stop(ndev->phydev);
+	rswitch_phy_deinit(rdev);
+
+err_phy_init:
+err_phy_node:
+	rswitch_mii_unregister(rdev);
+
+	return err;
+};
+
+static int rswitch_stop(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	netif_tx_stop_all_queues(ndev);
+
+	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
+	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
+
+	if (rdev->etha) {
+		phy_stop(ndev->phydev);
+		rswitch_phy_deinit(rdev);
+		rswitch_mii_unregister(rdev);
+	}
+
+	napi_disable(&rdev->napi);
+
+	return 0;
+};
+
+static int rswitch_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_gwca_chain *c = rdev->tx_chain;
+	struct rswitch_ext_desc *desc;
+	int ret = NETDEV_TX_OK;
+	dma_addr_t dma_addr;
+	int entry;
+
+	if (c->cur - c->dirty > c->num_ring - 1) {
+		netif_stop_subqueue(ndev, 0);
+		return ret;
+	}
+
+	if (skb_put_padto(skb, ETH_ZLEN))
+		return ret;
+
+	dma_addr = dma_map_single(ndev->dev.parent, skb->data, skb->len, DMA_TO_DEVICE);
+	if (dma_mapping_error(ndev->dev.parent, dma_addr)) {
+		dev_kfree_skb_any(skb);
+		return ret;
+	}
+
+	entry = c->cur % c->num_ring;
+	c->skb[entry] = skb;
+	desc = &c->ring[entry];
+	desc->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
+	desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
+	desc->info_ds = cpu_to_le16(skb->len);
+
+	dma_wmb();
+
+	desc->die_dt = DT_FSINGLE | DIE;
+	wmb();	/* c->cur must be incremented after die_dt was set */
+
+	c->cur++;
+	rswitch_modify(rdev->addr, GWTRC(c->index), 0, BIT(c->index % 32));
+
+	return ret;
+}
+
+static struct net_device_stats *rswitch_get_stats(struct net_device *ndev)
+{
+	return &ndev->stats;
+}
+
+static int rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
+{
+	struct phy_device *phydev = ndev->phydev;
+
+	if (!netif_running(ndev))
+		return -EINVAL;
+	if (!phydev)
+		return -ENODEV;
+
+	return phy_mii_ioctl(phydev, req, cmd);
+}
+
+static const struct net_device_ops rswitch_netdev_ops = {
+	.ndo_open = rswitch_open,
+	.ndo_stop = rswitch_stop,
+	.ndo_start_xmit = rswitch_start_xmit,
+	.ndo_get_stats = rswitch_get_stats,
+	.ndo_eth_ioctl = rswitch_eth_ioctl,
+	.ndo_validate_addr = eth_validate_addr,
+	.ndo_set_mac_address = eth_mac_addr,
+};
+
+static const struct of_device_id renesas_eth_sw_of_table[] = {
+	{ .compatible = "renesas,r8a779f0-ether-switch", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, renesas_eth_sw_of_table);
+
+static void rswitch_etha_init(struct rswitch_private *priv, int index)
+{
+	struct rswitch_etha *etha = &priv->etha[index];
+
+	memset(etha, 0, sizeof(*etha));
+	etha->index = index;
+	etha->addr = priv->addr + RSWITCH_ETHA_OFFSET + index * RSWITCH_ETHA_SIZE;
+	etha->coma_addr = priv->addr;
+}
+
+static int rswitch_device_alloc(struct rswitch_private *priv, int index)
+{
+	struct platform_device *pdev = priv->pdev;
+	struct rswitch_device *rdev;
+	struct net_device *ndev;
+	int err;
+
+	ndev = alloc_etherdev_mqs(sizeof(struct rswitch_device), 1, 1);
+	if (!ndev)
+		return -ENOMEM;
+
+	SET_NETDEV_DEV(ndev, &pdev->dev);
+	ether_setup(ndev);
+
+	rdev = netdev_priv(ndev);
+	rdev->ndev = ndev;
+	rdev->priv = priv;
+	priv->rdev[index] = rdev;
+	/* TODO: netdev instance : ETHA port is 1:1 mapping */
+	if (index < RSWITCH_MAX_NUM_ETHA) {
+		rdev->port = index;
+		rdev->etha = &priv->etha[index];
+	} else {
+		rdev->port = -1;
+		rdev->etha = NULL;
+	}
+	rdev->addr = priv->addr;
+
+	ndev->base_addr = (unsigned long)rdev->addr;
+	snprintf(ndev->name, IFNAMSIZ, "tsn%d", index);
+	ndev->netdev_ops = &rswitch_netdev_ops;
+
+	netif_napi_add(ndev, &rdev->napi, rswitch_poll, 64);
+
+	err = of_get_ethdev_address(pdev->dev.of_node, ndev);
+	if (err) {
+		if (rdev->etha && is_valid_ether_addr(rdev->etha->mac_addr))
+			eth_hw_addr_set(ndev, rdev->etha->mac_addr);
+		else
+			eth_hw_addr_random(ndev);
+	}
+
+	err = rswitch_etha_get_params(rdev);
+	if (err < 0)
+		goto out_get_params;
+
+	if (rdev->priv->gwca.speed < rdev->etha->speed)
+		rdev->priv->gwca.speed = rdev->etha->speed;
+
+	err = rswitch_rxdmac_alloc(ndev);
+	if (err < 0)
+		goto out_rxdmac;
+
+	err = rswitch_txdmac_alloc(ndev);
+	if (err < 0)
+		goto out_txdmac;
+
+	return 0;
+
+out_txdmac:
+	rswitch_rxdmac_free(ndev);
+
+out_rxdmac:
+out_get_params:
+	netif_napi_del(&rdev->napi);
+	free_netdev(ndev);
+
+	return err;
+}
+
+static void rswitch_device_free(struct rswitch_private *priv, int index)
+{
+	struct rswitch_device *rdev = priv->rdev[index];
+	struct net_device *ndev = rdev->ndev;
+
+	rswitch_txdmac_free(ndev);
+	rswitch_rxdmac_free(ndev);
+	netif_napi_del(&rdev->napi);
+	free_netdev(ndev);
+}
+
+static int rswitch_init(struct rswitch_private *priv)
+{
+	int i, err;
+
+	for (i = 0; i < RSWITCH_NUM_ETHA; i++)
+		rswitch_etha_init(priv, i);
+
+	rswitch_clock_enable(priv);
+	for (i = 0; i < RSWITCH_NUM_ETHA; i++)
+		rswitch_etha_read_mac_address(&priv->etha[i]);
+
+	rswitch_reset(priv);
+
+	rswitch_clock_enable(priv);
+	rswitch_top_init(priv);
+	err = rswitch_bpool_config(priv);
+	if (err < 0)
+		return err;
+
+	err = rswitch_gwca_desc_alloc(priv);
+	if (err < 0)
+		return -ENOMEM;
+
+	for (i = 0; i < RSWITCH_NUM_NDEV; i++) {
+		err = rswitch_device_alloc(priv, i);
+		if (err < 0) {
+			for (i--; i >= 0; i--)
+				rswitch_device_free(priv, i);
+			goto err_device_alloc;
+		}
+	}
+
+	rswitch_fwd_init(priv);
+
+	err = rswitch_gwca_request_irqs(priv);
+	if (err < 0)
+		goto err_gwca_request_irq;
+
+	err = rswitch_gwca_hw_init(priv);
+	if (err < 0)
+		goto err_gwca_hw_init;
+
+	for (i = 0; i < RSWITCH_NUM_NDEV; i++) {
+		err = register_netdev(priv->rdev[i]->ndev);
+		if (err) {
+			for (i--; i >= 0; i--)
+				unregister_netdev(priv->rdev[i]->ndev);
+			goto err_register_netdev;
+		}
+
+		netdev_info(priv->rdev[i]->ndev, "MAC address %pMn",
+			    priv->rdev[i]->ndev->dev_addr);
+	}
+
+	return 0;
+
+err_register_netdev:
+	rswitch_gwca_hw_deinit(priv);
+
+err_gwca_hw_init:
+err_gwca_request_irq:
+	for (i = 0; i < RSWITCH_NUM_NDEV; i++)
+		rswitch_device_free(priv, i);
+
+err_device_alloc:
+	rswitch_gwca_desc_free(priv);
+
+	return err;
+}
+
+static int renesas_eth_sw_probe(struct platform_device *pdev)
+{
+	struct rswitch_private *priv;
+	struct resource *res;
+	int ret;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "secure_base");
+	if (!res) {
+		dev_err(&pdev->dev, "invalid resource\n");
+		return -EINVAL;
+	}
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->tsn_clk = devm_clk_get(&pdev->dev, "tsn");
+	if (IS_ERR(priv->tsn_clk)) {
+		dev_err(&pdev->dev, "Failed to get tsn clock: %ld\n", PTR_ERR(priv->tsn_clk));
+		return PTR_ERR(priv->tsn_clk);
+	}
+
+	platform_set_drvdata(pdev, priv);
+	priv->pdev = pdev;
+	priv->addr = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->addr))
+		return PTR_ERR(priv->addr);
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40));
+	if (ret < 0) {
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+		if (ret < 0)
+			return ret;
+	}
+
+	priv->gwca.index = AGENT_INDEX_GWCA;
+	priv->gwca.num_chains = min(RSWITCH_NUM_NDEV * NUM_CHAINS_PER_NDEV,
+				    RSWITCH_MAX_NUM_CHAINS);
+	priv->gwca.chains = devm_kcalloc(&pdev->dev, priv->gwca.num_chains,
+					 sizeof(*priv->gwca.chains), GFP_KERNEL);
+	if (!priv->gwca.chains)
+		return -ENOMEM;
+
+	pm_runtime_enable(&pdev->dev);
+	pm_runtime_get_sync(&pdev->dev);
+	clk_prepare(priv->tsn_clk);
+	clk_enable(priv->tsn_clk);
+
+	ret = rswitch_init(priv);
+
+	device_set_wakeup_capable(&pdev->dev, 1);
+
+	return ret;
+}
+
+static void rswitch_deinit(struct rswitch_private *priv)
+{
+	int i;
+
+	rswitch_gwca_hw_deinit(priv);
+
+	for (i = 0; i < RSWITCH_NUM_NDEV; i++) {
+		struct rswitch_device *rdev = priv->rdev[i];
+
+		rswitch_phy_deinit(rdev);
+		rswitch_mii_unregister(rdev);
+		unregister_netdev(rdev->ndev);
+		rswitch_device_free(priv, i);
+	}
+
+	rswitch_gwca_desc_free(priv);
+
+	rswitch_clock_disable(priv);
+}
+
+static int renesas_eth_sw_remove(struct platform_device *pdev)
+{
+	struct rswitch_private *priv = platform_get_drvdata(pdev);
+
+	rswitch_deinit(priv);
+
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	clk_disable(priv->tsn_clk);
+
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver renesas_eth_sw_driver_platform = {
+	.probe = renesas_eth_sw_probe,
+	.remove = renesas_eth_sw_remove,
+	.driver = {
+		.name = "renesas_eth_sw",
+		.of_match_table = renesas_eth_sw_of_table,
+	}
+};
+module_platform_driver(renesas_eth_sw_driver_platform);
+MODULE_AUTHOR("Yoshihiro Shimoda");
+MODULE_DESCRIPTION("Renesas Ethernet Switch device driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
new file mode 100644
index 000000000000..3dd62f82abb1
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -0,0 +1,965 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Renesas Ethernet Switch device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#ifndef __RSWITCH_H__
+#define __RSWITCH_H__
+
+#include <linux/platform_device.h>
+
+#define RSWITCH_NUM_HW		5
+#define RSWITCH_MAX_NUM_ETHA	3
+#define RSWITCH_MAX_NUM_CHAINS	128
+
+/* TODO: hardcoded number of ndev and etha for now */
+#define RSWITCH_NUM_NDEV	1
+#define RSWITCH_NUM_ETHA	1
+
+#define TX_RING_SIZE		1024
+#define RX_RING_SIZE		1024
+
+#define PKT_BUF_SZ		1584
+#define RSWITCH_ALIGN		128
+#define RSWITCH_MAX_CTAG_PCP	7
+
+#define RSWITCH_TIMEOUT_US	100000
+
+#define RSWITCH_TOP_OFFSET	0x00008000
+#define RSWITCH_COMA_OFFSET	0x00009000
+#define RSWITCH_ETHA_OFFSET	0x0000a000	/* with RMAC */
+#define RSWITCH_ETHA_SIZE	0x00002000	/* with RMAC */
+#define RSWITCH_GWCA0_OFFSET	0x00010000
+#define RSWITCH_GWCA1_OFFSET	0x00012000
+
+/* TODO: hardcoded GWCA settings for now */
+#define GWCA_IRQ_RESOURCE_NAME	"gwca0_rxtx%d"
+#define GWCA_IRQ_NAME		"rswitch: gwca0_rxtx%d"
+#define GWCA_NUM_IRQS		8
+#define GWCA_INDEX		0
+#define AGENT_INDEX_GWCA	3
+#define GWRO			RSWITCH_GWCA0_OFFSET
+
+#define FWRO	0
+#define TPRO	RSWITCH_TOP_OFFSET
+#define CARO	RSWITCH_COMA_OFFSET
+#define TARO	0
+#define RMRO	0x1000
+enum rswitch_reg {
+	FWGC		= FWRO + 0x0000,
+	FWTTC0		= FWRO + 0x0010,
+	FWTTC1		= FWRO + 0x0014,
+	FWLBMC		= FWRO + 0x0018,
+	FWCEPTC		= FWRO + 0x0020,
+	FWCEPRC0	= FWRO + 0x0024,
+	FWCEPRC1	= FWRO + 0x0028,
+	FWCEPRC2	= FWRO + 0x002c,
+	FWCLPTC		= FWRO + 0x0030,
+	FWCLPRC		= FWRO + 0x0034,
+	FWCMPTC		= FWRO + 0x0040,
+	FWEMPTC		= FWRO + 0x0044,
+	FWSDMPTC	= FWRO + 0x0050,
+	FWSDMPVC	= FWRO + 0x0054,
+	FWLBWMC0	= FWRO + 0x0080,
+	FWPC00		= FWRO + 0x0100,
+	FWPC10		= FWRO + 0x0104,
+	FWPC20		= FWRO + 0x0108,
+	FWCTGC00	= FWRO + 0x0400,
+	FWCTGC10	= FWRO + 0x0404,
+	FWCTTC00	= FWRO + 0x0408,
+	FWCTTC10	= FWRO + 0x040c,
+	FWCTTC200	= FWRO + 0x0410,
+	FWCTSC00	= FWRO + 0x0420,
+	FWCTSC10	= FWRO + 0x0424,
+	FWCTSC20	= FWRO + 0x0428,
+	FWCTSC30	= FWRO + 0x042c,
+	FWCTSC40	= FWRO + 0x0430,
+	FWTWBFC0	= FWRO + 0x1000,
+	FWTWBFVC0	= FWRO + 0x1004,
+	FWTHBFC0	= FWRO + 0x1400,
+	FWTHBFV0C0	= FWRO + 0x1404,
+	FWTHBFV1C0	= FWRO + 0x1408,
+	FWFOBFC0	= FWRO + 0x1800,
+	FWFOBFV0C0	= FWRO + 0x1804,
+	FWFOBFV1C0	= FWRO + 0x1808,
+	FWRFC0		= FWRO + 0x1c00,
+	FWRFVC0		= FWRO + 0x1c04,
+	FWCFC0		= FWRO + 0x2000,
+	FWCFMC00	= FWRO + 0x2004,
+	FWIP4SC		= FWRO + 0x4008,
+	FWIP6SC		= FWRO + 0x4018,
+	FWIP6OC		= FWRO + 0x401c,
+	FWL2SC		= FWRO + 0x4020,
+	FWSFHEC		= FWRO + 0x4030,
+	FWSHCR0		= FWRO + 0x4040,
+	FWSHCR1		= FWRO + 0x4044,
+	FWSHCR2		= FWRO + 0x4048,
+	FWSHCR3		= FWRO + 0x404c,
+	FWSHCR4		= FWRO + 0x4050,
+	FWSHCR5		= FWRO + 0x4054,
+	FWSHCR6		= FWRO + 0x4058,
+	FWSHCR7		= FWRO + 0x405c,
+	FWSHCR8		= FWRO + 0x4060,
+	FWSHCR9		= FWRO + 0x4064,
+	FWSHCR10	= FWRO + 0x4068,
+	FWSHCR11	= FWRO + 0x406c,
+	FWSHCR12	= FWRO + 0x4070,
+	FWSHCR13	= FWRO + 0x4074,
+	FWSHCRR		= FWRO + 0x4078,
+	FWLTHHEC	= FWRO + 0x4090,
+	FWLTHHC		= FWRO + 0x4094,
+	FWLTHTL0	= FWRO + 0x40a0,
+	FWLTHTL1	= FWRO + 0x40a4,
+	FWLTHTL2	= FWRO + 0x40a8,
+	FWLTHTL3	= FWRO + 0x40ac,
+	FWLTHTL4	= FWRO + 0x40b0,
+	FWLTHTL5	= FWRO + 0x40b4,
+	FWLTHTL6	= FWRO + 0x40b8,
+	FWLTHTL7	= FWRO + 0x40bc,
+	FWLTHTL80	= FWRO + 0x40c0,
+	FWLTHTL9	= FWRO + 0x40d0,
+	FWLTHTLR	= FWRO + 0x40d4,
+	FWLTHTIM	= FWRO + 0x40e0,
+	FWLTHTEM	= FWRO + 0x40e4,
+	FWLTHTS0	= FWRO + 0x4100,
+	FWLTHTS1	= FWRO + 0x4104,
+	FWLTHTS2	= FWRO + 0x4108,
+	FWLTHTS3	= FWRO + 0x410c,
+	FWLTHTS4	= FWRO + 0x4110,
+	FWLTHTSR0	= FWRO + 0x4120,
+	FWLTHTSR1	= FWRO + 0x4124,
+	FWLTHTSR2	= FWRO + 0x4128,
+	FWLTHTSR3	= FWRO + 0x412c,
+	FWLTHTSR40	= FWRO + 0x4130,
+	FWLTHTSR5	= FWRO + 0x4140,
+	FWLTHTR		= FWRO + 0x4150,
+	FWLTHTRR0	= FWRO + 0x4154,
+	FWLTHTRR1	= FWRO + 0x4158,
+	FWLTHTRR2	= FWRO + 0x415c,
+	FWLTHTRR3	= FWRO + 0x4160,
+	FWLTHTRR4	= FWRO + 0x4164,
+	FWLTHTRR5	= FWRO + 0x4168,
+	FWLTHTRR6	= FWRO + 0x416c,
+	FWLTHTRR7	= FWRO + 0x4170,
+	FWLTHTRR8	= FWRO + 0x4174,
+	FWLTHTRR9	= FWRO + 0x4180,
+	FWLTHTRR10	= FWRO + 0x4190,
+	FWIPHEC		= FWRO + 0x4214,
+	FWIPHC		= FWRO + 0x4218,
+	FWIPTL0		= FWRO + 0x4220,
+	FWIPTL1		= FWRO + 0x4224,
+	FWIPTL2		= FWRO + 0x4228,
+	FWIPTL3		= FWRO + 0x422c,
+	FWIPTL4		= FWRO + 0x4230,
+	FWIPTL5		= FWRO + 0x4234,
+	FWIPTL6		= FWRO + 0x4238,
+	FWIPTL7		= FWRO + 0x4240,
+	FWIPTL8		= FWRO + 0x4250,
+	FWIPTLR		= FWRO + 0x4254,
+	FWIPTIM		= FWRO + 0x4260,
+	FWIPTEM		= FWRO + 0x4264,
+	FWIPTS0		= FWRO + 0x4270,
+	FWIPTS1		= FWRO + 0x4274,
+	FWIPTS2		= FWRO + 0x4278,
+	FWIPTS3		= FWRO + 0x427c,
+	FWIPTS4		= FWRO + 0x4280,
+	FWIPTSR0	= FWRO + 0x4284,
+	FWIPTSR1	= FWRO + 0x4288,
+	FWIPTSR2	= FWRO + 0x428c,
+	FWIPTSR3	= FWRO + 0x4290,
+	FWIPTSR4	= FWRO + 0x42a0,
+	FWIPTR		= FWRO + 0x42b0,
+	FWIPTRR0	= FWRO + 0x42b4,
+	FWIPTRR1	= FWRO + 0x42b8,
+	FWIPTRR2	= FWRO + 0x42bc,
+	FWIPTRR3	= FWRO + 0x42c0,
+	FWIPTRR4	= FWRO + 0x42c4,
+	FWIPTRR5	= FWRO + 0x42c8,
+	FWIPTRR6	= FWRO + 0x42cc,
+	FWIPTRR7	= FWRO + 0x42d0,
+	FWIPTRR8	= FWRO + 0x42e0,
+	FWIPTRR9	= FWRO + 0x42f0,
+	FWIPHLEC	= FWRO + 0x4300,
+	FWIPAGUSPC	= FWRO + 0x4500,
+	FWIPAGC		= FWRO + 0x4504,
+	FWIPAGM0	= FWRO + 0x4510,
+	FWIPAGM1	= FWRO + 0x4514,
+	FWIPAGM2	= FWRO + 0x4518,
+	FWIPAGM3	= FWRO + 0x451c,
+	FWIPAGM4	= FWRO + 0x4520,
+	FWMACHEC	= FWRO + 0x4620,
+	FWMACHC		= FWRO + 0x4624,
+	FWMACTL0	= FWRO + 0x4630,
+	FWMACTL1	= FWRO + 0x4634,
+	FWMACTL2	= FWRO + 0x4638,
+	FWMACTL3	= FWRO + 0x463c,
+	FWMACTL4	= FWRO + 0x4640,
+	FWMACTL5	= FWRO + 0x4650,
+	FWMACTLR	= FWRO + 0x4654,
+	FWMACTIM	= FWRO + 0x4660,
+	FWMACTEM	= FWRO + 0x4664,
+	FWMACTS0	= FWRO + 0x4670,
+	FWMACTS1	= FWRO + 0x4674,
+	FWMACTSR0	= FWRO + 0x4678,
+	FWMACTSR1	= FWRO + 0x467c,
+	FWMACTSR2	= FWRO + 0x4680,
+	FWMACTSR3	= FWRO + 0x4690,
+	FWMACTR		= FWRO + 0x46a0,
+	FWMACTRR0	= FWRO + 0x46a4,
+	FWMACTRR1	= FWRO + 0x46a8,
+	FWMACTRR2	= FWRO + 0x46ac,
+	FWMACTRR3	= FWRO + 0x46b0,
+	FWMACTRR4	= FWRO + 0x46b4,
+	FWMACTRR5	= FWRO + 0x46c0,
+	FWMACTRR6	= FWRO + 0x46d0,
+	FWMACHLEC	= FWRO + 0x4700,
+	FWMACAGUSPC	= FWRO + 0x4880,
+	FWMACAGC	= FWRO + 0x4884,
+	FWMACAGM0	= FWRO + 0x4888,
+	FWMACAGM1	= FWRO + 0x488c,
+	FWVLANTEC	= FWRO + 0x4900,
+	FWVLANTL0	= FWRO + 0x4910,
+	FWVLANTL1	= FWRO + 0x4914,
+	FWVLANTL2	= FWRO + 0x4918,
+	FWVLANTL3	= FWRO + 0x4920,
+	FWVLANTL4	= FWRO + 0x4930,
+	FWVLANTLR	= FWRO + 0x4934,
+	FWVLANTIM	= FWRO + 0x4940,
+	FWVLANTEM	= FWRO + 0x4944,
+	FWVLANTS	= FWRO + 0x4950,
+	FWVLANTSR0	= FWRO + 0x4954,
+	FWVLANTSR1	= FWRO + 0x4958,
+	FWVLANTSR2	= FWRO + 0x4960,
+	FWVLANTSR3	= FWRO + 0x4970,
+	FWPBFC0		= FWRO + 0x4a00,
+	FWPBFCSDC00	= FWRO + 0x4a04,
+	FWL23URL0	= FWRO + 0x4e00,
+	FWL23URL1	= FWRO + 0x4e04,
+	FWL23URL2	= FWRO + 0x4e08,
+	FWL23URL3	= FWRO + 0x4e0c,
+	FWL23URLR	= FWRO + 0x4e10,
+	FWL23UTIM	= FWRO + 0x4e20,
+	FWL23URR	= FWRO + 0x4e30,
+	FWL23URRR0	= FWRO + 0x4e34,
+	FWL23URRR1	= FWRO + 0x4e38,
+	FWL23URRR2	= FWRO + 0x4e3c,
+	FWL23URRR3	= FWRO + 0x4e40,
+	FWL23URMC0	= FWRO + 0x4f00,
+	FWPMFGC0	= FWRO + 0x5000,
+	FWPGFC0		= FWRO + 0x5100,
+	FWPGFIGSC0	= FWRO + 0x5104,
+	FWPGFENC0	= FWRO + 0x5108,
+	FWPGFENM0	= FWRO + 0x510c,
+	FWPGFCSTC00	= FWRO + 0x5110,
+	FWPGFCSTC10	= FWRO + 0x5114,
+	FWPGFCSTM00	= FWRO + 0x5118,
+	FWPGFCSTM10	= FWRO + 0x511c,
+	FWPGFCTC0	= FWRO + 0x5120,
+	FWPGFCTM0	= FWRO + 0x5124,
+	FWPGFHCC0	= FWRO + 0x5128,
+	FWPGFSM0	= FWRO + 0x512c,
+	FWPGFGC0	= FWRO + 0x5130,
+	FWPGFGL0	= FWRO + 0x5500,
+	FWPGFGL1	= FWRO + 0x5504,
+	FWPGFGLR	= FWRO + 0x5518,
+	FWPGFGR		= FWRO + 0x5510,
+	FWPGFGRR0	= FWRO + 0x5514,
+	FWPGFGRR1	= FWRO + 0x5518,
+	FWPGFRIM	= FWRO + 0x5520,
+	FWPMTRFC0	= FWRO + 0x5600,
+	FWPMTRCBSC0	= FWRO + 0x5604,
+	FWPMTRC0RC0	= FWRO + 0x5608,
+	FWPMTREBSC0	= FWRO + 0x560c,
+	FWPMTREIRC0	= FWRO + 0x5610,
+	FWPMTRFM0	= FWRO + 0x5614,
+	FWFTL0		= FWRO + 0x6000,
+	FWFTL1		= FWRO + 0x6004,
+	FWFTLR		= FWRO + 0x6008,
+	FWFTOC		= FWRO + 0x6010,
+	FWFTOPC		= FWRO + 0x6014,
+	FWFTIM		= FWRO + 0x6020,
+	FWFTR		= FWRO + 0x6030,
+	FWFTRR0		= FWRO + 0x6034,
+	FWFTRR1		= FWRO + 0x6038,
+	FWFTRR2		= FWRO + 0x603c,
+	FWSEQNGC0	= FWRO + 0x6100,
+	FWSEQNGM0	= FWRO + 0x6104,
+	FWSEQNRC	= FWRO + 0x6200,
+	FWCTFDCN0	= FWRO + 0x6300,
+	FWLTHFDCN0	= FWRO + 0x6304,
+	FWIPFDCN0	= FWRO + 0x6308,
+	FWLTWFDCN0	= FWRO + 0x630c,
+	FWPBFDCN0	= FWRO + 0x6310,
+	FWMHLCN0	= FWRO + 0x6314,
+	FWIHLCN0	= FWRO + 0x6318,
+	FWICRDCN0	= FWRO + 0x6500,
+	FWWMRDCN0	= FWRO + 0x6504,
+	FWCTRDCN0	= FWRO + 0x6508,
+	FWLTHRDCN0	= FWRO + 0x650c,
+	FWIPRDCN0	= FWRO + 0x6510,
+	FWLTWRDCN0	= FWRO + 0x6514,
+	FWPBRDCN0	= FWRO + 0x6518,
+	FWPMFDCN0	= FWRO + 0x6700,
+	FWPGFDCN0	= FWRO + 0x6780,
+	FWPMGDCN0	= FWRO + 0x6800,
+	FWPMYDCN0	= FWRO + 0x6804,
+	FWPMRDCN0	= FWRO + 0x6808,
+	FWFRPPCN0	= FWRO + 0x6a00,
+	FWFRDPCN0	= FWRO + 0x6a04,
+	FWEIS00		= FWRO + 0x7900,
+	FWEIE00		= FWRO + 0x7904,
+	FWEID00		= FWRO + 0x7908,
+	FWEIS1		= FWRO + 0x7a00,
+	FWEIE1		= FWRO + 0x7a04,
+	FWEID1		= FWRO + 0x7a08,
+	FWEIS2		= FWRO + 0x7a10,
+	FWEIE2		= FWRO + 0x7a14,
+	FWEID2		= FWRO + 0x7a18,
+	FWEIS3		= FWRO + 0x7a20,
+	FWEIE3		= FWRO + 0x7a24,
+	FWEID3		= FWRO + 0x7a28,
+	FWEIS4		= FWRO + 0x7a30,
+	FWEIE4		= FWRO + 0x7a34,
+	FWEID4		= FWRO + 0x7a38,
+	FWEIS5		= FWRO + 0x7a40,
+	FWEIE5		= FWRO + 0x7a44,
+	FWEID5		= FWRO + 0x7a48,
+	FWEIS60		= FWRO + 0x7a50,
+	FWEIE60		= FWRO + 0x7a54,
+	FWEID60		= FWRO + 0x7a58,
+	FWEIS61		= FWRO + 0x7a60,
+	FWEIE61		= FWRO + 0x7a64,
+	FWEID61		= FWRO + 0x7a68,
+	FWEIS62		= FWRO + 0x7a70,
+	FWEIE62		= FWRO + 0x7a74,
+	FWEID62		= FWRO + 0x7a78,
+	FWEIS63		= FWRO + 0x7a80,
+	FWEIE63		= FWRO + 0x7a84,
+	FWEID63		= FWRO + 0x7a88,
+	FWEIS70		= FWRO + 0x7a90,
+	FWEIE70		= FWRO + 0x7A94,
+	FWEID70		= FWRO + 0x7a98,
+	FWEIS71		= FWRO + 0x7aa0,
+	FWEIE71		= FWRO + 0x7aa4,
+	FWEID71		= FWRO + 0x7aa8,
+	FWEIS72		= FWRO + 0x7ab0,
+	FWEIE72		= FWRO + 0x7ab4,
+	FWEID72		= FWRO + 0x7ab8,
+	FWEIS73		= FWRO + 0x7ac0,
+	FWEIE73		= FWRO + 0x7ac4,
+	FWEID73		= FWRO + 0x7ac8,
+	FWEIS80		= FWRO + 0x7ad0,
+	FWEIE80		= FWRO + 0x7ad4,
+	FWEID80		= FWRO + 0x7ad8,
+	FWEIS81		= FWRO + 0x7ae0,
+	FWEIE81		= FWRO + 0x7ae4,
+	FWEID81		= FWRO + 0x7ae8,
+	FWEIS82		= FWRO + 0x7af0,
+	FWEIE82		= FWRO + 0x7af4,
+	FWEID82		= FWRO + 0x7af8,
+	FWEIS83		= FWRO + 0x7b00,
+	FWEIE83		= FWRO + 0x7b04,
+	FWEID83		= FWRO + 0x7b08,
+	FWMIS0		= FWRO + 0x7c00,
+	FWMIE0		= FWRO + 0x7c04,
+	FWMID0		= FWRO + 0x7c08,
+	FWSCR0		= FWRO + 0x7d00,
+	FWSCR1		= FWRO + 0x7d04,
+	FWSCR2		= FWRO + 0x7d08,
+	FWSCR3		= FWRO + 0x7d0c,
+	FWSCR4		= FWRO + 0x7d10,
+	FWSCR5		= FWRO + 0x7d14,
+	FWSCR6		= FWRO + 0x7d18,
+	FWSCR7		= FWRO + 0x7d1c,
+	FWSCR8		= FWRO + 0x7d20,
+	FWSCR9		= FWRO + 0x7d24,
+	FWSCR10		= FWRO + 0x7d28,
+	FWSCR11		= FWRO + 0x7d2c,
+	FWSCR12		= FWRO + 0x7d30,
+	FWSCR13		= FWRO + 0x7d34,
+	FWSCR14		= FWRO + 0x7d38,
+	FWSCR15		= FWRO + 0x7d3c,
+	FWSCR16		= FWRO + 0x7d40,
+	FWSCR17		= FWRO + 0x7d44,
+	FWSCR18		= FWRO + 0x7d48,
+	FWSCR19		= FWRO + 0x7d4c,
+	FWSCR20		= FWRO + 0x7d50,
+	FWSCR21		= FWRO + 0x7d54,
+	FWSCR22		= FWRO + 0x7d58,
+	FWSCR23		= FWRO + 0x7d5c,
+	FWSCR24		= FWRO + 0x7d60,
+	FWSCR25		= FWRO + 0x7d64,
+	FWSCR26		= FWRO + 0x7d68,
+	FWSCR27		= FWRO + 0x7d6c,
+	FWSCR28		= FWRO + 0x7d70,
+	FWSCR29		= FWRO + 0x7d74,
+	FWSCR30		= FWRO + 0x7d78,
+	FWSCR31		= FWRO + 0x7d7c,
+	FWSCR32		= FWRO + 0x7d80,
+	FWSCR33		= FWRO + 0x7d84,
+	FWSCR34		= FWRO + 0x7d88,
+	FWSCR35		= FWRO + 0x7d8c,
+	FWSCR36		= FWRO + 0x7d90,
+	FWSCR37		= FWRO + 0x7d94,
+	FWSCR38		= FWRO + 0x7d98,
+	FWSCR39		= FWRO + 0x7d9c,
+	FWSCR40		= FWRO + 0x7da0,
+	FWSCR41		= FWRO + 0x7da4,
+	FWSCR42		= FWRO + 0x7da8,
+	FWSCR43		= FWRO + 0x7dac,
+	FWSCR44		= FWRO + 0x7db0,
+	FWSCR45		= FWRO + 0x7db4,
+	FWSCR46		= FWRO + 0x7db8,
+
+	TPEMIMC0	= TPRO + 0x0000,
+	TPEMIMC1	= TPRO + 0x0004,
+	TPEMIMC2	= TPRO + 0x0008,
+	TPEMIMC3	= TPRO + 0x000c,
+	TPEMIMC4	= TPRO + 0x0010,
+	TPEMIMC5	= TPRO + 0x0014,
+	TPEMIMC60	= TPRO + 0x0080,
+	TPEMIMC70	= TPRO + 0x0100,
+	TSIM		= TPRO + 0x0700,
+	TFIM		= TPRO + 0x0704,
+	TCIM		= TPRO + 0x0708,
+	TGIM0		= TPRO + 0x0710,
+	TGIM1		= TPRO + 0x0714,
+	TEIM0		= TPRO + 0x0720,
+	TEIM1		= TPRO + 0x0724,
+	TEIM2		= TPRO + 0x0728,
+
+	RIPV		= CARO + 0x0000,
+	RRC		= CARO + 0x0004,
+	RCEC		= CARO + 0x0008,
+	RCDC		= CARO + 0x000c,
+	RSSIS		= CARO + 0x0010,
+	RSSIE		= CARO + 0x0014,
+	RSSID		= CARO + 0x0018,
+	CABPIBWMC	= CARO + 0x0020,
+	CABPWMLC	= CARO + 0x0040,
+	CABPPFLC0	= CARO + 0x0050,
+	CABPPWMLC0	= CARO + 0x0060,
+	CABPPPFLC00	= CARO + 0x00a0,
+	CABPULC		= CARO + 0x0100,
+	CABPIRM		= CARO + 0x0140,
+	CABPPCM		= CARO + 0x0144,
+	CABPLCM		= CARO + 0x0148,
+	CABPCPM		= CARO + 0x0180,
+	CABPMCPM	= CARO + 0x0200,
+	CARDNM		= CARO + 0x0280,
+	CARDMNM		= CARO + 0x0284,
+	CARDCN		= CARO + 0x0290,
+	CAEIS0		= CARO + 0x0300,
+	CAEIE0		= CARO + 0x0304,
+	CAEID0		= CARO + 0x0308,
+	CAEIS1		= CARO + 0x0310,
+	CAEIE1		= CARO + 0x0314,
+	CAEID1		= CARO + 0x0318,
+	CAMIS0		= CARO + 0x0340,
+	CAMIE0		= CARO + 0x0344,
+	CAMID0		= CARO + 0x0348,
+	CAMIS1		= CARO + 0x0350,
+	CAMIE1		= CARO + 0x0354,
+	CAMID1		= CARO + 0x0358,
+	CASCR		= CARO + 0x0380,
+
+	EAMC		= TARO + 0x0000,
+	EAMS		= TARO + 0x0004,
+	EAIRC		= TARO + 0x0010,
+	EATDQSC		= TARO + 0x0014,
+	EATDQC		= TARO + 0x0018,
+	EATDQAC		= TARO + 0x001c,
+	EATPEC		= TARO + 0x0020,
+	EATMFSC0	= TARO + 0x0040,
+	EATDQDC0	= TARO + 0x0060,
+	EATDQM0		= TARO + 0x0080,
+	EATDQMLM0	= TARO + 0x00a0,
+	EACTQC		= TARO + 0x0100,
+	EACTDQDC	= TARO + 0x0104,
+	EACTDQM		= TARO + 0x0108,
+	EACTDQMLM	= TARO + 0x010c,
+	EAVCC		= TARO + 0x0130,
+	EAVTC		= TARO + 0x0134,
+	EATTFC		= TARO + 0x0138,
+	EACAEC		= TARO + 0x0200,
+	EACC		= TARO + 0x0204,
+	EACAIVC0	= TARO + 0x0220,
+	EACAULC0	= TARO + 0x0240,
+	EACOEM		= TARO + 0x0260,
+	EACOIVM0	= TARO + 0x0280,
+	EACOULM0	= TARO + 0x02a0,
+	EACGSM		= TARO + 0x02c0,
+	EATASC		= TARO + 0x0300,
+	EATASENC0	= TARO + 0x0320,
+	EATASCTENC	= TARO + 0x0340,
+	EATASENM0	= TARO + 0x0360,
+	EATASCTENM	= TARO + 0x0380,
+	EATASCSTC0	= TARO + 0x03a0,
+	EATASCSTC1	= TARO + 0x03a4,
+	EATASCSTM0	= TARO + 0x03a8,
+	EATASCSTM1	= TARO + 0x03ac,
+	EATASCTC	= TARO + 0x03b0,
+	EATASCTM	= TARO + 0x03b4,
+	EATASGL0	= TARO + 0x03c0,
+	EATASGL1	= TARO + 0x03c4,
+	EATASGLR	= TARO + 0x03c8,
+	EATASGR		= TARO + 0x03d0,
+	EATASGRR	= TARO + 0x03d4,
+	EATASHCC	= TARO + 0x03e0,
+	EATASRIRM	= TARO + 0x03e4,
+	EATASSM		= TARO + 0x03e8,
+	EAUSMFSECN	= TARO + 0x0400,
+	EATFECN		= TARO + 0x0404,
+	EAFSECN		= TARO + 0x0408,
+	EADQOECN	= TARO + 0x040c,
+	EADQSECN	= TARO + 0x0410,
+	EACKSECN	= TARO + 0x0414,
+	EAEIS0		= TARO + 0x0500,
+	EAEIE0		= TARO + 0x0504,
+	EAEID0		= TARO + 0x0508,
+	EAEIS1		= TARO + 0x0510,
+	EAEIE1		= TARO + 0x0514,
+	EAEID1		= TARO + 0x0518,
+	EAEIS2		= TARO + 0x0520,
+	EAEIE2		= TARO + 0x0524,
+	EAEID2		= TARO + 0x0528,
+	EASCR		= TARO + 0x0580,
+
+	MPSM		= RMRO + 0x0000,
+	MPIC		= RMRO + 0x0004,
+	MPIM		= RMRO + 0x0008,
+	MIOC		= RMRO + 0x0010,
+	MIOM		= RMRO + 0x0014,
+	MXMS		= RMRO + 0x0018,
+	MTFFC		= RMRO + 0x0020,
+	MTPFC		= RMRO + 0x0024,
+	MTPFC2		= RMRO + 0x0028,
+	MTPFC30		= RMRO + 0x0030,
+	MTATC0		= RMRO + 0x0050,
+	MTIM		= RMRO + 0x0060,
+	MRGC		= RMRO + 0x0080,
+	MRMAC0		= RMRO + 0x0084,
+	MRMAC1		= RMRO + 0x0088,
+	MRAFC		= RMRO + 0x008c,
+	MRSCE		= RMRO + 0x0090,
+	MRSCP		= RMRO + 0x0094,
+	MRSCC		= RMRO + 0x0098,
+	MRFSCE		= RMRO + 0x009c,
+	MRFSCP		= RMRO + 0x00a0,
+	MTRC		= RMRO + 0x00a4,
+	MRIM		= RMRO + 0x00a8,
+	MRPFM		= RMRO + 0x00ac,
+	MPFC0		= RMRO + 0x0100,
+	MLVC		= RMRO + 0x0180,
+	MEEEC		= RMRO + 0x0184,
+	MLBC		= RMRO + 0x0188,
+	MXGMIIC		= RMRO + 0x0190,
+	MPCH		= RMRO + 0x0194,
+	MANC		= RMRO + 0x0198,
+	MANM		= RMRO + 0x019c,
+	MPLCA1		= RMRO + 0x01a0,
+	MPLCA2		= RMRO + 0x01a4,
+	MPLCA3		= RMRO + 0x01a8,
+	MPLCA4		= RMRO + 0x01ac,
+	MPLCAM		= RMRO + 0x01b0,
+	MHDC1		= RMRO + 0x01c0,
+	MHDC2		= RMRO + 0x01c4,
+	MEIS		= RMRO + 0x0200,
+	MEIE		= RMRO + 0x0204,
+	MEID		= RMRO + 0x0208,
+	MMIS0		= RMRO + 0x0210,
+	MMIE0		= RMRO + 0x0214,
+	MMID0		= RMRO + 0x0218,
+	MMIS1		= RMRO + 0x0220,
+	MMIE1		= RMRO + 0x0224,
+	MMID1		= RMRO + 0x0228,
+	MMIS2		= RMRO + 0x0230,
+	MMIE2		= RMRO + 0x0234,
+	MMID2		= RMRO + 0x0238,
+	MMPFTCT		= RMRO + 0x0300,
+	MAPFTCT		= RMRO + 0x0304,
+	MPFRCT		= RMRO + 0x0308,
+	MFCICT		= RMRO + 0x030c,
+	MEEECT		= RMRO + 0x0310,
+	MMPCFTCT0	= RMRO + 0x0320,
+	MAPCFTCT0	= RMRO + 0x0330,
+	MPCFRCT0	= RMRO + 0x0340,
+	MHDCC		= RMRO + 0x0350,
+	MROVFC		= RMRO + 0x0354,
+	MRHCRCEC	= RMRO + 0x0358,
+	MRXBCE		= RMRO + 0x0400,
+	MRXBCP		= RMRO + 0x0404,
+	MRGFCE		= RMRO + 0x0408,
+	MRGFCP		= RMRO + 0x040c,
+	MRBFC		= RMRO + 0x0410,
+	MRMFC		= RMRO + 0x0414,
+	MRUFC		= RMRO + 0x0418,
+	MRPEFC		= RMRO + 0x041c,
+	MRNEFC		= RMRO + 0x0420,
+	MRFMEFC		= RMRO + 0x0424,
+	MRFFMEFC	= RMRO + 0x0428,
+	MRCFCEFC	= RMRO + 0x042c,
+	MRFCEFC		= RMRO + 0x0430,
+	MRRCFEFC	= RMRO + 0x0434,
+	MRUEFC		= RMRO + 0x043c,
+	MROEFC		= RMRO + 0x0440,
+	MRBOEC		= RMRO + 0x0444,
+	MTXBCE		= RMRO + 0x0500,
+	MTXBCP		= RMRO + 0x0504,
+	MTGFCE		= RMRO + 0x0508,
+	MTGFCP		= RMRO + 0x050c,
+	MTBFC		= RMRO + 0x0510,
+	MTMFC		= RMRO + 0x0514,
+	MTUFC		= RMRO + 0x0518,
+	MTEFC		= RMRO + 0x051c,
+
+	GWMC		= GWRO + 0x0000,
+	GWMS		= GWRO + 0x0004,
+	GWIRC		= GWRO + 0x0010,
+	GWRDQSC		= GWRO + 0x0014,
+	GWRDQC		= GWRO + 0x0018,
+	GWRDQAC		= GWRO + 0x001c,
+	GWRGC		= GWRO + 0x0020,
+	GWRMFSC0	= GWRO + 0x0040,
+	GWRDQDC0	= GWRO + 0x0060,
+	GWRDQM0		= GWRO + 0x0080,
+	GWRDQMLM0	= GWRO + 0x00a0,
+	GWMTIRM		= GWRO + 0x0100,
+	GWMSTLS		= GWRO + 0x0104,
+	GWMSTLR		= GWRO + 0x0108,
+	GWMSTSS		= GWRO + 0x010c,
+	GWMSTSR		= GWRO + 0x0110,
+	GWMAC0		= GWRO + 0x0120,
+	GWMAC1		= GWRO + 0x0124,
+	GWVCC		= GWRO + 0x0130,
+	GWVTC		= GWRO + 0x0134,
+	GWTTFC		= GWRO + 0x0138,
+	GWTDCAC00	= GWRO + 0x0140,
+	GWTDCAC10	= GWRO + 0x0144,
+	GWTSDCC0	= GWRO + 0x0160,
+	GWTNM		= GWRO + 0x0180,
+	GWTMNM		= GWRO + 0x0184,
+	GWAC		= GWRO + 0x0190,
+	GWDCBAC0	= GWRO + 0x0194,
+	GWDCBAC1	= GWRO + 0x0198,
+	GWIICBSC	= GWRO + 0x019c,
+	GWMDNC		= GWRO + 0x01a0,
+	GWTRC0		= GWRO + 0x0200,
+	GWTPC0		= GWRO + 0x0300,
+	GWARIRM		= GWRO + 0x0380,
+	GWDCC0		= GWRO + 0x0400,
+	GWAARSS		= GWRO + 0x0800,
+	GWAARSR0	= GWRO + 0x0804,
+	GWAARSR1	= GWRO + 0x0808,
+	GWIDAUAS0	= GWRO + 0x0840,
+	GWIDASM0	= GWRO + 0x0880,
+	GWIDASAM00	= GWRO + 0x0900,
+	GWIDASAM10	= GWRO + 0x0904,
+	GWIDACAM00	= GWRO + 0x0980,
+	GWIDACAM10	= GWRO + 0x0984,
+	GWGRLC		= GWRO + 0x0a00,
+	GWGRLULC	= GWRO + 0x0a04,
+	GWRLIVC0	= GWRO + 0x0a80,
+	GWRLULC0	= GWRO + 0x0a84,
+	GWIDPC		= GWRO + 0x0b00,
+	GWIDC0		= GWRO + 0x0c00,
+	GWDIS0		= GWRO + 0x1100,
+	GWDIE0		= GWRO + 0x1104,
+	GWDID0		= GWRO + 0x1108,
+	GWTSDIS		= GWRO + 0x1180,
+	GWTSDIE		= GWRO + 0x1184,
+	GWTSDID		= GWRO + 0x1188,
+	GWEIS0		= GWRO + 0x1190,
+	GWEIE0		= GWRO + 0x1194,
+	GWEID0		= GWRO + 0x1198,
+	GWEIS1		= GWRO + 0x11a0,
+	GWEIE1		= GWRO + 0x11a4,
+	GWEID1		= GWRO + 0x11a8,
+	GWEIS20		= GWRO + 0x1200,
+	GWEIE20		= GWRO + 0x1204,
+	GWEID20		= GWRO + 0x1208,
+	GWEIS3		= GWRO + 0x1280,
+	GWEIE3		= GWRO + 0x1284,
+	GWEID3		= GWRO + 0x1288,
+	GWEIS4		= GWRO + 0x1290,
+	GWEIE4		= GWRO + 0x1294,
+	GWEID4		= GWRO + 0x1298,
+	GWEIS5		= GWRO + 0x12a0,
+	GWEIE5		= GWRO + 0x12a4,
+	GWEID5		= GWRO + 0x12a8,
+	GWSCR0		= GWRO + 0x1800,
+	GWSCR1		= GWRO + 0x1900,
+};
+
+/* ETHA/RMAC */
+enum rswitch_etha_mode {
+	EAMC_OPC_RESET,
+	EAMC_OPC_DISABLE,
+	EAMC_OPC_CONFIG,
+	EAMC_OPC_OPERATION,
+};
+
+#define EAMS_OPS_MASK		EAMC_OPC_OPERATION
+
+#define EAVCC_VEM_SC_TAG	(0x3 << 16)
+
+#define MPIC_PIS_MII		0x00
+#define MPIC_PIS_GMII		0x02
+#define MPIC_PIS_XGMII		0x04
+#define MPIC_LSC_SHIFT		3
+#define MPIC_LSC_100M		(1 << MPIC_LSC_SHIFT)
+#define MPIC_LSC_1G		(2 << MPIC_LSC_SHIFT)
+#define MPIC_LSC_2_5G		(3 << MPIC_LSC_SHIFT)
+
+#define MDIO_READ_C45		0x03
+#define MDIO_WRITE_C45		0x01
+
+#define MPSM_PSME		BIT(0)
+#define MPSM_MFF_C45		BIT(2)
+#define MPSM_PRD_SHIFT		16
+#define MPSM_PRD_MASK		GENMASK(31, MPSM_PRD_SHIFT)
+
+/* Completion flags */
+#define MMIS1_PAACS             BIT(2) /* Address */
+#define MMIS1_PWACS             BIT(1) /* Write */
+#define MMIS1_PRACS             BIT(0) /* Read */
+#define MMIS1_CLEAR_FLAGS       0xf
+
+#define MPIC_PSMCS_SHIFT	16
+#define MPIC_PSMCS_MASK		GENMASK(22, MPIC_PSMCS_SHIFT)
+#define MPIC_PSMCS(val)		((val) << MPIC_PSMCS_SHIFT)
+
+#define MPIC_PSMHT_SHIFT	24
+#define MPIC_PSMHT_MASK		GENMASK(26, MPIC_PSMHT_SHIFT)
+#define MPIC_PSMHT(val)		((val) << MPIC_PSMHT_SHIFT)
+
+#define MLVC_PLV		BIT(16)
+
+/* GWCA */
+enum rswitch_gwca_mode {
+	GWMC_OPC_RESET,
+	GWMC_OPC_DISABLE,
+	GWMC_OPC_CONFIG,
+	GWMC_OPC_OPERATION,
+};
+
+#define GWMS_OPS_MASK		GWMC_OPC_OPERATION
+
+#define GWMTIRM_MTIOG		BIT(0)
+#define GWMTIRM_MTR		BIT(1)
+
+#define GWVCC_VEM_SC_TAG	(0x3 << 16)
+
+#define GWARIRM_ARIOG		BIT(0)
+#define GWARIRM_ARR		BIT(1)
+
+#define GWDCC_BALR		BIT(24)
+#define GWDCC_DQT		BIT(11)
+#define GWDCC_ETS		BIT(9)
+#define GWDCC_EDE		BIT(8)
+
+#define GWTRC(chain)		(GWTRC0 + (chain) / 32 * 4)
+#define GWDCC_OFFS(chain)	(GWDCC0 + (chain) * 4)
+
+#define GWDIS(i)		(GWDIS0 + (i) * 0x10)
+#define GWDIE(i)		(GWDIE0 + (i) * 0x10)
+#define GWDID(i)		(GWDID0 + (i) * 0x10)
+
+/* COMA */
+#define RRC_RR			BIT(0)
+#define RRC_RR_CLR		0
+#define RCEC_RCE		BIT(16)
+#define RCDC_RCD		BIT(16)
+
+#define CABPIRM_BPIOG		BIT(0)
+#define CABPIRM_BPR		BIT(1)
+
+/* MFWD */
+#define FWPC0_LTHTA		BIT(0)
+#define FWPC0_IP4UE		BIT(3)
+#define FWPC0_IP4TE		BIT(4)
+#define FWPC0_IP4OE		BIT(5)
+#define FWPC0_L2SE		BIT(9)
+#define FWPC0_IP4EA		BIT(10)
+#define FWPC0_IPDSA		BIT(12)
+#define FWPC0_IPHLA		BIT(18)
+#define FWPC0_MACSDA		BIT(20)
+#define FWPC0_MACHLA		BIT(26)
+#define FWPC0_MACHMA		BIT(27)
+#define FWPC0_VLANSA		BIT(28)
+
+#define FWPC0(i)		(FWPC00 + (i) * 0x10)
+#define FWPC0_DEFAULT		(FWPC0_LTHTA | FWPC0_IP4UE | FWPC0_IP4TE | \
+				 FWPC0_IP4OE | FWPC0_L2SE | FWPC0_IP4EA | \
+				 FWPC0_IPDSA | FWPC0_IPHLA | FWPC0_MACSDA | \
+				 FWPC0_MACHLA |	FWPC0_MACHMA | FWPC0_VLANSA)
+
+#define	FWPBFC(i)		(FWPBFC0 + (i) * 0x10)
+
+#define FWPBFCSDC(j, i)         (FWPBFCSDC00 + (i) * 0x10 + (j) * 0x04)
+
+/* TOP */
+#define TPEMIMC7(chain)		(TPEMIMC70 + (chain) * 4)
+
+/* Descriptors */
+enum RX_DS_CC_BIT {
+	RX_DS	= 0x0fff, /* Data size */
+	RX_TR	= 0x1000, /* Truncation indication */
+	RX_EI	= 0x2000, /* Error indication */
+	RX_PS	= 0xc000, /* Padding selection */
+};
+
+enum TX_DS_TAGL_BIT {
+	TX_DS	= 0x0fff, /* Data size */
+	TX_TAGL	= 0xf000, /* Frame tag LSBs */
+};
+
+enum DIE_DT {
+	/* Frame data */
+	DT_FSINGLE	= 0x80,
+	DT_FSTART	= 0x90,
+	DT_FMID		= 0xa0,
+	DT_FEND		= 0xb8,
+
+	/* Chain control */
+	DT_LEMPTY	= 0xc0,
+	DT_EEMPTY	= 0xd0,
+	DT_LINKFIX	= 0x00,
+	DT_LINK		= 0xe0,
+	DT_EOS		= 0xf0,
+	/* HW/SW arbitration */
+	DT_FEMPTY	= 0x40,
+	DT_FEMPTY_IS	= 0x10,
+	DT_FEMPTY_IC	= 0x20,
+	DT_FEMPTY_ND	= 0x38,
+	DT_FEMPTY_START	= 0x50,
+	DT_FEMPTY_MID	= 0x60,
+	DT_FEMPTY_END	= 0x70,
+
+	DT_MASK		= 0xf0,
+	DIE		= 0x08,	/* Descriptor Interrupt Enable */
+};
+
+struct rswitch_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+} __packed;
+
+struct rswitch_ts_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+	__le32 ts_nsec;
+	__le32 ts_sec;
+} __packed;
+
+struct rswitch_ext_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+	__le64 info1;
+} __packed;
+
+struct rswitch_ext_ts_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+	__le64 info1;
+	__le32 ts_nsec;
+	__le32 ts_sec;
+} __packed;
+
+struct rswitch_etha {
+	int index;
+	void __iomem *addr;
+	void __iomem *coma_addr;
+	bool external_phy;
+	struct mii_bus *mii;
+	phy_interface_t phy_interface;
+	u8 mac_addr[MAX_ADDR_LEN];
+	int link;
+	int speed;
+
+	/* This hardware could not be initialized twice so that marked
+	 * this flag to avoid multiple initialization.
+	 */
+	bool operated;
+};
+
+struct rswitch_gwca_chain {
+	int index;
+	bool dir_tx;
+	bool gptp;
+	union {
+		struct rswitch_ext_desc *ring;
+		struct rswitch_ext_ts_desc *ts_ring;
+	};
+	dma_addr_t ring_dma;
+	u32 num_ring;
+	u32 cur;
+	u32 dirty;
+	struct sk_buff **skb;
+
+	struct net_device *ndev;	/* chain to ndev for irq */
+};
+
+#define RSWITCH_NUM_IRQ_REGS	(RSWITCH_MAX_NUM_CHAINS / BITS_PER_TYPE(u32))
+struct rswitch_gwca {
+	int index;
+	struct rswitch_gwca_chain *chains;
+	int num_chains;
+	DECLARE_BITMAP(used, RSWITCH_MAX_NUM_CHAINS);
+	u32 tx_irq_bits[RSWITCH_NUM_IRQ_REGS];
+	u32 rx_irq_bits[RSWITCH_NUM_IRQ_REGS];
+	int speed;
+};
+
+#define NUM_CHAINS_PER_NDEV	2
+struct rswitch_device {
+	struct rswitch_private *priv;
+	struct net_device *ndev;
+	struct napi_struct napi;
+	void __iomem *addr;
+	struct rswitch_gwca_chain *tx_chain;
+	struct rswitch_gwca_chain *rx_chain;
+	u8 ts_tag;
+
+	int port;
+	struct rswitch_etha *etha;
+};
+
+struct rswitch_mfwd_mac_table_entry {
+	int chain_index;
+	unsigned char addr[MAX_ADDR_LEN];
+};
+
+struct rswitch_mfwd {
+	struct rswitch_mac_table_entry *mac_table_entries;
+	int num_mac_table_entries;
+};
+
+struct rswitch_private {
+	struct platform_device *pdev;
+	void __iomem *addr;
+	struct rswitch_desc *desc_bat;
+	dma_addr_t desc_bat_dma;
+	u32 desc_bat_size;
+
+	struct rswitch_device *rdev[RSWITCH_NUM_NDEV];
+
+	struct rswitch_gwca gwca;
+	struct rswitch_etha etha[RSWITCH_MAX_NUM_ETHA];
+	struct rswitch_mfwd mfwd;
+
+	struct clk *tsn_clk;
+
+	bool gwca_halt;
+};
+
+#endif	/* #ifndef __RSWITCH_H__ */
-- 
2.25.1


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

* [PATCH v3 3/3] net: ethernet: renesas: rswitch: Add R-Car Gen4 gPTP support
  2022-09-22  5:28 [PATCH v3 0/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
  2022-09-22  5:28 ` [PATCH v3 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch Yoshihiro Shimoda
  2022-09-22  5:28 ` [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
@ 2022-09-22  5:28 ` Yoshihiro Shimoda
  2 siblings, 0 replies; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-22  5:28 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt
  Cc: netdev, devicetree, linux-renesas-soc, Yoshihiro Shimoda

Add R-Car Gen4 gPTP support into the rswitch driver.

Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
 drivers/net/ethernet/renesas/Makefile        |   2 +-
 drivers/net/ethernet/renesas/rcar_gen4_ptp.c | 181 +++++++++++++++++++
 drivers/net/ethernet/renesas/rcar_gen4_ptp.h |  72 ++++++++
 drivers/net/ethernet/renesas/rswitch.c       | 150 ++++++++++++++-
 drivers/net/ethernet/renesas/rswitch.h       |   2 +
 5 files changed, 405 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/ethernet/renesas/rcar_gen4_ptp.c
 create mode 100644 drivers/net/ethernet/renesas/rcar_gen4_ptp.h

diff --git a/drivers/net/ethernet/renesas/Makefile b/drivers/net/ethernet/renesas/Makefile
index 32845ba53970..592005893464 100644
--- a/drivers/net/ethernet/renesas/Makefile
+++ b/drivers/net/ethernet/renesas/Makefile
@@ -9,6 +9,6 @@ ravb-objs := ravb_main.o ravb_ptp.o
 
 obj-$(CONFIG_RAVB) += ravb.o
 
-rswitch_drv-objs := rswitch.o
+rswitch_drv-objs := rswitch.o rcar_gen4_ptp.o
 
 obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch_drv.o
diff --git a/drivers/net/ethernet/renesas/rcar_gen4_ptp.c b/drivers/net/ethernet/renesas/rcar_gen4_ptp.c
new file mode 100644
index 000000000000..c007e33c47e1
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rcar_gen4_ptp.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Renesas R-Car Gen4 gPTP device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <linux/err.h>
+#include <linux/etherdevice.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "rcar_gen4_ptp.h"
+#define ptp_to_priv(ptp)	container_of(ptp, struct rcar_gen4_ptp_private, info)
+
+static const struct rcar_gen4_ptp_reg_offset s4_offs = {
+	.enable = PTPTMEC,
+	.disable = PTPTMDC,
+	.increment = PTPTIVC0,
+	.config_t0 = PTPTOVC00,
+	.config_t1 = PTPTOVC10,
+	.config_t2 = PTPTOVC20,
+	.monitor_t0 = PTPGPTPTM00,
+	.monitor_t1 = PTPGPTPTM10,
+	.monitor_t2 = PTPGPTPTM20,
+};
+
+static int rcar_gen4_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+	bool neg_adj = scaled_ppm < 0 ? true : false;
+	s64 addend = ptp_priv->default_addend;
+	s64 diff;
+
+	if (neg_adj)
+		scaled_ppm = -scaled_ppm;
+	diff = div_s64(addend * scaled_ppm_to_ppb(scaled_ppm), NSEC_PER_SEC);
+	addend = neg_adj ? addend - diff : addend + diff;
+
+	iowrite32(addend, ptp_priv->addr + ptp_priv->offs->increment);
+
+	return 0;
+}
+
+/* Caller must hold the lock */
+static void _rcar_gen4_ptp_gettime(struct ptp_clock_info *ptp,
+				   struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+
+	ts->tv_nsec = ioread32(ptp_priv->addr + ptp_priv->offs->monitor_t0);
+	ts->tv_sec = ioread32(ptp_priv->addr + ptp_priv->offs->monitor_t1) |
+		     ((s64)ioread32(ptp_priv->addr + ptp_priv->offs->monitor_t2) << 32);
+}
+
+static int rcar_gen4_ptp_gettime(struct ptp_clock_info *ptp,
+				 struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ptp_priv->lock, flags);
+	_rcar_gen4_ptp_gettime(ptp, ts);
+	spin_unlock_irqrestore(&ptp_priv->lock, flags);
+
+	return 0;
+}
+
+/* Caller must hold the lock */
+static void _rcar_gen4_ptp_settime(struct ptp_clock_info *ptp,
+				   const struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+
+	iowrite32(1, ptp_priv->addr + ptp_priv->offs->disable);
+	iowrite32(0, ptp_priv->addr + ptp_priv->offs->config_t2);
+	iowrite32(0, ptp_priv->addr + ptp_priv->offs->config_t1);
+	iowrite32(0, ptp_priv->addr + ptp_priv->offs->config_t0);
+	iowrite32(1, ptp_priv->addr + ptp_priv->offs->enable);
+	iowrite32(ts->tv_sec >> 32, ptp_priv->addr + ptp_priv->offs->config_t2);
+	iowrite32(ts->tv_sec, ptp_priv->addr + ptp_priv->offs->config_t1);
+	iowrite32(ts->tv_nsec, ptp_priv->addr + ptp_priv->offs->config_t0);
+}
+
+static int rcar_gen4_ptp_settime(struct ptp_clock_info *ptp,
+				 const struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ptp_priv->lock, flags);
+	_rcar_gen4_ptp_settime(ptp, ts);
+	spin_unlock_irqrestore(&ptp_priv->lock, flags);
+
+	return 0;
+}
+
+static int rcar_gen4_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+	struct timespec64 ts;
+	unsigned long flags;
+	s64 now;
+
+	spin_lock_irqsave(&ptp_priv->lock, flags);
+	_rcar_gen4_ptp_gettime(ptp, &ts);
+	now = ktime_to_ns(timespec64_to_ktime(ts));
+	ts = ns_to_timespec64(now + delta);
+	_rcar_gen4_ptp_settime(ptp, &ts);
+	spin_unlock_irqrestore(&ptp_priv->lock, flags);
+
+	return 0;
+}
+
+static int rcar_gen4_ptp_enable(struct ptp_clock_info *ptp,
+				struct ptp_clock_request *rq, int on)
+{
+	return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info rcar_gen4_ptp_info = {
+	.owner = THIS_MODULE,
+	.name = "rcar_gen4_ptp",
+	.max_adj = 50000000,
+	.adjfine = rcar_gen4_ptp_adjfine,
+	.adjtime = rcar_gen4_ptp_adjtime,
+	.gettime64 = rcar_gen4_ptp_gettime,
+	.settime64 = rcar_gen4_ptp_settime,
+	.enable = rcar_gen4_ptp_enable,
+};
+
+static void rcar_gen4_ptp_set_offs(struct rcar_gen4_ptp_private *ptp_priv,
+				   enum rcar_gen4_ptp_reg_layout layout)
+{
+	WARN_ON(layout != RCAR_GEN4_PTP_REG_LAYOUT_S4);
+
+	ptp_priv->offs = &s4_offs;
+}
+
+int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv,
+			   enum rcar_gen4_ptp_reg_layout layout, u32 clock)
+{
+	if (ptp_priv->initialized)
+		return 0;
+
+	spin_lock_init(&ptp_priv->lock);
+
+	rcar_gen4_ptp_set_offs(ptp_priv, layout);
+
+	ptp_priv->default_addend = clock;
+	iowrite32(ptp_priv->default_addend, ptp_priv->addr + ptp_priv->offs->increment);
+	ptp_priv->clock = ptp_clock_register(&ptp_priv->info, NULL);
+	if (IS_ERR(ptp_priv->clock))
+		return PTR_ERR(ptp_priv->clock);
+
+	iowrite32(0x01, ptp_priv->addr + ptp_priv->offs->enable);
+	ptp_priv->initialized = true;
+
+	return 0;
+}
+
+int rcar_gen4_ptp_unregister(struct rcar_gen4_ptp_private *ptp_priv)
+{
+	iowrite32(1, ptp_priv->addr + ptp_priv->offs->disable);
+
+	return ptp_clock_unregister(ptp_priv->clock);
+}
+
+struct rcar_gen4_ptp_private *rcar_gen4_ptp_alloc(struct platform_device *pdev)
+{
+	struct rcar_gen4_ptp_private *ptp;
+
+	ptp = devm_kzalloc(&pdev->dev, sizeof(*ptp), GFP_KERNEL);
+	if (!ptp)
+		return NULL;
+
+	ptp->info = rcar_gen4_ptp_info;
+
+	return ptp;
+}
diff --git a/drivers/net/ethernet/renesas/rcar_gen4_ptp.h b/drivers/net/ethernet/renesas/rcar_gen4_ptp.h
new file mode 100644
index 000000000000..b1bbea8d3a52
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rcar_gen4_ptp.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Renesas R-Car Gen4 gPTP device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#ifndef __RCAR_GEN4_PTP_H__
+#define __RCAR_GEN4_PTP_H__
+
+#include <linux/ptp_clock_kernel.h>
+
+#define PTPTIVC_INIT			0x19000000	/* 320MHz */
+#define RCAR_GEN4_PTP_CLOCK_S4		PTPTIVC_INIT
+#define RCAR_GEN4_GPTP_OFFSET_S4	0x00018000
+
+/* for rcar_gen4_ptp_init */
+enum rcar_gen4_ptp_reg_layout {
+	RCAR_GEN4_PTP_REG_LAYOUT_S4
+};
+
+/* driver's definitions */
+#define RCAR_GEN4_RXTSTAMP_ENABLED		BIT(0)
+#define RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT	BIT(1)
+#define RCAR_GEN4_RXTSTAMP_TYPE_ALL		(RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT | BIT(2))
+#define RCAR_GEN4_RXTSTAMP_TYPE			RCAR_GEN4_RXTSTAMP_TYPE_ALL
+
+#define RCAR_GEN4_TXTSTAMP_ENABLED		BIT(0)
+
+#define PTPRO				0
+
+enum rcar_gen4_ptp_reg_s4 {
+	PTPTMEC		= PTPRO + 0x0010,
+	PTPTMDC		= PTPRO + 0x0014,
+	PTPTIVC0	= PTPRO + 0x0020,
+	PTPTOVC00	= PTPRO + 0x0030,
+	PTPTOVC10	= PTPRO + 0x0034,
+	PTPTOVC20	= PTPRO + 0x0038,
+	PTPGPTPTM00	= PTPRO + 0x0050,
+	PTPGPTPTM10	= PTPRO + 0x0054,
+	PTPGPTPTM20	= PTPRO + 0x0058,
+};
+
+struct rcar_gen4_ptp_reg_offset {
+	u16 enable;
+	u16 disable;
+	u16 increment;
+	u16 config_t0;
+	u16 config_t1;
+	u16 config_t2;
+	u16 monitor_t0;
+	u16 monitor_t1;
+	u16 monitor_t2;
+};
+
+struct rcar_gen4_ptp_private {
+	void __iomem *addr;
+	struct ptp_clock *clock;
+	struct ptp_clock_info info;
+	const struct rcar_gen4_ptp_reg_offset *offs;
+	spinlock_t lock;	/* For multiple registers access */
+	u32 tstamp_tx_ctrl;
+	u32 tstamp_rx_ctrl;
+	s64 default_addend;
+	bool initialized;
+};
+
+int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv,
+			   enum rcar_gen4_ptp_reg_layout layout, u32 clock);
+int rcar_gen4_ptp_unregister(struct rcar_gen4_ptp_private *ptp_priv);
+struct rcar_gen4_ptp_private *rcar_gen4_ptp_alloc(struct platform_device *pdev);
+
+#endif	/* #ifndef __RCAR_GEN4_PTP_H__ */
diff --git a/drivers/net/ethernet/renesas/rswitch.c b/drivers/net/ethernet/renesas/rswitch.c
index 2723dffad93a..994bbc7a40d6 100644
--- a/drivers/net/ethernet/renesas/rswitch.c
+++ b/drivers/net/ethernet/renesas/rswitch.c
@@ -117,6 +117,13 @@ static void rswitch_fwd_init(struct rswitch_private *priv)
 	iowrite32(GENMASK(RSWITCH_NUM_ETHA - 1, 0), priv->addr + FWPBFC(3));
 }
 
+/* gPTP timer (gPTP) */
+static void rswitch_get_timestamp(struct rswitch_private *priv,
+				  struct timespec64 *ts)
+{
+	priv->ptp_priv->info.gettime64(&priv->ptp_priv->info, ts);
+}
+
 /* Gateway CPU agent block (GWCA) */
 static int rswitch_gwca_change_mode(struct rswitch_private *priv,
 				    enum rswitch_gwca_mode mode)
@@ -637,6 +644,7 @@ static bool rswitch_rx(struct net_device *ndev, int *quota)
 	struct sk_buff *skb;
 	dma_addr_t dma_addr;
 	u16 pkt_len;
+	u32 get_ts;
 
 	boguscnt = min_t(int, c->dirty + c->num_ring - c->cur, *quota);
 	limit = boguscnt;
@@ -651,6 +659,17 @@ static bool rswitch_rx(struct net_device *ndev, int *quota)
 		c->skb[entry] = NULL;
 		dma_addr = le32_to_cpu(desc->dptrl) | ((__le64)le32_to_cpu(desc->dptrh) << 32);
 		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ, DMA_FROM_DEVICE);
+		get_ts = rdev->priv->ptp_priv->tstamp_rx_ctrl & RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT;
+		if (get_ts) {
+			struct skb_shared_hwtstamps *shhwtstamps;
+			struct timespec64 ts;
+
+			shhwtstamps = skb_hwtstamps(skb);
+			memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+			ts.tv_sec = (u64)le32_to_cpu(desc->ts_sec);
+			ts.tv_nsec = le32_to_cpu(desc->ts_nsec & 0x3FFFFFFF);
+			shhwtstamps->hwtstamp = timespec64_to_ktime(ts);
+		}
 		skb_put(skb, pkt_len);
 		skb->protocol = eth_type_trans(skb, ndev);
 		netif_receive_skb(skb);
@@ -700,6 +719,15 @@ static int rswitch_tx_free(struct net_device *ndev, bool free_txed_only)
 		size = le16_to_cpu(desc->info_ds) & TX_DS;
 		skb = c->skb[entry];
 		if (skb) {
+			if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
+				struct skb_shared_hwtstamps shhwtstamps;
+				struct timespec64 ts;
+
+				rswitch_get_timestamp(rdev->priv, &ts);
+				memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+				shhwtstamps.hwtstamp = timespec64_to_ktime(ts);
+				skb_tstamp_tx(skb, &shhwtstamps);
+			}
 			dma_addr = le32_to_cpu(desc->dptrl) |
 				   ((__le64)le32_to_cpu(desc->dptrh) << 32);
 			dma_unmap_single(ndev->dev.parent, dma_addr,
@@ -1322,6 +1350,13 @@ static int rswitch_start_xmit(struct sk_buff *skb, struct net_device *ndev)
 	desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
 	desc->info_ds = cpu_to_le16(skb->len);
 
+	if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
+		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+		rdev->ts_tag++;
+		desc->info1 = (rdev->ts_tag << 8) | BIT(3);
+	}
+	skb_tx_timestamp(skb);
+
 	dma_wmb();
 
 	desc->die_dt = DT_FSINGLE | DIE;
@@ -1338,6 +1373,75 @@ static struct net_device_stats *rswitch_get_stats(struct net_device *ndev)
 	return &ndev->stats;
 }
 
+static int rswitch_hwstamp_get(struct net_device *ndev, struct ifreq *req)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rcar_gen4_ptp_private *ptp_priv;
+	struct hwtstamp_config config;
+
+	ptp_priv = rdev->priv->ptp_priv;
+
+	config.flags = 0;
+	config.tx_type = ptp_priv->tstamp_tx_ctrl ? HWTSTAMP_TX_ON :
+						    HWTSTAMP_TX_OFF;
+	switch (ptp_priv->tstamp_rx_ctrl & RCAR_GEN4_RXTSTAMP_TYPE) {
+	case RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT:
+		config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+		break;
+	case RCAR_GEN4_RXTSTAMP_TYPE_ALL:
+		config.rx_filter = HWTSTAMP_FILTER_ALL;
+		break;
+	default:
+		config.rx_filter = HWTSTAMP_FILTER_NONE;
+		break;
+	}
+
+	return copy_to_user(req->ifr_data, &config, sizeof(config)) ? -EFAULT : 0;
+}
+
+static int rswitch_hwstamp_set(struct net_device *ndev, struct ifreq *req)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	u32 tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED;
+	struct hwtstamp_config config;
+	u32 tstamp_tx_ctrl;
+
+	if (copy_from_user(&config, req->ifr_data, sizeof(config)))
+		return -EFAULT;
+
+	if (config.flags)
+		return -EINVAL;
+
+	switch (config.tx_type) {
+	case HWTSTAMP_TX_OFF:
+		tstamp_tx_ctrl = 0;
+		break;
+	case HWTSTAMP_TX_ON:
+		tstamp_tx_ctrl = RCAR_GEN4_TXTSTAMP_ENABLED;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	switch (config.rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		tstamp_rx_ctrl = 0;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+		tstamp_rx_ctrl |= RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT;
+		break;
+	default:
+		config.rx_filter = HWTSTAMP_FILTER_ALL;
+		tstamp_rx_ctrl |= RCAR_GEN4_RXTSTAMP_TYPE_ALL;
+		break;
+	}
+
+	rdev->priv->ptp_priv->tstamp_tx_ctrl = tstamp_tx_ctrl;
+	rdev->priv->ptp_priv->tstamp_rx_ctrl = tstamp_rx_ctrl;
+
+	return copy_to_user(req->ifr_data, &config, sizeof(config)) ? -EFAULT : 0;
+}
+
 static int rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
 {
 	struct phy_device *phydev = ndev->phydev;
@@ -1347,7 +1451,14 @@ static int rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd
 	if (!phydev)
 		return -ENODEV;
 
-	return phy_mii_ioctl(phydev, req, cmd);
+	switch (cmd) {
+	case SIOCGHWTSTAMP:
+		return rswitch_hwstamp_get(ndev, req);
+	case SIOCSHWTSTAMP:
+		return rswitch_hwstamp_set(ndev, req);
+	default:
+		return phy_mii_ioctl(phydev, req, cmd);
+	}
 }
 
 static const struct net_device_ops rswitch_netdev_ops = {
@@ -1360,6 +1471,27 @@ static const struct net_device_ops rswitch_netdev_ops = {
 	.ndo_set_mac_address = eth_mac_addr,
 };
 
+static int rswitch_get_ts_info(struct net_device *ndev, struct ethtool_ts_info *info)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	info->phc_index = ptp_clock_index(rdev->priv->ptp_priv->clock);
+	info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+				SOF_TIMESTAMPING_RX_SOFTWARE |
+				SOF_TIMESTAMPING_SOFTWARE |
+				SOF_TIMESTAMPING_TX_HARDWARE |
+				SOF_TIMESTAMPING_RX_HARDWARE |
+				SOF_TIMESTAMPING_RAW_HARDWARE;
+	info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
+	info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | BIT(HWTSTAMP_FILTER_ALL);
+
+	return 0;
+}
+
+static const struct ethtool_ops rswitch_ethtool_ops = {
+	.get_ts_info = rswitch_get_ts_info,
+};
+
 static const struct of_device_id renesas_eth_sw_of_table[] = {
 	{ .compatible = "renesas,r8a779f0-ether-switch", },
 	{ }
@@ -1407,6 +1539,7 @@ static int rswitch_device_alloc(struct rswitch_private *priv, int index)
 	ndev->base_addr = (unsigned long)rdev->addr;
 	snprintf(ndev->name, IFNAMSIZ, "tsn%d", index);
 	ndev->netdev_ops = &rswitch_netdev_ops;
+	ndev->ethtool_ops = &rswitch_ethtool_ops;
 
 	netif_napi_add(ndev, &rdev->napi, rswitch_poll, 64);
 
@@ -1491,6 +1624,11 @@ static int rswitch_init(struct rswitch_private *priv)
 
 	rswitch_fwd_init(priv);
 
+	err = rcar_gen4_ptp_register(priv->ptp_priv, RCAR_GEN4_PTP_REG_LAYOUT_S4,
+				     RCAR_GEN4_PTP_CLOCK_S4);
+	if (err < 0)
+		goto err_ptp_register;
+
 	err = rswitch_gwca_request_irqs(priv);
 	if (err < 0)
 		goto err_gwca_request_irq;
@@ -1518,6 +1656,9 @@ static int rswitch_init(struct rswitch_private *priv)
 
 err_gwca_hw_init:
 err_gwca_request_irq:
+	rcar_gen4_ptp_unregister(priv->ptp_priv);
+
+err_ptp_register:
 	for (i = 0; i < RSWITCH_NUM_NDEV; i++)
 		rswitch_device_free(priv, i);
 
@@ -1543,6 +1684,10 @@ static int renesas_eth_sw_probe(struct platform_device *pdev)
 	if (!priv)
 		return -ENOMEM;
 
+	priv->ptp_priv = rcar_gen4_ptp_alloc(pdev);
+	if (!priv->ptp_priv)
+		return -ENOMEM;
+
 	priv->tsn_clk = devm_clk_get(&pdev->dev, "tsn");
 	if (IS_ERR(priv->tsn_clk)) {
 		dev_err(&pdev->dev, "Failed to get tsn clock: %ld\n", PTR_ERR(priv->tsn_clk));
@@ -1555,6 +1700,8 @@ static int renesas_eth_sw_probe(struct platform_device *pdev)
 	if (IS_ERR(priv->addr))
 		return PTR_ERR(priv->addr);
 
+	priv->ptp_priv->addr = priv->addr + RCAR_GEN4_GPTP_OFFSET_S4;
+
 	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40));
 	if (ret < 0) {
 		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
@@ -1587,6 +1734,7 @@ static void rswitch_deinit(struct rswitch_private *priv)
 	int i;
 
 	rswitch_gwca_hw_deinit(priv);
+	rcar_gen4_ptp_unregister(priv->ptp_priv);
 
 	for (i = 0; i < RSWITCH_NUM_NDEV; i++) {
 		struct rswitch_device *rdev = priv->rdev[i];
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index 3dd62f82abb1..bb728ccd5c86 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -8,6 +8,7 @@
 #define __RSWITCH_H__
 
 #include <linux/platform_device.h>
+#include "rcar_gen4_ptp.h"
 
 #define RSWITCH_NUM_HW		5
 #define RSWITCH_MAX_NUM_ETHA	3
@@ -947,6 +948,7 @@ struct rswitch_mfwd {
 struct rswitch_private {
 	struct platform_device *pdev;
 	void __iomem *addr;
+	struct rcar_gen4_ptp_private *ptp_priv;
 	struct rswitch_desc *desc_bat;
 	dma_addr_t desc_bat_dma;
 	u32 desc_bat_size;
-- 
2.25.1


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

* Re: [PATCH v3 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch
  2022-09-22  5:28 ` [PATCH v3 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch Yoshihiro Shimoda
@ 2022-09-22  7:39   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 17+ messages in thread
From: Krzysztof Kozlowski @ 2022-09-22  7:39 UTC (permalink / raw)
  To: Yoshihiro Shimoda, davem, edumazet, kuba, pabeni, robh+dt,
	krzysztof.kozlowski+dt
  Cc: netdev, devicetree, linux-renesas-soc

On 22/09/2022 07:28, Yoshihiro Shimoda wrote:
> Document Renesas Etherent Switch for R-Car S4-8 (r8a779f0).
> 
> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> ---
>  .../bindings/net/renesas,etherswitch.yaml     | 286 ++++++++++++++++++
>  1 file changed, 286 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/net/renesas,etherswitch.yaml
> 
> diff --git a/Documentation/devicetree/bindings/net/renesas,etherswitch.yaml b/Documentation/devicetree/bindings/net/renesas,etherswitch.yaml
> new file mode 100644
> index 000000000000..988d14f5c54e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/renesas,etherswitch.yaml
> @@ -0,0 +1,286 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/renesas,etherswitch.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#

All the previous comments from v2 are still applicable (as I replied
after you sent this).

Please fix this or respond to my comments on v2.

Best regards,
Krzysztof


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

* Re: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-22  5:28 ` [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
@ 2022-09-23  2:43   ` Jakub Kicinski
  2022-09-26  6:30     ` Yoshihiro Shimoda
  2022-09-23 13:11   ` Andrew Lunn
  1 sibling, 1 reply; 17+ messages in thread
From: Jakub Kicinski @ 2022-09-23  2:43 UTC (permalink / raw)
  To: Yoshihiro Shimoda
  Cc: davem, edumazet, pabeni, robh+dt, krzysztof.kozlowski+dt, netdev,
	devicetree, linux-renesas-soc

On Thu, 22 Sep 2022 14:28:02 +0900 Yoshihiro Shimoda wrote:
> Add Renesas Ethernet Switch driver for R-Car S4-8 to be used as an
> ethernet controller.

Looks like we have a lot of sparse warnings here.

Please try building the module with W=1 C=1 and make sure 
it builds cleanly.

https://sparse.docs.kernel.org/en/latest/


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

* Re: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-22  5:28 ` [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
  2022-09-23  2:43   ` Jakub Kicinski
@ 2022-09-23 13:11   ` Andrew Lunn
  2022-09-26  8:12     ` Yoshihiro Shimoda
  1 sibling, 1 reply; 17+ messages in thread
From: Andrew Lunn @ 2022-09-23 13:11 UTC (permalink / raw)
  To: Yoshihiro Shimoda
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

> +/* Forwarding engine block (MFWD) */
> +static void rswitch_fwd_init(struct rswitch_private *priv)
> +{
> +	int i;
> +
> +	for (i = 0; i < RSWITCH_NUM_HW; i++) {
> +		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
> +		iowrite32(0, priv->addr + FWPBFC(i));
> +	}

What is RSWITCH_NUM_HW?

> +
> +	for (i = 0; i < RSWITCH_NUM_ETHA; i++) {

RSWITCH_NUM_ETHA appears to be the number of ports?

> +static void rswitch_gwca_set_rate_limit(struct rswitch_private *priv, int rate)
> +{
> +	u32 gwgrlulc, gwgrlc;
> +
> +	switch (rate) {
> +	case 1000:
> +		gwgrlulc = 0x0000005f;
> +		gwgrlc = 0x00010260;
> +		break;
> +	default:
> +		dev_err(&priv->pdev->dev, "%s: This rate is not supported (%d)\n", __func__, rate);
> +		return;
> +	}

Is this setting the Mbps between the switch matrix and the CPU? Why
limit the rate? Especially if you have 3 ports, would not 3000 make
sense?

> +static void rswitch_get_data_irq_status(struct rswitch_private *priv, u32 *dis)
> +{
> +	int i;
> +
> +	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
> +		dis[i] = ioread32(priv->addr + GWDIS(i));
> +		dis[i] &= ioread32(priv->addr + GWDIE(i));
> +	}
> +}
> +
> +static void rswitch_enadis_data_irq(struct rswitch_private *priv, int index, bool enable)
> +{
> +	u32 offs = enable ? GWDIE(index / 32) : GWDID(index / 32);
> +
> +	iowrite32(BIT(index % 32), priv->addr + offs);
> +}
> +
> +static void rswitch_ack_data_irq(struct rswitch_private *priv, int index)
> +{
> +	u32 offs = GWDIS(index / 32);
> +
> +	iowrite32(BIT(index % 32), priv->addr + offs);
> +}
> +
> +static bool rswitch_is_chain_rxed(struct rswitch_gwca_chain *c)
> +{
> +	struct rswitch_ext_ts_desc *desc;
> +	int entry;
> +
> +	entry = c->dirty % c->num_ring;
> +	desc = &c->ts_ring[entry];
> +
> +	if ((desc->die_dt & DT_MASK) != DT_FEMPTY)
> +		return true;
> +
> +	return false;

Is a chain a queue? Also known as a ring? The naming is different to
most drivers, which is making this harder to understand. Ideally, you
want to follow the basic naming the majority of other drivers use.

> +}
> +
> +static int rswitch_gwca_chain_alloc_skb(struct rswitch_gwca_chain *c,
> +					int start, int num)
> +{
> +	int i, index;
> +
> +	for (i = start; i < (start + num); i++) {
> +		index = i % c->num_ring;

Why this? Would it not make more sense to validate that start + num <
num_ring? It seems like bad parameters passed here could destroy some
other skb in the ring?

More naming... Here you use num_ring, not num_chain. Try to be
consistent. Also, num_ring makes my think of ring 7 of 9 rings. When
this actually appears to be the size of the ring. So c->ring_size
would be a better name.

> +		if (c->skb[index])
> +			continue;
> +		c->skb[index] = dev_alloc_skb(PKT_BUF_SZ + RSWITCH_ALIGN - 1);
> +		if (!c->skb[index])
> +			goto err;
> +		skb_reserve(c->skb[index], NET_IP_ALIGN);

netdev_alloc_skb_ip_align()?

> +static void rswitch_gwca_chain_free(struct net_device *ndev,
> +				    struct rswitch_gwca_chain *c)
> +{
> +	int i;
> +
> +	if (c->gptp) {
> +		dma_free_coherent(ndev->dev.parent,
> +				  sizeof(struct rswitch_ext_ts_desc) *
> +				  (c->num_ring + 1), c->ts_ring, c->ring_dma);
> +		c->ts_ring = NULL;
> +	} else {
> +		dma_free_coherent(ndev->dev.parent,
> +				  sizeof(struct rswitch_ext_desc) *
> +				  (c->num_ring + 1), c->ring, c->ring_dma);
> +		c->ring = NULL;
> +	}
> +
> +	if (!c->dir_tx) {
> +		for (i = 0; i < c->num_ring; i++)
> +			dev_kfree_skb(c->skb[i]);
> +	}
> +
> +	kfree(c->skb);
> +	c->skb = NULL;

When i see code like this, i wonder why an API call like
dev_kfree_skb() is not being used. I would suggest reaming this to
something other than skb, which has a very well understood meaning.

> +static bool rswitch_rx(struct net_device *ndev, int *quota)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct rswitch_gwca_chain *c = rdev->rx_chain;
> +	int entry = c->cur % c->num_ring;
> +	struct rswitch_ext_ts_desc *desc;
> +	int limit, boguscnt, num, ret;
> +	struct sk_buff *skb;
> +	dma_addr_t dma_addr;
> +	u16 pkt_len;
> +
> +	boguscnt = min_t(int, c->dirty + c->num_ring - c->cur, *quota);
> +	limit = boguscnt;
> +
> +	desc = &c->ts_ring[entry];
> +	while ((desc->die_dt & DT_MASK) != DT_FEMPTY) {
> +		dma_rmb();
> +		pkt_len = le16_to_cpu(desc->info_ds) & RX_DS;
> +		if (--boguscnt < 0)
> +			break;

Why the barrier, read the length and then decide to break out of the
loop?

> +static int rswitch_open(struct net_device *ndev)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct device_node *phy;
> +	int err = 0;
> +
> +	if (rdev->etha) {

Can this happen? What would a netdev without an etha mean?

    Andrew

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

* RE: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-23  2:43   ` Jakub Kicinski
@ 2022-09-26  6:30     ` Yoshihiro Shimoda
  0 siblings, 0 replies; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-26  6:30 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, edumazet, pabeni, robh+dt, krzysztof.kozlowski+dt, netdev,
	devicetree, linux-renesas-soc

Hi Jakub,

> From: Jakub Kicinski, Sent: Friday, September 23, 2022 11:44 AM
> 
> On Thu, 22 Sep 2022 14:28:02 +0900 Yoshihiro Shimoda wrote:
> > Add Renesas Ethernet Switch driver for R-Car S4-8 to be used as an
> > ethernet controller.
> 
> Looks like we have a lot of sparse warnings here.
> 
> Please try building the module with W=1 C=1 and make sure
> it builds cleanly.

I got it. I'll fix the warnings.

Best regards,
Yoshihiro Shimoda


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

* RE: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-23 13:11   ` Andrew Lunn
@ 2022-09-26  8:12     ` Yoshihiro Shimoda
  2022-09-26 19:17       ` Andrew Lunn
  0 siblings, 1 reply; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-26  8:12 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

Hi Andrew,

> From: Andrew Lunn, Sent: Friday, September 23, 2022 10:12 PM
> 
> > +/* Forwarding engine block (MFWD) */
> > +static void rswitch_fwd_init(struct rswitch_private *priv)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < RSWITCH_NUM_HW; i++) {
> > +		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
> > +		iowrite32(0, priv->addr + FWPBFC(i));
> > +	}
> 
> What is RSWITCH_NUM_HW?

I think the name is unclear...
Anyway, this hardware has 3 ethernet ports and 2 CPU ports.
So that the RSWITCH_NUM_HW is 5. Perhaps, RSWITCH_NUM_ALL_PORTS
is better name.

Perhaps, since the current driver supports 1 ethernet port and 1 CPU port only,
I should modify this driver for the current condition strictly.

> > +
> > +	for (i = 0; i < RSWITCH_NUM_ETHA; i++) {
> 
> RSWITCH_NUM_ETHA appears to be the number of ports?

Yes, this is number of ethernet ports.

> > +static void rswitch_gwca_set_rate_limit(struct rswitch_private *priv, int rate)
> > +{
> > +	u32 gwgrlulc, gwgrlc;
> > +
> > +	switch (rate) {
> > +	case 1000:
> > +		gwgrlulc = 0x0000005f;
> > +		gwgrlc = 0x00010260;
> > +		break;
> > +	default:
> > +		dev_err(&priv->pdev->dev, "%s: This rate is not supported (%d)\n", __func__, rate);
> > +		return;
> > +	}
> 
> Is this setting the Mbps between the switch matrix and the CPU? Why
> limit the rate? Especially if you have 3 ports, would not 3000 make
> sense?

This is needed to avoid about 10% packets loss when the hardware sends data
because the hardware will send much data if the limit is not set.

> > +static void rswitch_get_data_irq_status(struct rswitch_private *priv, u32 *dis)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
> > +		dis[i] = ioread32(priv->addr + GWDIS(i));
> > +		dis[i] &= ioread32(priv->addr + GWDIE(i));
> > +	}
> > +}
> > +
> > +static void rswitch_enadis_data_irq(struct rswitch_private *priv, int index, bool enable)
> > +{
> > +	u32 offs = enable ? GWDIE(index / 32) : GWDID(index / 32);
> > +
> > +	iowrite32(BIT(index % 32), priv->addr + offs);
> > +}
> > +
> > +static void rswitch_ack_data_irq(struct rswitch_private *priv, int index)
> > +{
> > +	u32 offs = GWDIS(index / 32);
> > +
> > +	iowrite32(BIT(index % 32), priv->addr + offs);
> > +}
> > +
> > +static bool rswitch_is_chain_rxed(struct rswitch_gwca_chain *c)
> > +{
> > +	struct rswitch_ext_ts_desc *desc;
> > +	int entry;
> > +
> > +	entry = c->dirty % c->num_ring;
> > +	desc = &c->ts_ring[entry];
> > +
> > +	if ((desc->die_dt & DT_MASK) != DT_FEMPTY)
> > +		return true;
> > +
> > +	return false;
> 
> Is a chain a queue? Also known as a ring?

Yes.

> The naming is different to
> most drivers, which is making this harder to understand. Ideally, you
> want to follow the basic naming the majority of other drivers use.

I got it. I'll rename them.
 
> > +}
> > +
> > +static int rswitch_gwca_chain_alloc_skb(struct rswitch_gwca_chain *c,
> > +					int start, int num)
> > +{
> > +	int i, index;
> > +
> > +	for (i = start; i < (start + num); i++) {
> > +		index = i % c->num_ring;
> 
> Why this? Would it not make more sense to validate that start + num <
> num_ring? It seems like bad parameters passed here could destroy some
> other skb in the ring?

The descriptor indexes (c->cur and c->dirty) are just counters so that
the index is always calculated by that. This implementation is similar
with other drivers/net/ethernet/renesas/ drivers. However, as you mentioned
above, this is not majority, I think...

Also, I realized that the function will cause an issue because the types of
c->cur and c->dirty are u32, but the type of start is int.

I'll fix the indexes handling.

> More naming... Here you use num_ring, not num_chain. Try to be
> consistent. Also, num_ring makes my think of ring 7 of 9 rings. When
> this actually appears to be the size of the ring. So c->ring_size
> would be a better name.

Yes, the num_ring means the size of the ring. So, I'll rename it as your suggestion.

> > +		if (c->skb[index])
> > +			continue;
> > +		c->skb[index] = dev_alloc_skb(PKT_BUF_SZ + RSWITCH_ALIGN - 1);
> > +		if (!c->skb[index])
> > +			goto err;
> > +		skb_reserve(c->skb[index], NET_IP_ALIGN);
> 
> netdev_alloc_skb_ip_align()?

Yes. I'll use it. Thanks!

> > +static void rswitch_gwca_chain_free(struct net_device *ndev,
> > +				    struct rswitch_gwca_chain *c)
> > +{
> > +	int i;
> > +
> > +	if (c->gptp) {
> > +		dma_free_coherent(ndev->dev.parent,
> > +				  sizeof(struct rswitch_ext_ts_desc) *
> > +				  (c->num_ring + 1), c->ts_ring, c->ring_dma);
> > +		c->ts_ring = NULL;
> > +	} else {
> > +		dma_free_coherent(ndev->dev.parent,
> > +				  sizeof(struct rswitch_ext_desc) *
> > +				  (c->num_ring + 1), c->ring, c->ring_dma);
> > +		c->ring = NULL;
> > +	}
> > +
> > +	if (!c->dir_tx) {
> > +		for (i = 0; i < c->num_ring; i++)
> > +			dev_kfree_skb(c->skb[i]);
> > +	}
> > +
> > +	kfree(c->skb);
> > +	c->skb = NULL;
> 
> When i see code like this, i wonder why an API call like
> dev_kfree_skb() is not being used. I would suggest reaming this to
> something other than skb, which has a very well understood meaning.

Perhaps, c->skbs is better name than just c->skb.

> > +static bool rswitch_rx(struct net_device *ndev, int *quota)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct rswitch_gwca_chain *c = rdev->rx_chain;
> > +	int entry = c->cur % c->num_ring;
> > +	struct rswitch_ext_ts_desc *desc;
> > +	int limit, boguscnt, num, ret;
> > +	struct sk_buff *skb;
> > +	dma_addr_t dma_addr;
> > +	u16 pkt_len;
> > +
> > +	boguscnt = min_t(int, c->dirty + c->num_ring - c->cur, *quota);
> > +	limit = boguscnt;
> > +
> > +	desc = &c->ts_ring[entry];
> > +	while ((desc->die_dt & DT_MASK) != DT_FEMPTY) {
> > +		dma_rmb();
> > +		pkt_len = le16_to_cpu(desc->info_ds) & RX_DS;
> > +		if (--boguscnt < 0)
> > +			break;
> 
> Why the barrier, read the length and then decide to break out of the
> loop?

Thank you for pointed it out. I should decrement/check boguscnt
before the barrier and read the length.

> > +static int rswitch_open(struct net_device *ndev)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct device_node *phy;
> > +	int err = 0;
> > +
> > +	if (rdev->etha) {
> 
> Can this happen? What would a netdev without an etha mean?

This cannot happen now. So, I'll drop it.
(I intended to create a netdev without an etha as a virtual device.
 But the current driver doesn't have such a feature.)

Best regards,
Yoshihiro Shimoda

>     Andrew

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

* Re: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-26  8:12     ` Yoshihiro Shimoda
@ 2022-09-26 19:17       ` Andrew Lunn
  2022-09-27  5:59         ` Yoshihiro Shimoda
  0 siblings, 1 reply; 17+ messages in thread
From: Andrew Lunn @ 2022-09-26 19:17 UTC (permalink / raw)
  To: Yoshihiro Shimoda
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

On Mon, Sep 26, 2022 at 08:12:14AM +0000, Yoshihiro Shimoda wrote:
> Hi Andrew,
> 
> > From: Andrew Lunn, Sent: Friday, September 23, 2022 10:12 PM
> > 
> > > +/* Forwarding engine block (MFWD) */
> > > +static void rswitch_fwd_init(struct rswitch_private *priv)
> > > +{
> > > +	int i;
> > > +
> > > +	for (i = 0; i < RSWITCH_NUM_HW; i++) {
> > > +		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
> > > +		iowrite32(0, priv->addr + FWPBFC(i));
> > > +	}
> > 
> > What is RSWITCH_NUM_HW?
> 
> I think the name is unclear...
> Anyway, this hardware has 3 ethernet ports and 2 CPU ports.
> So that the RSWITCH_NUM_HW is 5. Perhaps, RSWITCH_NUM_ALL_PORTS
> is better name.

How do the CPU ports differ to the other ports? When you mention CPU
ports, it makes me wonder if this should be a DSA driver?

Is there a public data sheet for this device?

> Perhaps, since the current driver supports 1 ethernet port and 1 CPU port only,
> I should modify this driver for the current condition strictly.

I would suggest you support all three user ports. For an initial
driver you don't need to support any sort of acceleration. You don't
need any hardware bridging etc. That can be added later. Just three
separated ports.

> > > +
> > > +	for (i = 0; i < RSWITCH_NUM_ETHA; i++) {
> > 
> > RSWITCH_NUM_ETHA appears to be the number of ports?
> 
> Yes, this is number of ethernet ports.

In the DSA world we call these user ports. 

> > > +	kfree(c->skb);
> > > +	c->skb = NULL;
> > 
> > When i see code like this, i wonder why an API call like
> > dev_kfree_skb() is not being used. I would suggest reaming this to
> > something other than skb, which has a very well understood meaning.
> 
> Perhaps, c->skbs is better name than just c->skb.

Yes, that is O.K.

     Andrew

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

* RE: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-26 19:17       ` Andrew Lunn
@ 2022-09-27  5:59         ` Yoshihiro Shimoda
  2022-09-27 12:54           ` Andrew Lunn
  0 siblings, 1 reply; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-27  5:59 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

Hi Andrew,

> From: Andrew Lunn, Sent: Tuesday, September 27, 2022 4:18 AM
> 
> On Mon, Sep 26, 2022 at 08:12:14AM +0000, Yoshihiro Shimoda wrote:
> > Hi Andrew,
> >
> > > From: Andrew Lunn, Sent: Friday, September 23, 2022 10:12 PM
> > >
> > > > +/* Forwarding engine block (MFWD) */
> > > > +static void rswitch_fwd_init(struct rswitch_private *priv)
> > > > +{
> > > > +	int i;
> > > > +
> > > > +	for (i = 0; i < RSWITCH_NUM_HW; i++) {
> > > > +		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
> > > > +		iowrite32(0, priv->addr + FWPBFC(i));
> > > > +	}
> > >
> > > What is RSWITCH_NUM_HW?
> >
> > I think the name is unclear...
> > Anyway, this hardware has 3 ethernet ports and 2 CPU ports.
> > So that the RSWITCH_NUM_HW is 5. Perhaps, RSWITCH_NUM_ALL_PORTS
> > is better name.
> 
> How do the CPU ports differ to the other ports? When you mention CPU
> ports, it makes me wonder if this should be a DSA driver?

I compared a DSA diagram [1] and this Ethernet Switch and then
this switch differs than the DSA diagram:
- This switch has a feature which accesses DRAM directly like an "ethernet controller".
  I called this feature as "cpu port", but it might be incorrect.
- This switch has doesn't have any "control path". Instead of that, this switch
  is controled by registers via APB (internal bus) directly.

So, IIUC, this should not be a DSA driver.

[1] https://bootlin.com/blog/tag/dsa/

> Is there a public data sheet for this device?

Unfortunately, we have no public data sheet for this device.
But, I tried to figure this switch diagram about control/data paths as below:

Control path:
+--- R-Car S4-8 SoC -------------------------+
|                                            |
| CPU ---(APB bus)---+--- Ethernet Switch ---|---(MDIO)--------------+
|                    |                       |                       |
|                    +--- Ethernet SERDES    |              External Ethernet PHY --- RJ45
|                                            |
+--------------------------------------------+
Notes: The switch and SERDES have 3 ports of MDIO and SGMII.

Data Path:
+--- R-Car S4-8 SoC ---------------------------------------------------------+
|                                                                            |
| CPU ---(AXI bus)---+--- DRAM        +--------+                             |
|                    +---(cpu port)---|        |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
|                    |                | Switch |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
|                    +---(cpu port)---|        |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
|                                     +--------|                             |
+----------------------------------------------------------------------------+

The current driver only supports one of MDIO, cpu port and ethernet port, and it acts as an ethernet device.

> > Perhaps, since the current driver supports 1 ethernet port and 1 CPU port only,
> > I should modify this driver for the current condition strictly.
> 
> I would suggest you support all three user ports. For an initial
> driver you don't need to support any sort of acceleration. You don't
> need any hardware bridging etc. That can be added later. Just three
> separated ports.

Thank you for your suggestion. However, supporting three user ports
is required to modify an external ethernet PHY driver.
(For now, a boot loader initialized the PHY, but one port only.)

The PHY is 88E2110 on my environment, so Linux has a driver in
drivers/net/phy/marvell10g.c. However, I guess this is related to
configuration of the PHY chip on the board, it needs to change
the host 7interface mode, but the driver doesn't support it for now.

So, I'd like to support three user ports later if possible...

> > > > +
> > > > +	for (i = 0; i < RSWITCH_NUM_ETHA; i++) {
> > >
> > > RSWITCH_NUM_ETHA appears to be the number of ports?
> >
> > Yes, this is number of ethernet ports.
> 
> In the DSA world we call these user ports.

I got it.
However, when I read the dsa document of Linux kernel,
it seems to call DSA ports. Perhaps, I could misunderstand the document though...
https://docs.kernel.org/networking/dsa/dsa.html

> > > > +	kfree(c->skb);
> > > > +	c->skb = NULL;
> > >
> > > When i see code like this, i wonder why an API call like
> > > dev_kfree_skb() is not being used. I would suggest reaming this to
> > > something other than skb, which has a very well understood meaning.
> >
> > Perhaps, c->skbs is better name than just c->skb.
> 
> Yes, that is O.K.

I got it. Thanks!

Best regards,
Yoshihiro Shimoda

>      Andrew

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

* Re: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-27  5:59         ` Yoshihiro Shimoda
@ 2022-09-27 12:54           ` Andrew Lunn
  2022-09-28  5:53             ` Yoshihiro Shimoda
  0 siblings, 1 reply; 17+ messages in thread
From: Andrew Lunn @ 2022-09-27 12:54 UTC (permalink / raw)
  To: Yoshihiro Shimoda
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

> > How do the CPU ports differ to the other ports? When you mention CPU
> > ports, it makes me wonder if this should be a DSA driver?
> 
> I compared a DSA diagram [1] and this Ethernet Switch and then
> this switch differs than the DSA diagram:
> - This switch has a feature which accesses DRAM directly like an "ethernet controller".
>   I called this feature as "cpu port", but it might be incorrect.
> - This switch has doesn't have any "control path". Instead of that, this switch
>   is controled by registers via APB (internal bus) directly.
> 
> So, IIUC, this should not be a DSA driver.
> 
> [1] https://bootlin.com/blog/tag/dsa/
> 
> > Is there a public data sheet for this device?
> 
> Unfortunately, we have no public data sheet for this device.
> But, I tried to figure this switch diagram about control/data paths as below:
> 
> Control path:
> +--- R-Car S4-8 SoC -------------------------+
> |                                            |
> | CPU ---(APB bus)---+--- Ethernet Switch ---|---(MDIO)--------------+
> |                    |                       |                       |
> |                    +--- Ethernet SERDES    |              External Ethernet PHY --- RJ45
> |                                            |
> +--------------------------------------------+
> Notes: The switch and SERDES have 3 ports of MDIO and SGMII.
> 
> Data Path:
> +--- R-Car S4-8 SoC ---------------------------------------------------------+
> |                                                                            |
> | CPU ---(AXI bus)---+--- DRAM        +--------+                             |
> |                    +---(cpu port)---|        |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
> |                    |                | Switch |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
> |                    +---(cpu port)---|        |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
> |                                     +--------|                             |
> +----------------------------------------------------------------------------+

This device is somewhere between a DSA switch and a pure switchdev
switch. It probably could be modelled as a DSA switch.

For a pure switchdev switch, you have a set of DMA channels per user
port. So with 3 user ports, i would expect there to be three sets of
RX and TX DMA rings. Frames from the CPU would go directly to the user
ports, bypassing the switch.

For this hardware, how are the rings organised? Are the rings for the
CPU ports, or for the user ports? How do you direct a frame from the
CPU out a specific user port? Via the DMA ring you place it into, or
do you need a tag on the frame to indicate its egress port?

The memory mapping of the registers is not really an issue. The
B52/SF2 can have memory mapped registers.

> The current driver only supports one of MDIO, cpu port and ethernet port, and it acts as an ethernet device.
> 
> > > Perhaps, since the current driver supports 1 ethernet port and 1 CPU port only,
> > > I should modify this driver for the current condition strictly.
> > 
> > I would suggest you support all three user ports. For an initial
> > driver you don't need to support any sort of acceleration. You don't
> > need any hardware bridging etc. That can be added later. Just three
> > separated ports.
> 
> Thank you for your suggestion. However, supporting three user ports
> is required to modify an external ethernet PHY driver.
> (For now, a boot loader initialized the PHY, but one port only.)
> 
> The PHY is 88E2110 on my environment, so Linux has a driver in
> drivers/net/phy/marvell10g.c. However, I guess this is related to
> configuration of the PHY chip on the board, it needs to change
> the host 7interface mode, but the driver doesn't support it for now.

Please give us more details. The marvell10g driver will change its
host side depending on the result of the line side negotiation. It
changes the value of phydev->interface to indicate what is it doing on
its host side, and you have some control over what modes it will use
on the host side. You can probably define its initial host side mode
via phy-mode in DT.

Also, relying on the bootloader is a bad idea. People like to change
the bootloader, upgrade it for new features, etc. In the ARM world we
try to avoid any dependency on the bootloader.

> > In the DSA world we call these user ports.
> 
> I got it.
> However, when I read the dsa document of Linux kernel,
> it seems to call DSA ports. Perhaps, I could misunderstand the document though...
> https://docs.kernel.org/networking/dsa/dsa.html

DSA has four classes of ports:

User ports:   connected to the outside world
CPU ports:    connected to the SoC
DSA ports:    connecting between switches. If you have two switches and want
              to combine them into one cluster, you use DSA ports between
	      them.
Unused ports: Physically exist, but are unused in the board design.

       Andrew

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

* RE: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-27 12:54           ` Andrew Lunn
@ 2022-09-28  5:53             ` Yoshihiro Shimoda
  2022-09-28 12:01               ` Andrew Lunn
  0 siblings, 1 reply; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-28  5:53 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

Hi Andrew,

> From: Andrew Lunn, Sent: Tuesday, September 27, 2022 9:54 PM
> 
> > > How do the CPU ports differ to the other ports? When you mention CPU
> > > ports, it makes me wonder if this should be a DSA driver?
> >
> > I compared a DSA diagram [1] and this Ethernet Switch and then
> > this switch differs than the DSA diagram:
> > - This switch has a feature which accesses DRAM directly like an "ethernet controller".
> >   I called this feature as "cpu port", but it might be incorrect.
> > - This switch has doesn't have any "control path". Instead of that, this switch
> >   is controled by registers via APB (internal bus) directly.
> >
> > So, IIUC, this should not be a DSA driver.
> >
> > [1] <snip>
> >
> > > Is there a public data sheet for this device?
> >
> > Unfortunately, we have no public data sheet for this device.
> > But, I tried to figure this switch diagram about control/data paths as below:
> >
> > Control path:
> > +--- R-Car S4-8 SoC -------------------------+
> > |                                            |
> > | CPU ---(APB bus)---+--- Ethernet Switch ---|---(MDIO)--------------+
> > |                    |                       |                       |
> > |                    +--- Ethernet SERDES    |              External Ethernet PHY --- RJ45
> > |                                            |
> > +--------------------------------------------+
> > Notes: The switch and SERDES have 3 ports of MDIO and SGMII.
> >
> > Data Path:
> > +--- R-Car S4-8 SoC ---------------------------------------------------------+
> > |                                                                            |
> > | CPU ---(AXI bus)---+--- DRAM        +--------+                             |
> > |                    +---(cpu port)---|        |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
> > |                    |                | Switch |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
> > |                    +---(cpu port)---|        |---(ether port)--- SERDES ---|---(SGMII)--- PHY --- RJ45
> > |                                     +--------|                             |
> > +----------------------------------------------------------------------------+
> 
> This device is somewhere between a DSA switch and a pure switchdev
> switch. It probably could be modelled as a DSA switch.

I got it.

> For a pure switchdev switch, you have a set of DMA channels per user
> port. So with 3 user ports, i would expect there to be three sets of
> RX and TX DMA rings. Frames from the CPU would go directly to the user
> ports, bypassing the switch.

I got it.

> For this hardware, how are the rings organised? Are the rings for the
> CPU ports, or for the user ports?

The rings are for CPU ports.

> How do you direct a frame from the
> CPU out a specific user port? Via the DMA ring you place it into, or
> do you need a tag on the frame to indicate its egress port?

Via the DMA ring.

> The memory mapping of the registers is not really an issue. The
> B52/SF2 can have memory mapped registers.

I got it.

> > The current driver only supports one of MDIO, cpu port and ethernet port, and it acts as an ethernet device.
> >
> > > > Perhaps, since the current driver supports 1 ethernet port and 1 CPU port only,
> > > > I should modify this driver for the current condition strictly.
> > >
> > > I would suggest you support all three user ports. For an initial
> > > driver you don't need to support any sort of acceleration. You don't
> > > need any hardware bridging etc. That can be added later. Just three
> > > separated ports.
> >
> > Thank you for your suggestion. However, supporting three user ports
> > is required to modify an external ethernet PHY driver.
> > (For now, a boot loader initialized the PHY, but one port only.)
> >
> > The PHY is 88E2110 on my environment, so Linux has a driver in
> > drivers/net/phy/marvell10g.c. However, I guess this is related to
> > configuration of the PHY chip on the board, it needs to change
> > the host 7interface mode, but the driver doesn't support it for now.
> 
> Please give us more details. The marvell10g driver will change its
> host side depending on the result of the line side negotiation. It
> changes the value of phydev->interface to indicate what is it doing on
> its host side, and you have some control over what modes it will use
> on the host side. You can probably define its initial host side mode
> via phy-mode in DT.

I'm sorry, my explanation was completely wrong.
My environment needs to change default MAC speed from 2.5G/5G to 1000M.
The register of 88E2110 is 31.F000.7:6. And sets the register to "10" (1000 Mbps).
(Default value of the register is "11" (Speed controlled by other register).)

> Also, relying on the bootloader is a bad idea. People like to change
> the bootloader, upgrade it for new features, etc. In the ARM world we
> try to avoid any dependency on the bootloader.

Indeed. So, I'll try to modify the marvell driver somehow.

> > > In the DSA world we call these user ports.
> >
> > I got it.
> > However, when I read the dsa document of Linux kernel,
> > it seems to call DSA ports. Perhaps, I could misunderstand the document though...
> >
> 
> DSA has four classes of ports:
> 
> User ports:   connected to the outside world
> CPU ports:    connected to the SoC
> DSA ports:    connecting between switches. If you have two switches and want
>               to combine them into one cluster, you use DSA ports between
> 	      them.
> Unused ports: Physically exist, but are unused in the board design.

Thank you for the detailed. I understood it.

Best regards,
Yoshihiro Shimoda

>        Andrew

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

* Re: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-28  5:53             ` Yoshihiro Shimoda
@ 2022-09-28 12:01               ` Andrew Lunn
  2022-09-29 12:22                 ` Yoshihiro Shimoda
  0 siblings, 1 reply; 17+ messages in thread
From: Andrew Lunn @ 2022-09-28 12:01 UTC (permalink / raw)
  To: Yoshihiro Shimoda
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

> > How do you direct a frame from the
> > CPU out a specific user port? Via the DMA ring you place it into, or
> > do you need a tag on the frame to indicate its egress port?
> 
> Via the DMA ring.

Are there bits in the ring descriptor which indicate the user port?
Can you set these bits to some other value which causes the switch to
use its MAC table to determine the egress interface?

> > > The PHY is 88E2110 on my environment, so Linux has a driver in
> > > drivers/net/phy/marvell10g.c. However, I guess this is related to
> > > configuration of the PHY chip on the board, it needs to change
> > > the host 7interface mode, but the driver doesn't support it for now.
> > 
> > Please give us more details. The marvell10g driver will change its
> > host side depending on the result of the line side negotiation. It
> > changes the value of phydev->interface to indicate what is it doing on
> > its host side, and you have some control over what modes it will use
> > on the host side. You can probably define its initial host side mode
> > via phy-mode in DT.
> 
> I'm sorry, my explanation was completely wrong.
> My environment needs to change default MAC speed from 2.5G/5G to 1000M.
> The register of 88E2110 is 31.F000.7:6. And sets the register to "10" (1000 Mbps).
> (Default value of the register is "11" (Speed controlled by other register).)

Is this the host side speed? The speed of the SERDES between the
switch and the PHY? Normally, the PHY determines this from the line
side. If the line side is using 2.5G, it will set the host side to
2500BaseX. If the line side is 1G, the host side is likely to be
SGMII.

You have already removed speeds you don't support. So the PHY will not
negotiate 2.5G or 5G. It is limited to 1G. So it should always have
the host side as SGMII. This should be enough to make it work.

    Andrew

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

* RE: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-28 12:01               ` Andrew Lunn
@ 2022-09-29 12:22                 ` Yoshihiro Shimoda
  2022-09-29 15:00                   ` Andrew Lunn
  0 siblings, 1 reply; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-29 12:22 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

Hi Andrew,

> From: Andrew Lunn, Sent: Wednesday, September 28, 2022 9:01 PM
> 
> > > How do you direct a frame from the
> > > CPU out a specific user port? Via the DMA ring you place it into, or
> > > do you need a tag on the frame to indicate its egress port?
> >
> > Via the DMA ring.
> 
> Are there bits in the ring descriptor which indicate the user port?
> Can you set these bits to some other value which causes the switch to
> use its MAC table to determine the egress interface?

I'm sorry, I misunderstood the hardware behaviors.

1) From CPU to user port: CPU sends a frame to all user ports.
2) From user port to CPU: each user port sends a frame to each DMA ring.

About the 1) above, the switch can have MAC tables and sends a frame to
a specific user port. However, the driver doesn't support it.

> > > > The PHY is 88E2110 on my environment, so Linux has a driver in
> > > > drivers/net/phy/marvell10g.c. However, I guess this is related to
> > > > configuration of the PHY chip on the board, it needs to change
> > > > the host 7interface mode, but the driver doesn't support it for now.
> > >
> > > Please give us more details. The marvell10g driver will change its
> > > host side depending on the result of the line side negotiation. It
> > > changes the value of phydev->interface to indicate what is it doing on
> > > its host side, and you have some control over what modes it will use
> > > on the host side. You can probably define its initial host side mode
> > > via phy-mode in DT.
> >
> > I'm sorry, my explanation was completely wrong.
> > My environment needs to change default MAC speed from 2.5G/5G to 1000M.
> > The register of 88E2110 is 31.F000.7:6. And sets the register to "10" (1000 Mbps).
> > (Default value of the register is "11" (Speed controlled by other register).)
> 
> Is this the host side speed? The speed of the SERDES between the
> switch and the PHY?

Yes.

> Normally, the PHY determines this from the line
> side. If the line side is using 2.5G, it will set the host side to
> 2500BaseX. If the line side is 1G, the host side is likely to be
> SGMII.
> 
> You have already removed speeds you don't support. So the PHY will not
> negotiate 2.5G or 5G. It is limited to 1G. So it should always have
> the host side as SGMII. This should be enough to make it work.

I see.
However, if I dropped specific registers setting, it doesn't work correctly.
I'll investigate why removing speeds of PHY didn't work.

Best regards,
Yoshihiro Shimoda

>     Andrew

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

* Re: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-29 12:22                 ` Yoshihiro Shimoda
@ 2022-09-29 15:00                   ` Andrew Lunn
  2022-09-30 13:45                     ` Yoshihiro Shimoda
  0 siblings, 1 reply; 17+ messages in thread
From: Andrew Lunn @ 2022-09-29 15:00 UTC (permalink / raw)
  To: Yoshihiro Shimoda
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

On Thu, Sep 29, 2022 at 12:22:15PM +0000, Yoshihiro Shimoda wrote:
> Hi Andrew,
> 
> > From: Andrew Lunn, Sent: Wednesday, September 28, 2022 9:01 PM
> > 
> > > > How do you direct a frame from the
> > > > CPU out a specific user port? Via the DMA ring you place it into, or
> > > > do you need a tag on the frame to indicate its egress port?
> > >
> > > Via the DMA ring.
> > 
> > Are there bits in the ring descriptor which indicate the user port?
> > Can you set these bits to some other value which causes the switch to
> > use its MAC table to determine the egress interface?
> 
> I'm sorry, I misunderstood the hardware behaviors.
> 
> 1) From CPU to user port: CPU sends a frame to all user ports.
> 2) From user port to CPU: each user port sends a frame to each DMA ring.
> 
> About the 1) above, the switch can have MAC tables and sends a frame to
> a specific user port. However, the driver doesn't support it.

In order to make STP and PTP work, you need to be able to send a frame
out a specific port. With STP, that port can also be blocked,
i.e. normal frames are not allowed to be transmitted/received, but
these STP frames are allowed. You also need to know what port an STP
frame was received on.

So the switch probably has a mechanism to send a frame from the CPU
out one specific port. And frames received from a user port and passed
to the CPU should also be identifiable. There are different ways of
doing this. DSA typically has an extra header on the frame, indicating
where it is from/to. Some switches have extra bits in the DMA buffer
descriptor indicating the port.

> However, if I dropped specific registers setting, it doesn't work correctly.
> I'll investigate why removing speeds of PHY didn't work.

It could be the PHY is using SGMII, but your MAC needs 1000BaseX?

   Andrew

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

* RE: [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver
  2022-09-29 15:00                   ` Andrew Lunn
@ 2022-09-30 13:45                     ` Yoshihiro Shimoda
  0 siblings, 0 replies; 17+ messages in thread
From: Yoshihiro Shimoda @ 2022-09-30 13:45 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: davem, edumazet, kuba, pabeni, robh+dt, krzysztof.kozlowski+dt,
	netdev, devicetree, linux-renesas-soc

Hi Andrew,

> From: Andrew Lunn, Sent: Friday, September 30, 2022 12:00 AM
> 
> On Thu, Sep 29, 2022 at 12:22:15PM +0000, Yoshihiro Shimoda wrote:
> > Hi Andrew,
> >
> > > From: Andrew Lunn, Sent: Wednesday, September 28, 2022 9:01 PM
> > >
> > > > > How do you direct a frame from the
> > > > > CPU out a specific user port? Via the DMA ring you place it into, or
> > > > > do you need a tag on the frame to indicate its egress port?
> > > >
> > > > Via the DMA ring.
> > >
> > > Are there bits in the ring descriptor which indicate the user port?
> > > Can you set these bits to some other value which causes the switch to
> > > use its MAC table to determine the egress interface?
> >
> > I'm sorry, I misunderstood the hardware behaviors.
> >
> > 1) From CPU to user port: CPU sends a frame to all user ports.
> > 2) From user port to CPU: each user port sends a frame to each DMA ring.
> >
> > About the 1) above, the switch can have MAC tables and sends a frame to
> > a specific user port. However, the driver doesn't support it.
> 
> In order to make STP and PTP work, you need to be able to send a frame
> out a specific port. With STP, that port can also be blocked,
> i.e. normal frames are not allowed to be transmitted/received, but
> these STP frames are allowed. You also need to know what port an STP
> frame was received on.

I understood it.

> So the switch probably has a mechanism to send a frame from the CPU
> out one specific port. And frames received from a user port and passed
> to the CPU should also be identifiable. There are different ways of
> doing this. DSA typically has an extra header on the frame, indicating
> where it is from/to. Some switches have extra bits in the DMA buffer
> descriptor indicating the port.

I'll looking for such a feature in the switch.

> > However, if I dropped specific registers setting, it doesn't work correctly.
> > I'll investigate why removing speeds of PHY didn't work.
> 
> It could be the PHY is using SGMII, but your MAC needs 1000BaseX?

Both the PHY and MAC is using SGMII. For now, I didn't find why.
So, I'll continue to investigate this issue too.

Best regards,
Yoshihiro Shimoda

>    Andrew

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

end of thread, other threads:[~2022-09-30 13:45 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-22  5:28 [PATCH v3 0/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
2022-09-22  5:28 ` [PATCH v3 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch Yoshihiro Shimoda
2022-09-22  7:39   ` Krzysztof Kozlowski
2022-09-22  5:28 ` [PATCH v3 2/3] net: ethernet: renesas: Add Ethernet Switch driver Yoshihiro Shimoda
2022-09-23  2:43   ` Jakub Kicinski
2022-09-26  6:30     ` Yoshihiro Shimoda
2022-09-23 13:11   ` Andrew Lunn
2022-09-26  8:12     ` Yoshihiro Shimoda
2022-09-26 19:17       ` Andrew Lunn
2022-09-27  5:59         ` Yoshihiro Shimoda
2022-09-27 12:54           ` Andrew Lunn
2022-09-28  5:53             ` Yoshihiro Shimoda
2022-09-28 12:01               ` Andrew Lunn
2022-09-29 12:22                 ` Yoshihiro Shimoda
2022-09-29 15:00                   ` Andrew Lunn
2022-09-30 13:45                     ` Yoshihiro Shimoda
2022-09-22  5:28 ` [PATCH v3 3/3] net: ethernet: renesas: rswitch: Add R-Car Gen4 gPTP support Yoshihiro Shimoda

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.