]> git.ipfire.org Git - thirdparty/u-boot.git/commitdiff
drivers: net: Add T-Head DWMAC glue layer
authorYao Zi <ziyao@disroot.org>
Thu, 10 Jul 2025 03:41:59 +0000 (03:41 +0000)
committerLeo Yu-Chi Liang <ycliang@andestech.com>
Thu, 17 Jul 2025 06:37:40 +0000 (14:37 +0800)
The Designware IP integrated in TH1520 SoC requires extra clock
configuration to operate correctly. The Linux kernel's T-Head DWMAC glue
driver is ported and adapted to U-Boot's API.

Signed-off-by: Yao Zi <ziyao@disroot.org>
Acked-by: Leo Yu-Chi Liang <ycliang@andestech.com>
MAINTAINERS
drivers/net/Kconfig
drivers/net/Makefile
drivers/net/dwmac_thead.c [new file with mode: 0644]

index f98acbc8885c6d9be544d3d2265ac5f68d604558..ce7f7e035052756362d62453ae10e62c80280d1b 100644 (file)
@@ -1576,6 +1576,7 @@ M:        Yao Zi <ziyao@disroot.org>
 S:     Maintained
 F:     arch/riscv/cpu/th1520/
 F:     drivers/clk/thead/clk-th1520-ap.c
+F:     drivers/net/dwmac_thead.c
 F:     drivers/pinctrl/pinctrl-th1520.c
 F:     drivers/ram/thead/th1520_ddr.c
 
index 950ed0f25a9ac5f08a6758ac5cbc3eaf2a1033eb..d942fa4e202d61247d512b987d53ddf98017abdc 100644 (file)
@@ -411,6 +411,14 @@ config ETH_DESIGNWARE_S700
          This provides glue layer to use Synopsys Designware Ethernet MAC
          present on Actions S700 SoC.
 
+config ETH_DESIGNWARE_THEAD
+       bool "T-Head glue driver for Synopsys Designware Ethernet MAC"
+       depends on ETH_DESIGNWARE
+       select DW_ALTDESCRIPTOR
+       help
+         This provides glue layer to use Synopsys Designware Ethernet MAC
+         present on T-Head SoCs.
+
 config DW_ALTDESCRIPTOR
        bool "Designware Ethernet MAC uses alternate (enhanced) descriptors"
        depends on ETH_DESIGNWARE
index 67bba3a85367537e0f19100230b217539109261a..79cc8b422b07675fbf6c4df50fe34889da5c33a6 100644 (file)
@@ -38,6 +38,7 @@ obj-$(CONFIG_ETH_DESIGNWARE) += designware.o
 obj-$(CONFIG_ETH_DESIGNWARE_MESON8B) += dwmac_meson8b.o
 obj-$(CONFIG_ETH_DESIGNWARE_S700) += dwmac_s700.o
 obj-$(CONFIG_ETH_DESIGNWARE_SOCFPGA) += dwmac_socfpga.o
+obj-$(CONFIG_ETH_DESIGNWARE_THEAD) += dwmac_thead.o
 obj-$(CONFIG_ETH_SANDBOX) += sandbox.o
 obj-$(CONFIG_ETH_SANDBOX_RAW) += sandbox-raw-bus.o
 obj-$(CONFIG_ETH_SANDBOX_RAW) += sandbox-raw.o
