summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/net/renesas,r8a779f0-ether-switch.yaml262
-rw-r--r--drivers/net/ethernet/renesas/Kconfig11
-rw-r--r--drivers/net/ethernet/renesas/Makefile4
-rw-r--r--drivers/net/ethernet/renesas/rcar_gen4_ptp.c181
-rw-r--r--drivers/net/ethernet/renesas/rcar_gen4_ptp.h72
-rw-r--r--drivers/net/ethernet/renesas/rswitch.c1841
-rw-r--r--drivers/net/ethernet/renesas/rswitch.h973
7 files changed, 3344 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/net/renesas,r8a779f0-ether-switch.yaml b/Documentation/devicetree/bindings/net/renesas,r8a779f0-ether-switch.yaml
new file mode 100644
index 000000000000..e933a1e48d67
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/renesas,r8a779f0-ether-switch.yaml
@@ -0,0 +1,262 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/renesas,r8a779f0-ether-switch.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: 1
+
+ resets:
+ maxItems: 1
+
+ iommus:
+ maxItems: 16
+
+ power-domains:
+ maxItems: 1
+
+ ethernet-ports:
+ type: object
+ additionalProperties: false
+
+ properties:
+ '#address-cells':
+ description: Port number of ETHA (TSNA).
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ "^port@[0-9a-f]+$":
+ type: object
+ $ref: /schemas/net/ethernet-controller.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+ description:
+ Port number of ETHA (TSNA).
+
+ phys:
+ maxItems: 1
+ description:
+ Phandle of an Ethernet SERDES.
+
+ mdio:
+ $ref: /schemas/net/mdio.yaml#
+ unevaluatedProperties: false
+
+ required:
+ - reg
+ - phy-handle
+ - phy-mode
+ - phys
+ - mdio
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - interrupt-names
+ - clocks
+ - 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 1505>;
+ power-domains = <&sysc R8A779F0_PD_ALWAYS_ON>;
+ resets = <&cpg 1505>;
+
+ 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>;
+ };
+ };
+ };
+ };
diff --git a/drivers/net/ethernet/renesas/Kconfig b/drivers/net/ethernet/renesas/Kconfig
index 8008b2f45934..7a5e26b6ea9b 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 PHYLINK
+ 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..592005893464 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 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
new file mode 100644
index 000000000000..20df2020d3e5
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rswitch.c
@@ -0,0 +1,1841 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Renesas Ethernet Switch device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#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/phylink.h>
+#include <linux/phy/phy.h>
+#include <linux/pm_runtime.h>
+#include <linux/rtnetlink.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(RCEC_ACE_DEFAULT | 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_QUEUES; 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 ETHA */
+ for (i = 0; i < RSWITCH_NUM_PORTS; i++) {
+ iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
+ iowrite32(0, priv->addr + FWPBFC(i));
+ }
+
+ for (i = 0; i < RSWITCH_NUM_PORTS; i++) {
+ iowrite32(priv->rdev[i]->rx_queue->index,
+ priv->addr + FWPBFCSDC(GWCA_INDEX, i));
+ iowrite32(BIT(priv->gwca.index), priv->addr + FWPBFC(i));
+ }
+
+ /* For GWCA */
+ iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(priv->gwca.index));
+ iowrite32(FWPC1_DDE, priv->addr + FWPC1(priv->gwca.index));
+ iowrite32(0, priv->addr + FWPBFC(priv->gwca.index));
+ iowrite32(GENMASK(RSWITCH_NUM_PORTS - 1, 0), priv->addr + FWPBFC(priv->gwca.index));
+}
+
+/* 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)
+{
+ 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 u32 rswitch_next_queue_index(struct rswitch_gwca_queue *gq, bool cur, u32 num)
+{
+ u32 index = cur ? gq->cur : gq->dirty;
+
+ if (index + num >= gq->ring_size)
+ index = (index + num) % gq->ring_size;
+ else
+ index += num;
+
+ return index;
+}
+
+static u32 rswitch_get_num_cur_queues(struct rswitch_gwca_queue *gq)
+{
+ if (gq->cur >= gq->dirty)
+ return gq->cur - gq->dirty;
+ else
+ return gq->ring_size - gq->dirty + gq->cur;
+}
+
+static bool rswitch_is_queue_rxed(struct rswitch_gwca_queue *gq)
+{
+ struct rswitch_ext_ts_desc *desc = &gq->ts_ring[gq->dirty];
+
+ if ((desc->desc.die_dt & DT_MASK) != DT_FEMPTY)
+ return true;
+
+ return false;
+}
+
+static int rswitch_gwca_queue_alloc_skb(struct rswitch_gwca_queue *gq,
+ u32 start_index, u32 num)
+{
+ u32 i, index;
+
+ for (i = 0; i < num; i++) {
+ index = (i + start_index) % gq->ring_size;
+ if (gq->skbs[index])
+ continue;
+ gq->skbs[index] = netdev_alloc_skb_ip_align(gq->ndev,
+ PKT_BUF_SZ + RSWITCH_ALIGN - 1);
+ if (!gq->skbs[index])
+ goto err;
+ }
+
+ return 0;
+
+err:
+ for (i--; i >= 0; i--) {
+ index = (i + start_index) % gq->ring_size;
+ dev_kfree_skb(gq->skbs[index]);
+ gq->skbs[index] = NULL;
+ }
+
+ return -ENOMEM;
+}
+
+static void rswitch_gwca_queue_free(struct net_device *ndev,
+ struct rswitch_gwca_queue *gq)
+{
+ int i;
+
+ if (gq->gptp) {
+ dma_free_coherent(ndev->dev.parent,
+ sizeof(struct rswitch_ext_ts_desc) *
+ (gq->ring_size + 1), gq->ts_ring, gq->ring_dma);
+ gq->ts_ring = NULL;
+ } else {
+ dma_free_coherent(ndev->dev.parent,
+ sizeof(struct rswitch_ext_desc) *
+ (gq->ring_size + 1), gq->ring, gq->ring_dma);
+ gq->ring = NULL;
+ }
+
+ if (!gq->dir_tx) {
+ for (i = 0; i < gq->ring_size; i++)
+ dev_kfree_skb(gq->skbs[i]);
+ }
+
+ kfree(gq->skbs);
+ gq->skbs = NULL;
+}
+
+static int rswitch_gwca_queue_alloc(struct net_device *ndev,
+ struct rswitch_private *priv,
+ struct rswitch_gwca_queue *gq,
+ bool dir_tx, bool gptp, int ring_size)
+{
+ int i, bit;
+
+ gq->dir_tx = dir_tx;
+ gq->gptp = gptp;
+ gq->ring_size = ring_size;
+ gq->ndev = ndev;
+
+ gq->skbs = kcalloc(gq->ring_size, sizeof(*gq->skbs), GFP_KERNEL);
+ if (!gq->skbs)
+ return -ENOMEM;
+
+ if (!dir_tx)
+ rswitch_gwca_queue_alloc_skb(gq, 0, gq->ring_size);
+
+ if (gptp)
+ gq->ts_ring = dma_alloc_coherent(ndev->dev.parent,
+ sizeof(struct rswitch_ext_ts_desc) *
+ (gq->ring_size + 1), &gq->ring_dma, GFP_KERNEL);
+ else
+ gq->ring = dma_alloc_coherent(ndev->dev.parent,
+ sizeof(struct rswitch_ext_desc) *
+ (gq->ring_size + 1), &gq->ring_dma, GFP_KERNEL);
+ if (!gq->ts_ring && !gq->ring)
+ goto out;
+
+ i = gq->index / 32;
+ bit = BIT(gq->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_queue_free(ndev, gq);
+
+ return -ENOMEM;
+}
+
+static void rswitch_desc_set_dptr(struct rswitch_desc *desc, dma_addr_t addr)
+{
+ desc->dptrl = cpu_to_le32(lower_32_bits(addr));
+ desc->dptrh = upper_32_bits(addr) & 0xff;
+}
+
+static dma_addr_t rswitch_desc_get_dptr(const struct rswitch_desc *desc)
+{
+ return __le32_to_cpu(desc->dptrl) | (u64)(desc->dptrh) << 32;
+}
+
+static int rswitch_gwca_queue_format(struct net_device *ndev,
+ struct rswitch_private *priv,
+ struct rswitch_gwca_queue *gq)
+{
+ int tx_ring_size = sizeof(struct rswitch_ext_desc) * gq->ring_size;
+ struct rswitch_ext_desc *desc;
+ struct rswitch_desc *linkfix;
+ dma_addr_t dma_addr;
+ int i;
+
+ memset(gq->ring, 0, tx_ring_size);
+ for (i = 0, desc = gq->ring; i < gq->ring_size; i++, desc++) {
+ if (!gq->dir_tx) {
+ dma_addr = dma_map_single(ndev->dev.parent,
+ gq->skbs[i]->data, PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(ndev->dev.parent, dma_addr))
+ goto err;
+
+ desc->desc.info_ds = cpu_to_le16(PKT_BUF_SZ);
+ rswitch_desc_set_dptr(&desc->desc, dma_addr);
+ desc->desc.die_dt = DT_FEMPTY | DIE;
+ } else {
+ desc->desc.die_dt = DT_EEMPTY | DIE;
+ }
+ }
+ rswitch_desc_set_dptr(&desc->desc, gq->ring_dma);
+ desc->desc.die_dt = DT_LINKFIX;
+
+ linkfix = &priv->linkfix_table[gq->index];
+ linkfix->die_dt = DT_LINKFIX;
+ rswitch_desc_set_dptr(linkfix, gq->ring_dma);
+
+ iowrite32(GWDCC_BALR | (gq->dir_tx ? GWDCC_DQT : 0) | GWDCC_EDE,
+ priv->addr + GWDCC_OFFS(gq->index));
+
+ return 0;
+
+err:
+ if (!gq->dir_tx) {
+ for (i--, desc = gq->ring; i >= 0; i--, desc++) {
+ dma_addr = rswitch_desc_get_dptr(&desc->desc);
+ dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+ }
+ }
+
+ return -ENOMEM;
+}
+
+static int rswitch_gwca_queue_ts_fill(struct net_device *ndev,
+ struct rswitch_gwca_queue *gq,
+ u32 start_index, u32 num)
+{
+ struct rswitch_device *rdev = netdev_priv(ndev);
+ struct rswitch_ext_ts_desc *desc;
+ dma_addr_t dma_addr;
+ u32 i, index;
+
+ for (i = 0; i < num; i++) {
+ index = (i + start_index) % gq->ring_size;
+ desc = &gq->ts_ring[index];
+ if (!gq->dir_tx) {
+ dma_addr = dma_map_single(ndev->dev.parent,
+ gq->skbs[index]->data, PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(ndev->dev.parent, dma_addr))
+ goto err;
+
+ desc->desc.info_ds = cpu_to_le16(PKT_BUF_SZ);
+ rswitch_desc_set_dptr(&desc->desc, dma_addr);
+ dma_wmb();
+ desc->desc.die_dt = DT_FEMPTY | DIE;
+ desc->info1 = cpu_to_le64(INFO1_SPN(rdev->etha->index));
+ } else {
+ desc->desc.die_dt = DT_EEMPTY | DIE;
+ }
+ }
+
+ return 0;
+
+err:
+ if (!gq->dir_tx) {
+ for (i--; i >= 0; i--) {
+ index = (i + start_index) % gq->ring_size;
+ desc = &gq->ts_ring[index];
+ dma_addr = rswitch_desc_get_dptr(&desc->desc);
+ dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+ }
+ }
+
+ return -ENOMEM;
+}
+
+static int rswitch_gwca_queue_ts_format(struct net_device *ndev,
+ struct rswitch_private *priv,
+ struct rswitch_gwca_queue *gq)
+{
+ int tx_ts_ring_size = sizeof(struct rswitch_ext_ts_desc) * gq->ring_size;
+ struct rswitch_ext_ts_desc *desc;
+ struct rswitch_desc *linkfix;
+ int err;
+
+ memset(gq->ts_ring, 0, tx_ts_ring_size);
+ err = rswitch_gwca_queue_ts_fill(ndev, gq, 0, gq->ring_size);
+ if (err < 0)
+ return err;
+
+ desc = &gq->ts_ring[gq->ring_size]; /* Last */
+ rswitch_desc_set_dptr(&desc->desc, gq->ring_dma);
+ desc->desc.die_dt = DT_LINKFIX;
+
+ linkfix = &priv->linkfix_table[gq->index];
+ linkfix->die_dt = DT_LINKFIX;
+ rswitch_desc_set_dptr(linkfix, gq->ring_dma);
+
+ iowrite32(GWDCC_BALR | (gq->dir_tx ? GWDCC_DQT : 0) | GWDCC_ETS | GWDCC_EDE,
+ priv->addr + GWDCC_OFFS(gq->index));
+
+ return 0;
+}
+
+static int rswitch_gwca_desc_alloc(struct rswitch_private *priv)
+{
+ int i, num_queues = priv->gwca.num_queues;
+ struct device *dev = &priv->pdev->dev;
+
+ priv->linkfix_table_size = sizeof(struct rswitch_desc) * num_queues;
+ priv->linkfix_table = dma_alloc_coherent(dev, priv->linkfix_table_size,
+ &priv->linkfix_table_dma, GFP_KERNEL);
+ if (!priv->linkfix_table)
+ return -ENOMEM;
+ for (i = 0; i < num_queues; i++)
+ priv->linkfix_table[i].die_dt = DT_EOS;
+
+ return 0;
+}
+
+static void rswitch_gwca_desc_free(struct rswitch_private *priv)
+{
+ if (priv->linkfix_table)
+ dma_free_coherent(&priv->pdev->dev, priv->linkfix_table_size,
+ priv->linkfix_table, priv->linkfix_table_dma);
+ priv->linkfix_table =