From mboxrd@z Thu Jan 1 00:00:00 1970 Delivery-date: Fri, 08 Nov 2024 14:15:51 +0100 Received: from metis.whiteo.stw.pengutronix.de ([2a0a:edc0:2:b01:1d::104]) by lore.white.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1t9Oq5-006Kno-31 for lore@lore.pengutronix.de; Fri, 08 Nov 2024 14:15:51 +0100 Received: from bombadil.infradead.org ([2607:7c80:54:3::133]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1t9Oq1-0003qh-Qv for lore@pengutronix.de; Fri, 08 Nov 2024 14:15:51 +0100 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:To:In-Reply-To:References: Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=h+q1T7XzKGTI1fPex1EQZ7RrDds5uRJmwLoNMXc7ffw=; b=3Jh/xEvHDS8xcqE4KW/uRwE6eV KBu+spGTI5xEh6LSGCC99VxKGE6QQq6i/4mZD2+HpMMsw8uggQNPfdm1/29dPEeO9Zant53UkxZcZ a/C6pN7gVFj0NO1xMRpX+gfiugRIvFBLzdyhWm3E4Rf8zCM0Qa2Ya0zC9RBpX6+wcMEZPmJifS5L+ Dp5Xqa2ss6VKIuPjHTquWEiy73ASw2fYu+8x19wlABx+1ugjyMjYFuJbFX6OU6auIXV87/xiQtaGw B4EcxkEaKeuxxXtc4sy6jMh2Tcc6Rs5cYyIf5h4L+Q+KW5nMlTM4zwnU1/D9d7GQtRRJAL/U5TqKM k+cv88nw==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98 #2 (Red Hat Linux)) id 1t9OpQ-0000000Adhj-0mck; Fri, 08 Nov 2024 13:15:08 +0000 Received: from metis.whiteo.stw.pengutronix.de ([2a0a:edc0:2:b01:1d::104]) by bombadil.infradead.org with esmtps (Exim 4.98 #2 (Red Hat Linux)) id 1t9OpK-0000000AdfI-2HAS for barebox@lists.infradead.org; Fri, 08 Nov 2024 13:15:05 +0000 Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1t9OpJ-0003X9-8L; Fri, 08 Nov 2024 14:15:01 +0100 Received: from dude02.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::28]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1t9OpI-002cw6-2y; Fri, 08 Nov 2024 14:15:00 +0100 Received: from localhost ([::1] helo=dude02.red.stw.pengutronix.de) by dude02.red.stw.pengutronix.de with esmtp (Exim 4.96) (envelope-from ) id 1t9OpI-00AFXY-2f; Fri, 08 Nov 2024 14:15:00 +0100 From: Sascha Hauer Date: Fri, 08 Nov 2024 14:15:05 +0100 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20241108-network-k3-v1-7-ee71bff15eb7@pengutronix.de> References: <20241108-network-k3-v1-0-ee71bff15eb7@pengutronix.de> In-Reply-To: <20241108-network-k3-v1-0-ee71bff15eb7@pengutronix.de> To: "open list:BAREBOX" X-Mailer: b4 0.12.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1731071700; l=23970; i=s.hauer@pengutronix.de; s=20230412; h=from:subject:message-id; bh=wiPgvebZl9ULnV3y3wx3OhTQPtlnbSM10O2OVs71ufA=; b=laOj+bTzHaUNwLS9Ox3WtqoSDeq6qLKmMSQfVL+ffhN3dUW3Z49HA2t2WIy6epS/WC53pCvKv sRS/LZ0arIsDjcadDi9wb8zRCS3uY0tWR1A/RwjfX4/HScBe/jRF9dt X-Developer-Key: i=s.hauer@pengutronix.de; a=ed25519; pk=4kuc9ocmECiBJKWxYgqyhtZOHj5AWi7+d0n/UjhkwTg= X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20241108_051503_070385_6CA61A04 X-CRM114-Status: GOOD ( 26.44 ) X-BeenThere: barebox@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "barebox" X-SA-Exim-Connect-IP: 2607:7c80:54:3::133 X-SA-Exim-Mail-From: barebox-bounces+lore=pengutronix.de@lists.infradead.org X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on metis.whiteo.stw.pengutronix.de X-Spam-Level: X-Spam-Status: No, score=-5.2 required=4.0 tests=AWL,BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,SPF_HELO_NONE,SPF_NONE autolearn=unavailable autolearn_force=no version=3.4.2 Subject: [PATCH 7/7] net: add am65-cpsw-nuss driver X-SA-Exim-Version: 4.2.1 (built Wed, 08 May 2019 21:11:16 +0000) X-SA-Exim-Scanned: Yes (on metis.whiteo.stw.pengutronix.de) This adds ethernet support for various TI K3 SoCs. The one we currently care about is the AM625 where this driver was tested on. The code is based on the U-Boot-2025.01-rc1 driver. Signed-off-by: Sascha Hauer --- drivers/net/Kconfig | 14 +- drivers/net/Makefile | 1 + drivers/net/am65-cpsw-nuss.c | 785 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 799 insertions(+), 1 deletion(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 7f0d277548..25f4f33739 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -58,13 +58,25 @@ config DRIVER_NET_CPSW config DRIVER_NET_TI_DAVINCI_MDIO bool "TI Davinci MDIO driver" - depends on ARCH_OMAP3 || COMPILE_TEST + depends on ARCH_OMAP3 || ARCH_K3 || COMPILE_TEST config DRIVER_NET_DAVINCI_EMAC bool "TI Davinci/OMAP EMAC ethernet driver" depends on ARCH_OMAP3 select PHYLIB +config DRIVER_NET_TI_K3_AM65_CPSW_NUSS + bool "TI K3 AM654x/J721E CPSW Ethernet driver" + depends on ARCH_K3 || COMPILE_TEST + select DRIVER_NET_TI_DAVINCI_MDIO + select PHYLIB + help + This driver supports TI K3 AM654/J721E CPSW2G Ethernet SubSystem. + The two-port Gigabit Ethernet MAC (MCU_CPSW0) subsystem provides + Ethernet packet communication for the device: One Ethernet port + (port 1) with selectable RGMII and RMII interfaces and an internal + Communications Port Programming Interface (CPPI) port (port 0). + config DRIVER_NET_DESIGNWARE bool "Designware DWMAC1000 Ethernet driver support" if COMPILE_TEST depends on HAS_DMA diff --git a/drivers/net/Makefile b/drivers/net/Makefile index b451813f7c..b66131632d 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_DRIVER_NET_CS8900) += cs8900.o obj-$(CONFIG_DRIVER_NET_CPSW) += cpsw.o obj-$(CONFIG_DRIVER_NET_DAVINCI_EMAC) += davinci_emac.o obj-$(CONFIG_DRIVER_NET_TI_DAVINCI_MDIO) += davinci_mdio.o +obj-$(CONFIG_DRIVER_NET_TI_K3_AM65_CPSW_NUSS) += am65-cpsw-nuss.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE) += designware.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_GENERIC) += designware_generic.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_SOCFPGA) += designware_socfpga.o diff --git a/drivers/net/am65-cpsw-nuss.c b/drivers/net/am65-cpsw-nuss.c new file mode 100644 index 0000000000..6e292ed3f4 --- /dev/null +++ b/drivers/net/am65-cpsw-nuss.c @@ -0,0 +1,785 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Texas Instruments K3 AM65 Ethernet Switch SubSystem Driver + * + * Copyright (C) 2019, Texas Instruments, Incorporated + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AM65_CPSW_CPSWNU_MAX_PORTS 9 + +#define AM65_CPSW_SS_BASE 0x0 +#define AM65_CPSW_SGMII_BASE 0x100 +#define AM65_CPSW_MDIO_BASE 0xf00 +#define AM65_CPSW_XGMII_BASE 0x2100 +#define AM65_CPSW_CPSW_NU_BASE 0x20000 +#define AM65_CPSW_CPSW_NU_ALE_BASE 0x1e000 + +#define AM65_CPSW_CPSW_NU_PORTS_OFFSET 0x1000 +#define AM65_CPSW_CPSW_NU_PORT_MACSL_OFFSET 0x330 + +#define AM65_CPSW_MDIO_BUS_FREQ_DEF 1000000 + +#define AM65_CPSW_CTL_REG 0x4 +#define AM65_CPSW_STAT_PORT_EN_REG 0x14 +#define AM65_CPSW_PTYPE_REG 0x18 + +#define AM65_CPSW_CTL_REG_P0_ENABLE BIT(2) +#define AM65_CPSW_CTL_REG_P0_TX_CRC_REMOVE BIT(13) +#define AM65_CPSW_CTL_REG_P0_RX_PAD BIT(14) + +#define AM65_CPSW_P0_FLOW_ID_REG 0x8 +#define AM65_CPSW_PN_RX_MAXLEN_REG 0x24 +#define AM65_CPSW_PN_REG_SA_L 0x308 +#define AM65_CPSW_PN_REG_SA_H 0x30c + +#define AM65_CPSW_SGMII_CONTROL_REG 0x010 +#define AM65_CPSW_SGMII_MR_ADV_ABILITY_REG 0x018 +#define AM65_CPSW_SGMII_CONTROL_MR_AN_ENABLE BIT(0) + +#define ADVERTISE_SGMII 0x1 + +#define AM65_CPSW_ALE_CTL_REG 0x8 +#define AM65_CPSW_ALE_CTL_REG_ENABLE BIT(31) +#define AM65_CPSW_ALE_CTL_REG_RESET_TBL BIT(30) +#define AM65_CPSW_ALE_CTL_REG_BYPASS BIT(4) +#define AM65_CPSW_ALE_PN_CTL_REG(x) (0x40 + (x) * 4) +#define AM65_CPSW_ALE_PN_CTL_REG_MODE_FORWARD 0x3 +#define AM65_CPSW_ALE_PN_CTL_REG_MAC_ONLY BIT(11) + +#define AM65_CPSW_ALE_THREADMAPDEF_REG 0x134 +#define AM65_CPSW_ALE_DEFTHREAD_EN BIT(15) + +#define AM65_CPSW_MACSL_CTL_REG 0x0 +#define AM65_CPSW_MACSL_CTL_REG_IFCTL_A BIT(15) +#define AM65_CPSW_MACSL_CTL_EXT_EN BIT(18) +#define AM65_CPSW_MACSL_CTL_REG_GIG BIT(7) +#define AM65_CPSW_MACSL_CTL_REG_GMII_EN BIT(5) +#define AM65_CPSW_MACSL_CTL_REG_LOOPBACK BIT(1) +#define AM65_CPSW_MACSL_CTL_REG_FULL_DUPLEX BIT(0) +#define AM65_CPSW_MACSL_RESET_REG 0x8 +#define AM65_CPSW_MACSL_RESET_REG_RESET BIT(0) +#define AM65_CPSW_MACSL_STATUS_REG 0x4 +#define AM65_CPSW_MACSL_RESET_REG_PN_IDLE BIT(31) +#define AM65_CPSW_MACSL_RESET_REG_PN_E_IDLE BIT(30) +#define AM65_CPSW_MACSL_RESET_REG_PN_P_IDLE BIT(29) +#define AM65_CPSW_MACSL_RESET_REG_PN_TX_IDLE BIT(28) +#define AM65_CPSW_MACSL_RESET_REG_IDLE_MASK \ + (AM65_CPSW_MACSL_RESET_REG_PN_IDLE | \ + AM65_CPSW_MACSL_RESET_REG_PN_E_IDLE | \ + AM65_CPSW_MACSL_RESET_REG_PN_P_IDLE | \ + AM65_CPSW_MACSL_RESET_REG_PN_TX_IDLE) + +#define AM65_CPSW_CPPI_PKT_TYPE 0x7 + +#define DEFAULT_GPIO_RESET_DELAY 10 + +struct am65_cpsw_port { + void __iomem *port_base; + void __iomem *port_sgmii_base; + void __iomem *macsl_base; + bool enabled; + u32 mac_control; + u32 port_id; + struct device_node *device_node; + struct eth_device edev; + struct device dev; + struct am65_cpsw_common *cpsw_common; + struct phy_device *phydev; + void *rx_buffer; + phy_interface_t interface; +}; + +struct am65_cpsw_common { + struct device *dev; + void __iomem *ss_base; + void __iomem *cpsw_base; + void __iomem *ale_base; + + struct clk *fclk; + struct pm_domain *pwrdmn; + + u32 port_num; + struct am65_cpsw_port ports[AM65_CPSW_CPSWNU_MAX_PORTS]; + + u32 bus_freq; + + struct dma *dma_tx; + struct dma *dma_rx; + u32 rx_next; + u32 rx_pend; + int started; +}; + +#define UDMA_RX_BUF_SIZE ALIGN(1522, DMA_ALIGNMENT) +#define UDMA_RX_DESC_NUM PKTBUFSRX + +static int am65_cpsw_macsl_reset(struct am65_cpsw_port *slave) +{ + u32 i = 100; + + /* Set the soft reset bit */ + writel(AM65_CPSW_MACSL_RESET_REG_RESET, + slave->macsl_base + AM65_CPSW_MACSL_RESET_REG); + + while ((readl(slave->macsl_base + AM65_CPSW_MACSL_RESET_REG) & + AM65_CPSW_MACSL_RESET_REG_RESET) && i--); + + /* Timeout on the reset */ + return i; +} + +static int am65_cpsw_macsl_wait_for_idle(struct am65_cpsw_port *slave) +{ + u32 i = 100; + + while ((readl(slave->macsl_base + AM65_CPSW_MACSL_STATUS_REG) & + AM65_CPSW_MACSL_RESET_REG_IDLE_MASK) && i--); + + return i; +} + +static struct am65_cpsw_port *edev_to_port(struct eth_device *edev) +{ + return container_of(edev, struct am65_cpsw_port, edev); +} + +static void am65_cpsw_update_link(struct eth_device *edev) +{ + struct am65_cpsw_port *port = edev_to_port(edev); + struct phy_device *phy = port->edev.phydev; + + u32 mac_control = 0; + + if (phy->interface == PHY_INTERFACE_MODE_SGMII) { + writel(ADVERTISE_SGMII, + port->port_sgmii_base + AM65_CPSW_SGMII_MR_ADV_ABILITY_REG); + writel(AM65_CPSW_SGMII_CONTROL_MR_AN_ENABLE, + port->port_sgmii_base + AM65_CPSW_SGMII_CONTROL_REG); + } + + if (phy->link) { /* link up */ + mac_control = /*AM65_CPSW_MACSL_CTL_REG_LOOPBACK |*/ + AM65_CPSW_MACSL_CTL_REG_GMII_EN; + if (phy->speed == 1000) + mac_control |= AM65_CPSW_MACSL_CTL_REG_GIG; + if (phy->speed == 10 && phy_interface_is_rgmii(phy)) + /* Can be used with in band mode only */ + mac_control |= AM65_CPSW_MACSL_CTL_EXT_EN; + if (phy->duplex == DUPLEX_FULL) + mac_control |= AM65_CPSW_MACSL_CTL_REG_FULL_DUPLEX; + if (phy->speed == 100) + mac_control |= AM65_CPSW_MACSL_CTL_REG_IFCTL_A; + if (phy->interface == PHY_INTERFACE_MODE_SGMII) + mac_control |= AM65_CPSW_MACSL_CTL_EXT_EN; + } + + if (mac_control == port->mac_control) + return; + + writel(mac_control, port->macsl_base + AM65_CPSW_MACSL_CTL_REG); + port->mac_control = mac_control; +} + +#define AM65_GMII_SEL_PORT_OFFS(x) (0x4 * ((x) - 1)) + +#define AM65_GMII_SEL_MODE_MII 0 +#define AM65_GMII_SEL_MODE_RMII 1 +#define AM65_GMII_SEL_MODE_RGMII 2 +#define AM65_GMII_SEL_MODE_SGMII 3 + +#define AM65_GMII_SEL_RGMII_IDMODE BIT(4) + +static int am65_cpsw_gmii_sel_k3(struct am65_cpsw_port *port, + phy_interface_t phy_mode) +{ + struct device *dev = &port->dev; + u32 offset, reg; + bool rgmii_id = false; + struct regmap *gmii_sel; + u32 mode = 0; + int ret; + + gmii_sel = syscon_regmap_lookup_by_phandle(port->device_node, "phys"); + if (IS_ERR(gmii_sel)) + return PTR_ERR(gmii_sel); + + ret = of_property_read_u32_index(port->device_node, "phys", 1, &offset); + if (ret) + return ret; + + regmap_read(gmii_sel, AM65_GMII_SEL_PORT_OFFS(offset), ®); + + dev_dbg(dev, "old gmii_sel: %08x\n", reg); + + switch (phy_mode) { + case PHY_INTERFACE_MODE_RMII: + mode = AM65_GMII_SEL_MODE_RMII; + break; + + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_RXID: + mode = AM65_GMII_SEL_MODE_RGMII; + break; + + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_TXID: + mode = AM65_GMII_SEL_MODE_RGMII; + rgmii_id = true; + break; + + case PHY_INTERFACE_MODE_SGMII: + mode = AM65_GMII_SEL_MODE_SGMII; + break; + + default: + dev_warn(dev, + "Unsupported PHY mode: %u. Defaulting to MII.\n", + phy_mode); + /* fallthrough */ + case PHY_INTERFACE_MODE_MII: + mode = AM65_GMII_SEL_MODE_MII; + break; + }; + + if (rgmii_id) + mode |= AM65_GMII_SEL_RGMII_IDMODE; + + reg = mode; + dev_dbg(dev, "gmii_sel PHY mode: %u, new gmii_sel: %08x\n", + phy_mode, reg); + regmap_write(gmii_sel, AM65_GMII_SEL_PORT_OFFS(offset), reg); + + regmap_read(gmii_sel, AM65_GMII_SEL_PORT_OFFS(offset), ®); + if (reg != mode) { + dev_err(dev, + "gmii_sel PHY mode NOT SET!: requested: %08x, gmii_sel: %08x\n", + mode, reg); + return 0; + } + + return 0; +} + +static int am65_cpsw_common_start(struct am65_cpsw_common *common) +{ + struct ti_udma_drv_chan_cfg_data *dma_rx_cfg_data; + struct am65_cpsw_port *port0 = &common->ports[0]; + int ret, i; + + if (common->started) + return 0; + + ret = clk_enable(common->fclk); + if (ret) { + dev_err(common->dev, "clk enabled failed %d\n", ret); + goto err_off_pwrdm; + } + + common->rx_next = 0; + common->rx_pend = 0; + common->dma_tx = dma_get_by_name(common->dev, "tx0"); + if (IS_ERR(common->dma_tx)) { + ret = PTR_ERR(common->dma_tx); + dev_err(common->dev, "TX dma get failed: %pe\n", common->dma_tx); + goto err_off_clk; + } + + common->dma_rx = dma_get_by_name(common->dev, "rx"); + if (IS_ERR(common->dma_rx)) { + ret = PTR_ERR(common->dma_rx); + dev_err(common->dev, "RX dma get failed: %pe\n", common->dma_rx); + goto err_free_tx; + } + + for (i = 0; i < UDMA_RX_DESC_NUM; i++) { + void *buf = dma_alloc(UDMA_RX_BUF_SIZE); + dma_addr_t dma; + + if (!buf) + return -ENOMEM; + + dma = dma_map_single(common->dev, buf, UDMA_RX_BUF_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(common->dev, dma)) + return -EFAULT; + + ret = dma_prepare_rcv_buf(common->dma_rx, + dma, + UDMA_RX_BUF_SIZE); + if (ret) { + dev_err(common->dev, "RX dma add buf failed %d\n", ret); + goto err_free_rx; + } + } + + ret = dma_enable(common->dma_tx); + if (ret) { + dev_err(common->dev, "TX dma_enable failed %d\n", ret); + goto err_free_rx; + } + + ret = dma_enable(common->dma_rx); + if (ret) { + dev_err(common->dev, "RX dma_enable failed %d\n", ret); + goto err_dis_tx; + } + + /* Control register */ + writel(AM65_CPSW_CTL_REG_P0_ENABLE | + AM65_CPSW_CTL_REG_P0_TX_CRC_REMOVE | + AM65_CPSW_CTL_REG_P0_RX_PAD, + common->cpsw_base + AM65_CPSW_CTL_REG); + + /* disable priority elevation */ + writel(0, common->cpsw_base + AM65_CPSW_PTYPE_REG); + + /* Port 0 length register */ + writel(UDMA_RX_BUF_SIZE, port0->port_base + AM65_CPSW_PN_RX_MAXLEN_REG); + + /* set base flow_id */ + dma_get_cfg(common->dma_rx, 0, (void **)&dma_rx_cfg_data); + writel(dma_rx_cfg_data->flow_id_base, + port0->port_base + AM65_CPSW_P0_FLOW_ID_REG); + dev_dbg(common->dev, "K3 CPSW: rflow_id_base: %u\n", + dma_rx_cfg_data->flow_id_base); + + /* Reset and enable the ALE */ + writel(AM65_CPSW_ALE_CTL_REG_ENABLE | AM65_CPSW_ALE_CTL_REG_RESET_TBL | + AM65_CPSW_ALE_CTL_REG_BYPASS, + common->ale_base + AM65_CPSW_ALE_CTL_REG); + + /* port 0 put into forward mode */ + writel(AM65_CPSW_ALE_PN_CTL_REG_MODE_FORWARD, + common->ale_base + AM65_CPSW_ALE_PN_CTL_REG(0)); + + writel(AM65_CPSW_ALE_DEFTHREAD_EN, + common->ale_base + AM65_CPSW_ALE_THREADMAPDEF_REG); + + common->started++; + + return 0; + +err_dis_tx: + dma_disable(common->dma_tx); +err_free_rx: + dma_free(common->dma_rx); +err_free_tx: + dma_free(common->dma_tx); +err_off_clk: + clk_disable(common->fclk); +err_off_pwrdm: + dev_err(common->dev, "%s end error\n", __func__); + + return ret; +} + +static int am65_cpsw_start(struct eth_device *edev) +{ + struct am65_cpsw_port *port = edev_to_port(edev); + struct device *dev = &port->dev; + struct am65_cpsw_common *common = port->cpsw_common; + int ret; + + ret = am65_cpsw_common_start(common); + if (ret) + return ret; + + ret = phy_device_connect(edev, NULL, -1, + am65_cpsw_update_link, 0, port->interface); + if (ret) + return ret; + + /* enable statistics */ + writel(BIT(0) | BIT(port->port_id), + common->cpsw_base + AM65_CPSW_STAT_PORT_EN_REG); + + /* Port x Max length register */ + writel(UDMA_RX_BUF_SIZE, port->port_base + AM65_CPSW_PN_RX_MAXLEN_REG); + + /* Port x ALE: mac_only, Forwarding */ + writel(AM65_CPSW_ALE_PN_CTL_REG_MAC_ONLY | + AM65_CPSW_ALE_PN_CTL_REG_MODE_FORWARD, + common->ale_base + AM65_CPSW_ALE_PN_CTL_REG(port->port_id)); + + port->mac_control = 0; + if (!am65_cpsw_macsl_reset(port)) { + dev_err(dev, "mac_sl reset failed\n"); + ret = -EFAULT; + goto err; + } + + return 0; +err: + writel(0, port->macsl_base + AM65_CPSW_MACSL_CTL_REG); + /* disable ports */ + writel(0, common->ale_base + AM65_CPSW_ALE_PN_CTL_REG(port->port_id)); + if (!am65_cpsw_macsl_wait_for_idle(port)) + dev_err(common->dev, "mac_sl idle timeout\n"); + + return ret; +} + +static int am65_cpsw_send(struct eth_device *edev, void *packet, int length) +{ + struct am65_cpsw_port *port = edev_to_port(edev); + struct am65_cpsw_common *common = port->cpsw_common; + struct ti_udma_drv_packet_data packet_data; + dma_addr_t dma; + int ret; + + if (!common->started) + return -ENETDOWN; + + packet_data.pkt_type = AM65_CPSW_CPPI_PKT_TYPE; + packet_data.dest_tag = port->port_id; + + dma = dma_map_single(common->dev, packet, length, DMA_TO_DEVICE); + if (dma_mapping_error(common->dev, dma)) + return -EFAULT; + + ret = dma_send(common->dma_tx, dma, length, &packet_data); + if (ret) { + dev_err(&port->dev, "TX dma_send failed %d\n", ret); + return ret; + } + + dma_unmap_single(common->dev, dma, length, DMA_TO_DEVICE); + + return 0; +} + +static void am65_cpsw_recv(struct eth_device *edev) +{ + struct am65_cpsw_port *port = edev_to_port(edev); + struct am65_cpsw_common *common = port->cpsw_common; + struct ti_udma_drv_packet_data packet_data; + dma_addr_t pkt; + int ret; + u32 num, port_id; + bool valid = true; + + if (!common->started) + return; + + ret = dma_receive(common->dma_rx, &pkt, &packet_data); + if (!ret) + return; + if (ret < 0) { + dev_err(common->dev, "dma failed with %d\n", ret); + return; + } + + dma_sync_single_for_cpu(common->dev, pkt, ret, DMA_FROM_DEVICE); + + /* + * We have multiple ports (with an ethernet device per port), pick + * the right ethernet devices based on the incoming tag id. + */ + + port_id = packet_data.src_tag; + if (port_id >= AM65_CPSW_CPSWNU_MAX_PORTS) { + dev_err(common->dev, "received pkt on invalid port_id %d\n", port_id); + valid = false; + } + + port = &common->ports[port_id]; + if (!port->enabled) { + dev_err(common->dev, "received pkt on disabled port_id %d\n", port_id); + valid = false; + } + + if (valid) + net_receive(&port->edev, (void *)pkt, ret); + + num = common->rx_next % UDMA_RX_DESC_NUM; + + dma_sync_single_for_device(common->dev, pkt, ret, DMA_FROM_DEVICE); + + ret = dma_prepare_rcv_buf(common->dma_rx, pkt, UDMA_RX_BUF_SIZE); + if (ret) + dev_err(common->dev, "RX dma free_pkt failed %d\n", ret); + + common->rx_next++; +} + +static void am65_cpsw_common_stop(struct am65_cpsw_common *common) +{ + common->started--; + + if (common->started) + return; + + writel(0, common->ale_base + AM65_CPSW_ALE_PN_CTL_REG(0)); + writel(0, common->ale_base + AM65_CPSW_ALE_CTL_REG); + writel(0, common->cpsw_base + AM65_CPSW_CTL_REG); + + dma_disable(common->dma_tx); + dma_release(common->dma_tx); + + dma_disable(common->dma_rx); + dma_release(common->dma_rx); +} + +static void am65_cpsw_stop(struct eth_device *edev) +{ + struct am65_cpsw_port *port = edev_to_port(edev); + struct am65_cpsw_common *common = port->cpsw_common; + + if (!am65_cpsw_macsl_wait_for_idle(port)) + dev_err(&port->dev, "mac_sl idle timeout\n"); + + writel(0, common->ale_base + AM65_CPSW_ALE_PN_CTL_REG(port->port_id)); + writel(0, port->macsl_base + AM65_CPSW_MACSL_CTL_REG); + + am65_cpsw_common_stop(common); +} + +static int am65_cpsw_am654_get_efuse_macid(struct am65_cpsw_port *port, + u8 *mac_addr) +{ + u32 mac_lo, mac_hi, offset; + struct regmap *syscon; + int ret; + + syscon = syscon_regmap_lookup_by_phandle(port->device_node, "ti,syscon-efuse"); + if (IS_ERR(syscon)) { + if (PTR_ERR(syscon) == -ENODEV) + return 0; + return PTR_ERR(syscon); + } + + ret = of_property_read_u32_index(port->device_node, "ti,syscon-efuse", 1, &offset); + if (ret) + return ret; + + regmap_read(syscon, offset, &mac_lo); + regmap_read(syscon, offset + 4, &mac_hi); + + mac_addr[0] = (mac_hi >> 8) & 0xff; + mac_addr[1] = mac_hi & 0xff; + mac_addr[2] = (mac_lo >> 24) & 0xff; + mac_addr[3] = (mac_lo >> 16) & 0xff; + mac_addr[4] = (mac_lo >> 8) & 0xff; + mac_addr[5] = mac_lo & 0xff; + + return 0; +} + +static int am65_cpsw_get_ethaddr(struct eth_device *edev, unsigned char *mac) +{ + struct am65_cpsw_port *port = edev_to_port(edev); + + am65_cpsw_am654_get_efuse_macid(port, mac); + + return 0; +} + +#define mac_hi(mac) (((mac)[0] << 0) | ((mac)[1] << 8) | \ + ((mac)[2] << 16) | ((mac)[3] << 24)) +#define mac_lo(mac) (((mac)[4] << 0) | ((mac)[5] << 8)) + +static int am65_cpsw_set_ethaddr(struct eth_device *edev, const unsigned char *mac) +{ + struct am65_cpsw_port *port = edev_to_port(edev); + + writel(mac_hi(mac), port->port_base + AM65_CPSW_PN_REG_SA_H); + writel(mac_lo(mac), port->port_base + AM65_CPSW_PN_REG_SA_L); + + return 0; +} + +static int am65_cpsw_ofdata_parse_phy(struct am65_cpsw_port *port) +{ + int ret; + + ret = of_get_phy_mode(port->device_node); + if (ret < 0) + port->interface = PHY_INTERFACE_MODE_MII; + else + port->interface = ret; + + return 0; +} + +static int am65_cpsw_port_probe(struct am65_cpsw_common *cpsw_common, + struct am65_cpsw_port *port) +{ + struct eth_device *edev; + struct device *dev; + int ret; + + if (!port->enabled) + return 0; + + dev = &port->dev; + + dev_set_name(dev, "cpsw-slave"); + dev->id = port->port_id; + dev->parent = cpsw_common->dev; + dev->of_node = port->device_node; + ret = register_device(dev); + if (ret) + return ret; + + port->cpsw_common = cpsw_common; + + ret = am65_cpsw_ofdata_parse_phy(port); + if (ret) + goto out; + + ret = am65_cpsw_gmii_sel_k3(port, port->interface); + if (ret) + goto out; + + edev = &port->edev; + edev->parent = dev; + + edev->open = am65_cpsw_start; + edev->halt = am65_cpsw_stop; + edev->send = am65_cpsw_send; + edev->recv = am65_cpsw_recv; + edev->get_ethaddr = am65_cpsw_get_ethaddr; + edev->set_ethaddr = am65_cpsw_set_ethaddr; + + ret = eth_register(edev); + if (!ret) + return 0; +out: + return ret; +} + +static int am65_cpsw_probe_nuss(struct device *dev) +{ + struct am65_cpsw_common *cpsw_common; + struct device_node *ports_np, *node; + int ret, i; + + cpsw_common = xzalloc(sizeof(*cpsw_common)); + + cpsw_common->dev = dev; + cpsw_common->ss_base = dev_request_mem_region(dev, 0); + if (IS_ERR(cpsw_common->ss_base)) + return -EINVAL; + + cpsw_common->fclk = clk_get(dev, "fck"); + if (IS_ERR(cpsw_common->fclk)) + return dev_err_probe(dev, PTR_ERR(cpsw_common->fclk), "failed to get clock\n"); + + cpsw_common->cpsw_base = cpsw_common->ss_base + AM65_CPSW_CPSW_NU_BASE; + cpsw_common->ale_base = cpsw_common->cpsw_base + + AM65_CPSW_CPSW_NU_ALE_BASE; + + dev->priv = cpsw_common; + + ports_np = of_get_child_by_name(dev->of_node, "ethernet-ports"); + if (!ports_np) { + ret = -ENOENT; + goto out; + } + + for_each_child_of_node(ports_np, node) { + const char *node_name; + u32 port_id; + bool enabled; + struct am65_cpsw_port *port; + + node_name = node->name; + + enabled = of_device_is_available(node); + + ret = of_property_read_u32(node, "reg", &port_id); + if (ret) { + dev_err(dev, "%s: failed to get port_id (%d)\n", + node_name, ret); + goto out; + } + + if (port_id >= AM65_CPSW_CPSWNU_MAX_PORTS) { + dev_err(dev, "%s: invalid port_id (%d)\n", + node_name, port_id); + ret = -EINVAL; + goto out; + } + cpsw_common->port_num++; + + if (!port_id) + continue; + + port = &cpsw_common->ports[port_id]; + + port->enabled = true; + port->device_node = node; + } + + for (i = 0; i < AM65_CPSW_CPSWNU_MAX_PORTS; i++) { + struct am65_cpsw_port *port = &cpsw_common->ports[i]; + + port->port_id = i; + port->port_base = cpsw_common->cpsw_base + + AM65_CPSW_CPSW_NU_PORTS_OFFSET + + (i * AM65_CPSW_CPSW_NU_PORTS_OFFSET); + port->port_sgmii_base = cpsw_common->ss_base + + (i * AM65_CPSW_SGMII_BASE); + port->macsl_base = port->port_base + + AM65_CPSW_CPSW_NU_PORT_MACSL_OFFSET; + + ret = am65_cpsw_port_probe(cpsw_common, port); + + if (ret) + dev_err(dev, "Failed to probe port %d\n", port->port_id); + + } + + cpsw_common->bus_freq = AM65_CPSW_MDIO_BUS_FREQ_DEF; + of_property_read_u32(dev->of_node, "bus_freq", &cpsw_common->bus_freq); + + dev_dbg(dev, "K3 CPSW: nuss_ver: 0x%08X cpsw_ver: 0x%08X ale_ver: 0x%08X Ports:%u\n", + readl(cpsw_common->ss_base), + readl(cpsw_common->cpsw_base), + readl(cpsw_common->ale_base), + cpsw_common->port_num); + + of_platform_populate(dev->of_node, NULL, dev); +out: + return ret; +} + +static void am65_cpsw_remove_nuss(struct device *dev) +{ + struct am65_cpsw_common *cpsw_common = dev->priv; + int i; + + for (i = 0; i < AM65_CPSW_CPSWNU_MAX_PORTS; i++) { + struct am65_cpsw_port *port = &cpsw_common->ports[i]; + + if (!port->enabled) + continue; + + am65_cpsw_stop(&port->edev); + } +} + +static const struct of_device_id am65_cpsw_nuss_ids[] = { + { .compatible = "ti,am654-cpsw-nuss" }, + { .compatible = "ti,j721e-cpsw-nuss" }, + { .compatible = "ti,am642-cpsw-nuss" }, + { } +}; + +static struct driver am65_cpsw_nuss = { + .probe = am65_cpsw_probe_nuss, + .remove = am65_cpsw_remove_nuss, + .name = "am65_cpsw_nuss", + .of_compatible = am65_cpsw_nuss_ids, +}; + +device_platform_driver(am65_cpsw_nuss); -- 2.39.5