// SPDX-License-Identifier: GPL-2.0 /* * T-HEAD DWMAC platform driver * * Copyright (C) 2021 Alibaba Group Holding Limited. * Copyright (C) 2023 Jisheng Zhang * Copyright (C) 2025 Yao Zi * */ #include #include #include #include #include #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, };