diff --git a/drivers/net/dwmac_thead.c b/drivers/net/dwmac_thead.c
new file mode 100644 (file)
index 0000000..138d71a
--- /dev/null
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-HEAD DWMAC platform driver
+ *
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ * Copyright (C) 2023 Jisheng Zhang <jszhang@kernel.org>
+ * Copyright (C) 2025 Yao Zi <ziyao@disroot.org>
+ *
+ */
+
+#include <asm/io.h>
+#include <clk.h>
+#include <dm.h>
+#include <linux/bitfield.h>
+#include <phy.h>
+
+#include "designware.h"
+
+#define GMAC_CLK_EN                    0x00
+#define  GMAC_TX_CLK_EN                        BIT(1)
+#define  GMAC_TX_CLK_N_EN              BIT(2)
+#define  GMAC_TX_CLK_OUT_EN            BIT(3)
+#define  GMAC_RX_CLK_EN                        BIT(4)
+#define  GMAC_RX_CLK_N_EN              BIT(5)
+#define  GMAC_EPHY_REF_CLK_EN          BIT(6)
+#define GMAC_RXCLK_DELAY_CTRL          0x04
+#define  GMAC_RXCLK_BYPASS             BIT(15)
+#define  GMAC_RXCLK_INVERT             BIT(14)
+#define  GMAC_RXCLK_DELAY              GENMASK(4, 0)
+#define GMAC_TXCLK_DELAY_CTRL          0x08
+#define  GMAC_TXCLK_BYPASS             BIT(15)
+#define  GMAC_TXCLK_INVERT             BIT(14)
+#define  GMAC_TXCLK_DELAY              GENMASK(4, 0)
+#define GMAC_PLLCLK_DIV                        0x0c
+#define  GMAC_PLLCLK_DIV_EN            BIT(31)
+#define  GMAC_PLLCLK_DIV_NUM           GENMASK(7, 0)
+#define GMAC_GTXCLK_SEL                        0x18
+#define  GMAC_GTXCLK_SEL_PLL           BIT(0)
+#define GMAC_INTF_CTRL                 0x1c
+#define  PHY_INTF_MASK                 BIT(0)
+#define  PHY_INTF_RGMII                        FIELD_PREP(PHY_INTF_MASK, 1)
+#define  PHY_INTF_MII_GMII             FIELD_PREP(PHY_INTF_MASK, 0)
+#define GMAC_TXCLK_OEN                 0x20
+#define  TXCLK_DIR_MASK                        BIT(0)
+#define  TXCLK_DIR_OUTPUT              FIELD_PREP(TXCLK_DIR_MASK, 0)
+#define  TXCLK_DIR_INPUT               FIELD_PREP(TXCLK_DIR_MASK, 1)
+
+#define GMAC_RGMII_CLK_RATE            125000000
+
+struct dwmac_thead_plat {
+       struct dw_eth_pdata dw_eth_pdata;
+       void __iomem *apb_base;
+};
+
+static int dwmac_thead_set_phy_if(struct dwmac_thead_plat *plat)
+{
+       u32 phyif;
+
+       switch (plat->dw_eth_pdata.eth_pdata.phy_interface) {
+       case PHY_INTERFACE_MODE_MII:
+               phyif = PHY_INTF_MII_GMII;
+               break;
+       case PHY_INTERFACE_MODE_RGMII:
+       case PHY_INTERFACE_MODE_RGMII_ID:
+       case PHY_INTERFACE_MODE_RGMII_TXID:
+       case PHY_INTERFACE_MODE_RGMII_RXID:
+               phyif = PHY_INTF_RGMII;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       writel(phyif, plat->apb_base + GMAC_INTF_CTRL);
+       return 0;
+}
+
+static int dwmac_thead_set_txclk_dir(struct dwmac_thead_plat *plat)
+{
+       u32 txclk_dir;
+
+       switch (plat->dw_eth_pdata.eth_pdata.phy_interface) {
+       case PHY_INTERFACE_MODE_MII:
+               txclk_dir = TXCLK_DIR_INPUT;
+               break;
+       case PHY_INTERFACE_MODE_RGMII:
+       case PHY_INTERFACE_MODE_RGMII_ID:
+       case PHY_INTERFACE_MODE_RGMII_TXID:
+       case PHY_INTERFACE_MODE_RGMII_RXID:
+               txclk_dir = TXCLK_DIR_OUTPUT;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       writel(txclk_dir, plat->apb_base + GMAC_TXCLK_OEN);
+       return 0;
+}
+
+static unsigned long dwmac_thead_rgmii_tx_rate(int speed)
+{
+       switch (speed) {
+       case 10:
+               return 2500000;
+       case 100:
+               return 25000000;
+       case 1000:
+               return 125000000;
+       }
+
+       return -EINVAL;
+}
+
+static int dwmac_thead_set_clk_tx_rate(struct dwmac_thead_plat *plat,
+                                      struct dw_eth_dev *edev,
+                                      unsigned long tx_rate)
+{
+       unsigned long rate;
+       u32 div, reg;
+
+       rate = clk_get_rate(&edev->clocks[0]);
+
+       writel(0, plat->apb_base + GMAC_PLLCLK_DIV);
+
+       div = rate / tx_rate;
+       if (rate != tx_rate * div) {
+               pr_err("invalid gmac rate %lu\n", rate);
+               return -EINVAL;
+       }
+
+       reg = FIELD_PREP(GMAC_PLLCLK_DIV_EN, 1) |
+             FIELD_PREP(GMAC_PLLCLK_DIV_NUM, div);
+               writel(reg, plat->apb_base + GMAC_PLLCLK_DIV);
+
+       return 0;
+}
+
+static int dwmac_thead_enable_clk(struct dwmac_thead_plat *plat)
+{
+       u32 reg;
+
+       switch (plat->dw_eth_pdata.eth_pdata.phy_interface) {
+       case PHY_INTERFACE_MODE_MII:
+               reg = GMAC_RX_CLK_EN | GMAC_TX_CLK_EN;
+               break;
+
+       case PHY_INTERFACE_MODE_RGMII:
+       case PHY_INTERFACE_MODE_RGMII_ID:
+       case PHY_INTERFACE_MODE_RGMII_RXID:
+       case PHY_INTERFACE_MODE_RGMII_TXID:
+               /* use pll */
+               writel(GMAC_GTXCLK_SEL_PLL, plat->apb_base + GMAC_GTXCLK_SEL);
+               reg = GMAC_TX_CLK_EN | GMAC_TX_CLK_N_EN | GMAC_TX_CLK_OUT_EN |
+                     GMAC_RX_CLK_EN | GMAC_RX_CLK_N_EN;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       writel(reg, plat->apb_base + GMAC_CLK_EN);
+       return 0;
+}
+
+static int dwmac_thead_eth_start(struct udevice *dev)
+{
+       struct dwmac_thead_plat *plat = dev_get_plat(dev);
+       struct dw_eth_dev *edev = dev_get_priv(dev);
+       phy_interface_t interface;
+       bool is_rgmii;
+       long tx_rate;
+       int ret;
+
+       interface = plat->dw_eth_pdata.eth_pdata.phy_interface;
+       is_rgmii = (interface == PHY_INTERFACE_MODE_RGMII)      |
+                  (interface == PHY_INTERFACE_MODE_RGMII_ID)   |
+                  (interface == PHY_INTERFACE_MODE_RGMII_RXID) |
+                  (interface == PHY_INTERFACE_MODE_RGMII_TXID);
+
+       /*
+        * When operating in RGMII mode, the TX clock is generated by an
+        * internal divider and fed to the MAC. Configure and enable it before
+        * initializing the MAC.
+        */
+       if (is_rgmii) {
+               ret = dwmac_thead_set_clk_tx_rate(plat, edev,
+                                                 GMAC_RGMII_CLK_RATE);
+               if (ret)
+                       return ret;
+       }
+
+       ret = designware_eth_init(edev, plat->dw_eth_pdata.eth_pdata.enetaddr);
+       if (ret)
+               return ret;
+
+       if (is_rgmii) {
+               tx_rate = dwmac_thead_rgmii_tx_rate(edev->phydev->speed);
+               if (tx_rate < 0)
+                       return tx_rate;
+
+               ret = dwmac_thead_set_clk_tx_rate(plat, edev, tx_rate);
+               if (ret)
+                       return ret;
+       }
+
+       ret = designware_eth_enable(edev);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int dwmac_thead_probe(struct udevice *dev)
+{
+       struct dwmac_thead_plat *plat = dev_get_plat(dev);
+       unsigned int reg;
+       int ret;
+
+       ret = designware_eth_probe(dev);
+       if (ret)
+               return ret;
+
+       ret = dwmac_thead_set_phy_if(plat);
+       if (ret) {
+               pr_err("failed to set phy interface: %d\n", ret);
+               return ret;
+       }
+
+       ret = dwmac_thead_set_txclk_dir(plat);
+       if (ret) {
+               pr_err("failed to set TX clock direction: %d\n", ret);
+               return ret;
+       }
+
+       reg = readl(plat->apb_base + GMAC_RXCLK_DELAY_CTRL);
+       reg &= ~(GMAC_RXCLK_DELAY);
+       reg |= FIELD_PREP(GMAC_RXCLK_DELAY, 0);
+       writel(reg, plat->apb_base + GMAC_RXCLK_DELAY_CTRL);
+
+       reg = readl(plat->apb_base + GMAC_TXCLK_DELAY_CTRL);
+       reg &= ~(GMAC_TXCLK_DELAY);
+       reg |= FIELD_PREP(GMAC_TXCLK_DELAY, 0);
+       writel(reg, plat->apb_base + GMAC_TXCLK_DELAY_CTRL);
+
+       ret = dwmac_thead_enable_clk(plat);
+       if (ret)
+               pr_err("failed to enable clock: %d\n", ret);
+
+       return ret;
+}
+
+static int dwmac_thead_of_to_plat(struct udevice *dev)
+{
+       struct dwmac_thead_plat *pdata = dev_get_plat(dev);
+
+       pdata->apb_base = dev_read_addr_index_ptr(dev, 1);
+       if (!pdata->apb_base) {
+               pr_err("failed to get apb registers\n");
+               return -ENOENT;
+       }
+
+       return designware_eth_of_to_plat(dev);
+}
+
+static const struct eth_ops dwmac_thead_eth_ops = {
+       .start                  = dwmac_thead_eth_start,
+       .send                   = designware_eth_send,
+       .recv                   = designware_eth_recv,
+       .free_pkt               = designware_eth_free_pkt,
+       .stop                   = designware_eth_stop,
+       .write_hwaddr           = designware_eth_write_hwaddr,
+};
+
+static const struct udevice_id dwmac_thead_match[] = {
+       { .compatible = "thead,th1520-gmac" },
+       { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(dwmac_thead) = {
+       .name           = "dwmac_thead",
+       .id             = UCLASS_ETH,
+       .of_match       = dwmac_thead_match,
+       .of_to_plat     = dwmac_thead_of_to_plat,
+       .probe          = dwmac_thead_probe,
+       .ops            = &dwmac_thead_eth_ops,
+       .priv_auto      = sizeof(struct dw_eth_dev),
+       .plat_auto      = sizeof(struct dwmac_thead_plat),
+       .flags          = DM_FLAG_ALLOC_PRIV_DMA,
+};