From patchwork Thu Oct 27 13:40:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yoshihiro Shimoda X-Patchwork-Id: 13022176 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D397FECAAA1 for ; Thu, 27 Oct 2022 13:41:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236268AbiJ0Nk7 (ORCPT ); Thu, 27 Oct 2022 09:40:59 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56144 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236259AbiJ0Nk6 (ORCPT ); Thu, 27 Oct 2022 09:40:58 -0400 Received: from relmlie5.idc.renesas.com (relmlor1.renesas.com [210.160.252.171]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 6568EB3B16; Thu, 27 Oct 2022 06:40:56 -0700 (PDT) X-IronPort-AV: E=Sophos;i="5.95,217,1661785200"; d="scan'208";a="138151483" Received: from unknown (HELO relmlir5.idc.renesas.com) ([10.200.68.151]) by relmlie5.idc.renesas.com with ESMTP; 27 Oct 2022 22:40:55 +0900 Received: from localhost.localdomain (unknown [10.166.15.32]) by relmlir5.idc.renesas.com (Postfix) with ESMTP id 9EBD340006BD; Thu, 27 Oct 2022 22:40:55 +0900 (JST) From: Yoshihiro Shimoda To: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org Cc: netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-renesas-soc@vger.kernel.org, Yoshihiro Shimoda Subject: [PATCH v5 1/3] dt-bindings: net: renesas: Document Renesas Ethernet Switch Date: Thu, 27 Oct 2022 22:40:32 +0900 Message-Id: <20221027134034.2343230-2-yoshihiro.shimoda.uh@renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221027134034.2343230-1-yoshihiro.shimoda.uh@renesas.com> References: <20221027134034.2343230-1-yoshihiro.shimoda.uh@renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org Document Renesas Etherent Switch for R-Car S4-8 (r8a779f0). Signed-off-by: Yoshihiro Shimoda --- .../net/renesas,r8a779f0-ether-switch.yaml | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/renesas,r8a779f0-ether-switch.yaml 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..581fff8902f4 --- /dev/null +++ b/Documentation/devicetree/bindings/net/renesas,r8a779f0-ether-switch.yaml @@ -0,0 +1,261 @@ +# 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 + +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: + 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 + #include + #include + + ethernet@e6880000 { + compatible = "renesas,r8a779f0-ether-switch"; + reg = <0xe6880000 0x20000>, <0xe68c0000 0x20000>; + reg-names = "base", "secure_base"; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + 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 = <ð_phy0>; + phy-mode = "sgmii"; + phys = <ð_serdes 0>; + mdio { + #address-cells = <1>; + #size-cells = <0>; + }; + }; + port@1 { + reg = <1>; + phy-handle = <ð_phy1>; + phy-mode = "sgmii"; + phys = <ð_serdes 1>; + mdio { + #address-cells = <1>; + #size-cells = <0>; + }; + }; + port@2 { + reg = <2>; + phy-handle = <ð_phy2>; + phy-mode = "sgmii"; + phys = <ð_serdes 2>; + mdio { + #address-cells = <1>; + #size-cells = <0>; + }; + }; + }; + }; From patchwork Thu Oct 27 13:40:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yoshihiro Shimoda X-Patchwork-Id: 13022178 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E6C00FA3742 for ; Thu, 27 Oct 2022 13:41:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236289AbiJ0NlL (ORCPT ); Thu, 27 Oct 2022 09:41:11 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56260 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236269AbiJ0NlB (ORCPT ); Thu, 27 Oct 2022 09:41:01 -0400 Received: from relmlie6.idc.renesas.com (relmlor2.renesas.com [210.160.252.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 635EABCB8D; Thu, 27 Oct 2022 06:40:57 -0700 (PDT) X-IronPort-AV: E=Sophos;i="5.95,217,1661785200"; d="scan'208";a="140602861" Received: from unknown (HELO relmlir5.idc.renesas.com) ([10.200.68.151]) by relmlie6.idc.renesas.com with ESMTP; 27 Oct 2022 22:40:55 +0900 Received: from localhost.localdomain (unknown [10.166.15.32]) by relmlir5.idc.renesas.com (Postfix) with ESMTP id C5AB840006BD; Thu, 27 Oct 2022 22:40:55 +0900 (JST) From: Yoshihiro Shimoda To: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org Cc: netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-renesas-soc@vger.kernel.org, Yoshihiro Shimoda Subject: [PATCH v5 2/3] net: ethernet: renesas: Add support for "Ethernet Switch" Date: Thu, 27 Oct 2022 22:40:33 +0900 Message-Id: <20221027134034.2343230-3-yoshihiro.shimoda.uh@renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221027134034.2343230-1-yoshihiro.shimoda.uh@renesas.com> References: <20221027134034.2343230-1-yoshihiro.shimoda.uh@renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org Add initial support for Renesas "Ethernet Switch" device of R-Car S4-8. The hardware has features about forwarding for an ethernet switch device. But, for now, it acts as ethernet controllers so that any forwarding offload features are not supported. So, any switchdev header files and DSA framework are not used. Signed-off-by: Yoshihiro Shimoda --- drivers/net/ethernet/renesas/Kconfig | 11 + drivers/net/ethernet/renesas/Makefile | 4 + drivers/net/ethernet/renesas/rswitch.c | 1690 ++++++++++++++++++++++++ drivers/net/ethernet/renesas/rswitch.h | 972 ++++++++++++++ 4 files changed, 2677 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..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..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..121d3c3b1842 --- /dev/null +++ b/drivers/net/ethernet/renesas/rswitch.c @@ -0,0 +1,1690 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Renesas Ethernet Switch device driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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)); +} + +/* 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 = NULL; +} + +static struct rswitch_gwca_queue *rswitch_gwca_get(struct rswitch_private *priv) +{ + struct rswitch_gwca_queue *gq; + int index; + + index = find_first_zero_bit(priv->gwca.used, priv->gwca.num_queues); + if (index >= priv->gwca.num_queues) + return NULL; + set_bit(index, priv->gwca.used); + gq = &priv->gwca.queues[index]; + memset(gq, 0, sizeof(*gq)); + gq->index = index; + + return gq; +} + +static void rswitch_gwca_put(struct rswitch_private *priv, + struct rswitch_gwca_queue *gq) +{ + clear_bit(gq->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_queue = rswitch_gwca_get(priv); + if (!rdev->tx_queue) + return -EBUSY; + + err = rswitch_gwca_queue_alloc(ndev, priv, rdev->tx_queue, true, false, + TX_RING_SIZE); + if (err < 0) { + rswitch_gwca_put(priv, rdev->tx_queue); + return err; + } + + return 0; +} + +static void rswitch_txdmac_free(struct net_device *ndev) +{ + struct rswitch_device *rdev = netdev_priv(ndev); + + rswitch_gwca_queue_free(ndev, rdev->tx_queue); + rswitch_gwca_put(rdev->priv, rdev->tx_queue); +} + +static int rswitch_txdmac_init(struct rswitch_private *priv, int index) +{ + struct rswitch_device *rdev = priv->rdev[index]; + + return rswitch_gwca_queue_format(rdev->ndev, priv, rdev->tx_queue); +} + +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_queue = rswitch_gwca_get(priv); + if (!rdev->rx_queue) + return -EBUSY; + + err = rswitch_gwca_queue_alloc(ndev, priv, rdev->rx_queue, false, true, + RX_RING_SIZE); + if (err < 0) { + rswitch_gwca_put(priv, rdev->rx_queue); + return err; + } + + return 0; +} + +static void rswitch_rxdmac_free(struct net_device *ndev) +{ + struct rswitch_device *rdev = netdev_priv(ndev); + + rswitch_gwca_queue_free(ndev, rdev->rx_queue); + rswitch_gwca_put(rdev->priv, rdev->rx_queue); +} + +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_queue_ts_format(ndev, priv, rdev->rx_queue); +} + +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->linkfix_table_dma), priv->addr + GWDCBAC1); + iowrite32(upper_32_bits(priv->linkfix_table_dma), priv->addr + GWDCBAC0); + rswitch_gwca_set_rate_limit(priv, priv->gwca.speed); + + for (i = 0; i < RSWITCH_NUM_PORTS; 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_queue *gq = rdev->rx_queue; + 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, gq->ring_size, *quota); + limit = boguscnt; + + desc = &gq->ts_ring[gq->cur]; + while ((desc->desc.die_dt & DT_MASK) != DT_FEMPTY) { + if (--boguscnt < 0) + break; + dma_rmb(); + pkt_len = le16_to_cpu(desc->desc.info_ds) & RX_DS; + skb = gq->skbs[gq->cur]; + gq->skbs[gq->cur] = NULL; + dma_addr = rswitch_desc_get_dptr(&desc->desc); + 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; + + gq->cur = rswitch_next_queue_index(gq, true, 1); + desc = &gq->ts_ring[gq->cur]; + } + + num = rswitch_get_num_cur_queues(gq); + ret = rswitch_gwca_queue_alloc_skb(gq, gq->dirty, num); + if (ret < 0) + goto err; + ret = rswitch_gwca_queue_ts_fill(ndev, gq, gq->dirty, num); + if (ret < 0) + goto err; + gq->dirty = rswitch_next_queue_index(gq, false, 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_queue *gq = rdev->tx_queue; + struct rswitch_ext_desc *desc; + dma_addr_t dma_addr; + struct sk_buff *skb; + int free_num = 0; + int size; + + for (; gq->cur - gq->dirty > 0; gq->dirty = rswitch_next_queue_index(gq, false, 1)) { + desc = &gq->ring[gq->dirty]; + if (free_txed_only && (desc->desc.die_dt & DT_MASK) != DT_FEMPTY) + break; + + dma_rmb(); + size = le16_to_cpu(desc->desc.info_ds) & TX_DS; + skb = gq->skbs[gq->dirty]; + if (skb) { + dma_addr = rswitch_desc_get_dptr(&desc->desc); + dma_unmap_single(ndev->dev.parent, dma_addr, + size, DMA_TO_DEVICE); + dev_kfree_skb_any(gq->skbs[gq->dirty]); + gq->skbs[gq->dirty] = NULL; + free_num++; + } + desc->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, "a)) + goto out; + else if (rdev->priv->gwca_halt) + goto err; + else if (rswitch_is_queue_rxed(rdev->rx_queue)) + goto retry; + + netif_wake_subqueue(ndev, 0); + + napi_complete(napi); + + rswitch_enadis_data_irq(priv, rdev->tx_queue->index, true); + rswitch_enadis_data_irq(priv, rdev->rx_queue->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_queue->index, false); + rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, false); + __napi_schedule(&rdev->napi); + } +} + +static irqreturn_t rswitch_data_irq(struct rswitch_private *priv, u32 *dis) +{ + struct rswitch_gwca_queue *gq; + int i, index, bit; + + for (i = 0; i < priv->gwca.num_queues; i++) { + gq = &priv->gwca.queues[i]; + index = gq->index / 32; + bit = BIT(gq->index % 32); + if (!(dis[index] & bit)) + continue; + + rswitch_ack_data_irq(priv, gq->index); + rswitch_queue_interrupt(gq->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 = ða->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(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->mii) { + mdiobus_unregister(rdev->etha->mii); + mdiobus_free(rdev->etha->mii); + rdev->etha->mii = NULL; + } +} + +static void rswitch_mac_config(struct phylink_config *config, + unsigned int mode, + const struct phylink_link_state *state) +{ +} + +static void rswitch_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ +} + +static void rswitch_mac_link_up(struct phylink_config *config, + struct phy_device *phydev, unsigned int mode, + phy_interface_t interface, int speed, + int duplex, bool tx_pause, bool rx_pause) +{ + /* Current hardware cannot change speed at runtime */ +} + +static const struct phylink_mac_ops rswitch_phylink_ops = { + .validate = phylink_generic_validate, + .mac_config = rswitch_mac_config, + .mac_link_down = rswitch_mac_link_down, + .mac_link_up = rswitch_mac_link_up, +}; + +static int rswitch_phylink_init(struct rswitch_device *rdev) +{ + struct device_node *port; + struct phylink *phylink; + int err; + + port = rswitch_get_port_node(rdev); + if (!port) + return -ENODEV; + + rdev->phylink_config.dev = &rdev->ndev->dev; + rdev->phylink_config.type = PHYLINK_NETDEV; + __set_bit(PHY_INTERFACE_MODE_SGMII, rdev->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_USXGMII, rdev->phylink_config.supported_interfaces); + rdev->phylink_config.mac_capabilities = MAC_100FD | MAC_1000FD | MAC_2500FD; + + phylink = phylink_create(&rdev->phylink_config, &port->fwnode, + rdev->etha->phy_interface, &rswitch_phylink_ops); + if (IS_ERR(phylink)) { + err = PTR_ERR(phylink); + goto out; + } + + rdev->phylink = phylink; + err = phylink_of_phy_connect(rdev->phylink, port, rdev->etha->phy_interface); +out: + of_node_put(port); + + return err; +} + +static void rswitch_phylink_deinit(struct rswitch_device *rdev) +{ + rtnl_lock(); + phylink_disconnect_phy(rdev->phylink); + rtnl_unlock(); + phylink_destroy(rdev->phylink); +} + +static int rswitch_serdes_set_params(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; + + return phy_set_speed(serdes, rdev->etha->speed); +} + +static int rswitch_serdes_init(struct rswitch_device *rdev) +{ + struct device_node *port = rswitch_get_port_node(rdev); + struct phy *serdes; + + serdes = devm_of_phy_get(&rdev->priv->pdev->dev, port, NULL); + of_node_put(port); + if (IS_ERR(serdes)) + return PTR_ERR(serdes); + + return phy_init(serdes); +} + +static int rswitch_serdes_deinit(struct rswitch_device *rdev) +{ + struct device_node *port = rswitch_get_port_node(rdev); + struct phy *serdes; + + serdes = devm_of_phy_get(&rdev->priv->pdev->dev, port, NULL); + of_node_put(port); + if (IS_ERR(serdes)) + return PTR_ERR(serdes); + + return phy_exit(serdes); +} + +static int rswitch_ether_port_init_one(struct rswitch_device *rdev) +{ + int err; + + if (!rdev->etha->operated) { + err = rswitch_etha_hw_init(rdev->etha, rdev->ndev->dev_addr); + if (err < 0) + return err; + rdev->etha->operated = true; + } + + err = rswitch_mii_register(rdev); + if (err < 0) + return err; + + err = rswitch_phylink_init(rdev); + if (err < 0) + goto err_phylink_init; + + err = rswitch_serdes_set_params(rdev); + if (err < 0) + goto err_serdes_set_params; + + return 0; + +err_serdes_set_params: + rswitch_phylink_deinit(rdev); + +err_phylink_init: + rswitch_mii_unregister(rdev); + + return err; +} + +static void rswitch_ether_port_deinit_one(struct rswitch_device *rdev) +{ + rswitch_phylink_deinit(rdev); + rswitch_mii_unregister(rdev); +} + +static int rswitch_ether_port_init_all(struct rswitch_private *priv) +{ + int i, err; + + for (i = 0; i < RSWITCH_NUM_PORTS; i++) { + err = rswitch_ether_port_init_one(priv->rdev[i]); + if (err) + goto err_init_one; + } + + for (i = 0; i < RSWITCH_NUM_PORTS; i++) { + err = rswitch_serdes_init(priv->rdev[i]); + if (err) + goto err_serdes; + } + + return 0; + +err_serdes: + for (i--; i >= 0; i--) + rswitch_serdes_deinit(priv->rdev[i]); + i = RSWITCH_NUM_PORTS; + +err_init_one: + for (i--; i >= 0; i--) + rswitch_ether_port_deinit_one(priv->rdev[i]); + + return err; +} + +static int rswitch_open(struct net_device *ndev) +{ + struct rswitch_device *rdev = netdev_priv(ndev); + + phylink_start(rdev->phylink); + + napi_enable(&rdev->napi); + netif_start_queue(ndev); + + rswitch_enadis_data_irq(rdev->priv, rdev->tx_queue->index, true); + rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, true); + + return 0; +}; + +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_queue->index, false); + rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, false); + + phylink_stop(rdev->phylink); + 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_queue *gq = rdev->tx_queue; + struct rswitch_ext_desc *desc; + int ret = NETDEV_TX_OK; + dma_addr_t dma_addr; + + if (rswitch_get_num_cur_queues(gq) >= gq->ring_size - 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; + } + + gq->skbs[gq->cur] = skb; + desc = &gq->ring[gq->cur]; + rswitch_desc_set_dptr(&desc->desc, dma_addr); + desc->desc.info_ds = cpu_to_le16(skb->len); + + desc->info1 = cpu_to_le64(INFO1_DV(BIT(rdev->etha->index)) | INFO1_FMT); + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + rdev->ts_tag++; + desc->info1 |= cpu_to_le64(INFO1_TSUN(rdev->ts_tag) | INFO1_TXC); + } + skb_tx_timestamp(skb); + + dma_wmb(); + + desc->desc.die_dt = DT_FSINGLE | DIE; + wmb(); /* gq->cur must be incremented after die_dt was set */ + + gq->cur = rswitch_next_queue_index(gq, true, 1); + rswitch_modify(rdev->addr, GWTRC(gq->index), 0, BIT(gq->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 rswitch_device *rdev = netdev_priv(ndev); + + if (!netif_running(ndev)) + return -EINVAL; + + return phylink_mii_ioctl(rdev->phylink, 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; + + if (index >= RSWITCH_NUM_PORTS) + return -EINVAL; + + 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; + rdev->port = index; + rdev->etha = &priv->etha[index]; + 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); + + err = of_get_ethdev_address(pdev->dev.of_node, ndev); + if (err) { + if (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_PORTS; i++) + rswitch_etha_init(priv, i); + + rswitch_clock_enable(priv); + for (i = 0; i < RSWITCH_NUM_PORTS; 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_PORTS; 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_PORTS; 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; + } + } + + err = rswitch_ether_port_init_all(priv); + if (err) + goto err_ether_port_init_all; + + for (i = 0; i < RSWITCH_NUM_PORTS; i++) + netdev_info(priv->rdev[i]->ndev, "MAC address %pMn", + priv->rdev[i]->ndev->dev_addr); + + return 0; + +err_ether_port_init_all: + for (i = 0; i < RSWITCH_NUM_PORTS; i++) + unregister_netdev(priv->rdev[i]->ndev); + +err_register_netdev: + rswitch_gwca_hw_deinit(priv); + +err_gwca_hw_init: +err_gwca_request_irq: + for (i = 0; i < RSWITCH_NUM_PORTS; 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; + + 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_queues = min(RSWITCH_NUM_PORTS * NUM_QUEUES_PER_NDEV, + RSWITCH_MAX_NUM_QUEUES); + priv->gwca.queues = devm_kcalloc(&pdev->dev, priv->gwca.num_queues, + sizeof(*priv->gwca.queues), GFP_KERNEL); + if (!priv->gwca.queues) + return -ENOMEM; + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + 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_PORTS; i++) { + struct rswitch_device *rdev = priv->rdev[i]; + + rswitch_serdes_deinit(rdev); + rswitch_ether_port_deinit_one(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); + + 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..3c58a7ca8b8a --- /dev/null +++ b/drivers/net/ethernet/renesas/rswitch.h @@ -0,0 +1,972 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Renesas Ethernet Switch device driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#ifndef __RSWITCH_H__ +#define __RSWITCH_H__ + +#include + +#define RSWITCH_MAX_NUM_QUEUES 128 + +#define RSWITCH_NUM_PORTS 3 + +#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 ETHA/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(queue) (GWTRC0 + (queue) / 32 * 4) +#define GWDCC_OFFS(queue) (GWDCC0 + (queue) * 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_ACE_DEFAULT (BIT(0) | BIT(AGENT_INDEX_GWCA)) +#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 FWPC1(i) (FWPC10 + (i) * 0x10) +#define FWPC1_DDE BIT(0) + +#define FWPBFC(i) (FWPBFC0 + (i) * 0x10) + +#define FWPBFCSDC(j, i) (FWPBFCSDC00 + (i) * 0x10 + (j) * 0x04) + +/* TOP */ +#define TPEMIMC7(queue) (TPEMIMC70 + (queue) * 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 */ +}; + +/* Both transmission and reception */ +#define INFO1_FMT BIT(2) +#define INFO1_TXC BIT(3) + +/* For transmission */ +#define INFO1_TSUN(val) ((u64)(val) << 8ULL) +#define INFO1_CSD0(index) ((u64)(index) << 32ULL) +#define INFO1_CSD1(index) ((u64)(index) << 40ULL) +#define INFO1_DV(port_vector) ((u64)(port_vector) << 48ULL) + +/* For reception */ +#define INFO1_SPN(port) ((u64)(port) << 36ULL) + +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 { + struct rswitch_desc desc; + __le32 ts_nsec; + __le32 ts_sec; +} __packed; + +struct rswitch_ext_desc { + struct rswitch_desc desc; + __le64 info1; +} __packed; + +struct rswitch_ext_ts_desc { + struct rswitch_desc desc; + __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; +}; + +/* The datasheet said descriptor "chain" and/or "queue". For consistency of + * name, this driver calls "queue". + */ +struct rswitch_gwca_queue { + 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 ring_size; + u32 cur; + u32 dirty; + struct sk_buff **skbs; + + struct net_device *ndev; /* queue to ndev for irq */ +}; + +#define RSWITCH_NUM_IRQ_REGS (RSWITCH_MAX_NUM_QUEUES / BITS_PER_TYPE(u32)) +struct rswitch_gwca { + int index; + struct rswitch_gwca_queue *queues; + int num_queues; + DECLARE_BITMAP(used, RSWITCH_MAX_NUM_QUEUES); + u32 tx_irq_bits[RSWITCH_NUM_IRQ_REGS]; + u32 rx_irq_bits[RSWITCH_NUM_IRQ_REGS]; + int speed; +}; + +#define NUM_QUEUES_PER_NDEV 2 +struct rswitch_device { + struct rswitch_private *priv; + struct net_device *ndev; + struct napi_struct napi; + struct phylink *phylink; + struct phylink_config phylink_config; + void __iomem *addr; + struct rswitch_gwca_queue *tx_queue; + struct rswitch_gwca_queue *rx_queue; + u8 ts_tag; + + int port; + struct rswitch_etha *etha; +}; + +struct rswitch_mfwd_mac_table_entry { + int queue_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 rcar_gen4_ptp_private *ptp_priv; + struct rswitch_desc *linkfix_table; + dma_addr_t linkfix_table_dma; + u32 linkfix_table_size; + + struct rswitch_device *rdev[RSWITCH_NUM_PORTS]; + + struct rswitch_gwca gwca; + struct rswitch_etha etha[RSWITCH_NUM_PORTS]; + struct rswitch_mfwd mfwd; + + bool gwca_halt; +}; + +#endif /* #ifndef __RSWITCH_H__ */ From patchwork Thu Oct 27 13:40:34 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yoshihiro Shimoda X-Patchwork-Id: 13022177 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E4A20FA3740 for ; Thu, 27 Oct 2022 13:41:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236279AbiJ0NlC (ORCPT ); Thu, 27 Oct 2022 09:41:02 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56224 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236253AbiJ0NlA (ORCPT ); Thu, 27 Oct 2022 09:41:00 -0400 Received: from relmlie5.idc.renesas.com (relmlor1.renesas.com [210.160.252.171]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 3D90DCABFB; Thu, 27 Oct 2022 06:40:58 -0700 (PDT) X-IronPort-AV: E=Sophos;i="5.95,217,1661785200"; d="scan'208";a="138151486" Received: from unknown (HELO relmlir5.idc.renesas.com) ([10.200.68.151]) by relmlie5.idc.renesas.com with ESMTP; 27 Oct 2022 22:40:56 +0900 Received: from localhost.localdomain (unknown [10.166.15.32]) by relmlir5.idc.renesas.com (Postfix) with ESMTP id E12BA40083E4; Thu, 27 Oct 2022 22:40:55 +0900 (JST) From: Yoshihiro Shimoda To: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org Cc: netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-renesas-soc@vger.kernel.org, Yoshihiro Shimoda Subject: [PATCH v5 3/3] net: ethernet: renesas: rswitch: Add R-Car Gen4 gPTP support Date: Thu, 27 Oct 2022 22:40:34 +0900 Message-Id: <20221027134034.2343230-4-yoshihiro.shimoda.uh@renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221027134034.2343230-1-yoshihiro.shimoda.uh@renesas.com> References: <20221027134034.2343230-1-yoshihiro.shimoda.uh@renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org Add R-Car Gen4 gPTP support into the rswitch driver. Signed-off-by: Yoshihiro Shimoda --- 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 | 144 ++++++++++++++- drivers/net/ethernet/renesas/rswitch.h | 1 + 5 files changed, 398 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 +#include +#include +#include +#include +#include + +#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 + +#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 121d3c3b1842..eae0b9eb6abf 100644 --- a/drivers/net/ethernet/renesas/rswitch.c +++ b/drivers/net/ethernet/renesas/rswitch.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,13 @@ static void rswitch_fwd_init(struct rswitch_private *priv) 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) @@ -663,6 +671,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, gq->ring_size, *quota); limit = boguscnt; @@ -677,6 +686,17 @@ static bool rswitch_rx(struct net_device *ndev, int *quota) gq->skbs[gq->cur] = NULL; dma_addr = rswitch_desc_get_dptr(&desc->desc); 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 = __le32_to_cpu(desc->ts_sec); + ts.tv_nsec = __le32_to_cpu(desc->ts_nsec & cpu_to_le32(0x3fffffff)); + shhwtstamps->hwtstamp = timespec64_to_ktime(ts); + } skb_put(skb, pkt_len); skb->protocol = eth_type_trans(skb, ndev); netif_receive_skb(skb); @@ -725,6 +745,15 @@ static int rswitch_tx_free(struct net_device *ndev, bool free_txed_only) size = le16_to_cpu(desc->desc.info_ds) & TX_DS; skb = gq->skbs[gq->dirty]; 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 = rswitch_desc_get_dptr(&desc->desc); dma_unmap_single(ndev->dev.parent, dma_addr, size, DMA_TO_DEVICE); @@ -1402,6 +1431,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 rswitch_device *rdev = netdev_priv(ndev); @@ -1409,7 +1507,14 @@ static int rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd if (!netif_running(ndev)) return -EINVAL; - return phylink_mii_ioctl(rdev->phylink, req, cmd); + switch (cmd) { + case SIOCGHWTSTAMP: + return rswitch_hwstamp_get(ndev, req); + case SIOCSHWTSTAMP: + return rswitch_hwstamp_set(ndev, req); + default: + return phylink_mii_ioctl(rdev->phylink, req, cmd); + } } static const struct net_device_ops rswitch_netdev_ops = { @@ -1422,6 +1527,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", }, { } @@ -1466,6 +1592,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); @@ -1550,6 +1677,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; @@ -1586,6 +1718,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_PORTS; i++) rswitch_device_free(priv, i); @@ -1611,12 +1746,18 @@ 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; + 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); + 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)); @@ -1647,6 +1788,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_PORTS; 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 3c58a7ca8b8a..778177ec8d4f 100644 --- a/drivers/net/ethernet/renesas/rswitch.h +++ b/drivers/net/ethernet/renesas/rswitch.h @@ -8,6 +8,7 @@ #define __RSWITCH_H__ #include +#include "rcar_gen4_ptp.h" #define RSWITCH_MAX_NUM_QUEUES 128