summaryrefslogtreecommitdiff
path: root/drivers/net
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/Kconfig8
-rw-r--r--drivers/net/Makefile1
-rw-r--r--drivers/net/dwmac_thead.c288
3 files changed, 297 insertions, 0 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 950ed0f25a9..d942fa4e202 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -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
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 67bba3a8536..79cc8b422b0 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -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
index 00000000000..138d71a6202
--- /dev/null
+++ b/drivers/net/dwmac_thead.c
@@ -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,
+};