| // 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, |
| }; |