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