mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2026-06-02 09:46:37 +03:00
drivers: net: Add T-Head DWMAC glue layer
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>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
288
drivers/net/dwmac_thead.c
Normal file
288
drivers/net/dwmac_thead.c
Normal file
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user