404 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			404 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-only
 | |
| #include <linux/iommu.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/of.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/stmmac.h>
 | |
| #include <linux/clk.h>
 | |
| 
 | |
| #include "stmmac_platform.h"
 | |
| 
 | |
| static const char *const mgbe_clks[] = {
 | |
| 	"rx-pcs", "tx", "tx-pcs", "mac-divider", "mac", "mgbe", "ptp-ref", "mac"
 | |
| };
 | |
| 
 | |
| struct tegra_mgbe {
 | |
| 	struct device *dev;
 | |
| 
 | |
| 	struct clk_bulk_data *clks;
 | |
| 
 | |
| 	struct reset_control *rst_mac;
 | |
| 	struct reset_control *rst_pcs;
 | |
| 
 | |
| 	u32 iommu_sid;
 | |
| 
 | |
| 	void __iomem *hv;
 | |
| 	void __iomem *regs;
 | |
| 	void __iomem *xpcs;
 | |
| 
 | |
| 	struct mii_bus *mii;
 | |
| };
 | |
| 
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL 0x801c
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD BIT(31)
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY BIT(10)
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET BIT(9)
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN BIT(8)
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP (BIT(7) | BIT(6))
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ BIT(5)
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ BIT(4)
 | |
| #define XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN BIT(0)
 | |
| #define XPCS_WRAP_UPHY_HW_INIT_CTRL 0x8020
 | |
| #define XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN BIT(0)
 | |
| #define XPCS_WRAP_UPHY_HW_INIT_CTRL_RX_EN BIT(2)
 | |
| #define XPCS_WRAP_UPHY_STATUS 0x8044
 | |
| #define XPCS_WRAP_UPHY_STATUS_TX_P_UP BIT(0)
 | |
| #define XPCS_WRAP_IRQ_STATUS 0x8050
 | |
| #define XPCS_WRAP_IRQ_STATUS_PCS_LINK_STS BIT(6)
 | |
| 
 | |
| #define XPCS_REG_ADDR_SHIFT 10
 | |
| #define XPCS_REG_ADDR_MASK 0x1fff
 | |
| #define XPCS_ADDR 0x3fc
 | |
| 
 | |
| #define MGBE_WRAP_COMMON_INTR_ENABLE	0x8704
 | |
| #define MAC_SBD_INTR			BIT(2)
 | |
| #define MGBE_WRAP_AXI_ASID0_CTRL	0x8400
 | |
| 
 | |
| static int __maybe_unused tegra_mgbe_suspend(struct device *dev)
 | |
| {
 | |
| 	struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(dev);
 | |
| 	int err;
 | |
| 
 | |
| 	err = stmmac_suspend(dev);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 
 | |
| 	return reset_control_assert(mgbe->rst_mac);
 | |
| }
 | |
| 
 | |
| static int __maybe_unused tegra_mgbe_resume(struct device *dev)
 | |
| {
 | |
| 	struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(dev);
 | |
| 	u32 value;
 | |
| 	int err;
 | |
| 
 | |
| 	err = clk_bulk_prepare_enable(ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 	if (err < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	err = reset_control_deassert(mgbe->rst_mac);
 | |
| 	if (err < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	/* Enable common interrupt at wrapper level */
 | |
| 	writel(MAC_SBD_INTR, mgbe->regs + MGBE_WRAP_COMMON_INTR_ENABLE);
 | |
| 
 | |
| 	/* Program SID */
 | |
| 	writel(mgbe->iommu_sid, mgbe->hv + MGBE_WRAP_AXI_ASID0_CTRL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_STATUS);
 | |
| 	if ((value & XPCS_WRAP_UPHY_STATUS_TX_P_UP) == 0) {
 | |
| 		value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
 | |
| 		value |= XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN;
 | |
| 		writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
 | |
| 	}
 | |
| 
 | |
| 	err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL, value,
 | |
| 				 (value & XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN) == 0,
 | |
| 				 500, 500 * 2000);
 | |
| 	if (err < 0) {
 | |
| 		dev_err(mgbe->dev, "timeout waiting for TX lane to become enabled\n");
 | |
| 		clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	err = stmmac_resume(dev);
 | |
| 	if (err < 0)
 | |
| 		clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int mgbe_uphy_lane_bringup_serdes_up(struct net_device *ndev, void *mgbe_data)
 | |
| {
 | |
| 	struct tegra_mgbe *mgbe = (struct tegra_mgbe *)mgbe_data;
 | |
| 	u32 value;
 | |
| 	int err;
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value &= ~XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	usleep_range(10, 20);  /* 50ns min delay needed as per HW design */
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	usleep_range(10, 20);  /* 500ns min delay needed as per HW design */
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL, value,
 | |
| 				 (value & XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN) == 0,
 | |
| 				 1000, 1000 * 2000);
 | |
| 	if (err < 0) {
 | |
| 		dev_err(mgbe->dev, "timeout waiting for RX calibration to become enabled\n");
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	usleep_range(10, 20);  /* 50ns min delay needed as per HW design */
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	usleep_range(10, 20);  /* 50ns min delay needed as per HW design */
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	usleep_range(10, 20);  /* 50ns min delay needed as per HW design */
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	msleep(30);  /* 30ms delay needed as per HW design */
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_IRQ_STATUS, value,
 | |
| 				 value & XPCS_WRAP_IRQ_STATUS_PCS_LINK_STS,
 | |
| 				 500, 500 * 2000);
 | |
| 	if (err < 0) {
 | |
| 		dev_err(mgbe->dev, "timeout waiting for link to become ready\n");
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	/* clear status */
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_IRQ_STATUS);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void mgbe_uphy_lane_bringup_serdes_down(struct net_device *ndev, void *mgbe_data)
 | |
| {
 | |
| 	struct tegra_mgbe *mgbe = (struct tegra_mgbe *)mgbe_data;
 | |
| 	u32 value;
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| 	value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ;
 | |
| 	writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
 | |
| }
 | |
| 
 | |
| static int tegra_mgbe_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct plat_stmmacenet_data *plat;
 | |
| 	struct stmmac_resources res;
 | |
| 	struct tegra_mgbe *mgbe;
 | |
| 	int irq, err, i;
 | |
| 	u32 value;
 | |
| 
 | |
| 	mgbe = devm_kzalloc(&pdev->dev, sizeof(*mgbe), GFP_KERNEL);
 | |
| 	if (!mgbe)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	mgbe->dev = &pdev->dev;
 | |
| 
 | |
| 	memset(&res, 0, sizeof(res));
 | |
| 
 | |
| 	irq = platform_get_irq(pdev, 0);
 | |
| 	if (irq < 0)
 | |
| 		return irq;
 | |
| 
 | |
| 	mgbe->hv = devm_platform_ioremap_resource_byname(pdev, "hypervisor");
 | |
| 	if (IS_ERR(mgbe->hv))
 | |
| 		return PTR_ERR(mgbe->hv);
 | |
| 
 | |
| 	mgbe->regs = devm_platform_ioremap_resource_byname(pdev, "mac");
 | |
| 	if (IS_ERR(mgbe->regs))
 | |
| 		return PTR_ERR(mgbe->regs);
 | |
| 
 | |
| 	mgbe->xpcs = devm_platform_ioremap_resource_byname(pdev, "xpcs");
 | |
| 	if (IS_ERR(mgbe->xpcs))
 | |
| 		return PTR_ERR(mgbe->xpcs);
 | |
| 
 | |
| 	/* get controller's stream id from iommu property in device tree */
 | |
| 	if (!tegra_dev_iommu_get_stream_id(mgbe->dev, &mgbe->iommu_sid)) {
 | |
| 		dev_err(mgbe->dev, "failed to get iommu stream id\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	res.addr = mgbe->regs;
 | |
| 	res.irq = irq;
 | |
| 
 | |
| 	mgbe->clks = devm_kcalloc(&pdev->dev, ARRAY_SIZE(mgbe_clks),
 | |
| 				  sizeof(*mgbe->clks), GFP_KERNEL);
 | |
| 	if (!mgbe->clks)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	for (i = 0; i <  ARRAY_SIZE(mgbe_clks); i++)
 | |
| 		mgbe->clks[i].id = mgbe_clks[i];
 | |
| 
 | |
| 	err = devm_clk_bulk_get(mgbe->dev, ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 	if (err < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	err = clk_bulk_prepare_enable(ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 	if (err < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	/* Perform MAC reset */
 | |
| 	mgbe->rst_mac = devm_reset_control_get(&pdev->dev, "mac");
 | |
| 	if (IS_ERR(mgbe->rst_mac)) {
 | |
| 		err = PTR_ERR(mgbe->rst_mac);
 | |
| 		goto disable_clks;
 | |
| 	}
 | |
| 
 | |
| 	err = reset_control_assert(mgbe->rst_mac);
 | |
| 	if (err < 0)
 | |
| 		goto disable_clks;
 | |
| 
 | |
| 	usleep_range(2000, 4000);
 | |
| 
 | |
| 	err = reset_control_deassert(mgbe->rst_mac);
 | |
| 	if (err < 0)
 | |
| 		goto disable_clks;
 | |
| 
 | |
| 	/* Perform PCS reset */
 | |
| 	mgbe->rst_pcs = devm_reset_control_get(&pdev->dev, "pcs");
 | |
| 	if (IS_ERR(mgbe->rst_pcs)) {
 | |
| 		err = PTR_ERR(mgbe->rst_pcs);
 | |
| 		goto disable_clks;
 | |
| 	}
 | |
| 
 | |
| 	err = reset_control_assert(mgbe->rst_pcs);
 | |
| 	if (err < 0)
 | |
| 		goto disable_clks;
 | |
| 
 | |
| 	usleep_range(2000, 4000);
 | |
| 
 | |
| 	err = reset_control_deassert(mgbe->rst_pcs);
 | |
| 	if (err < 0)
 | |
| 		goto disable_clks;
 | |
| 
 | |
| 	plat = devm_stmmac_probe_config_dt(pdev, res.mac);
 | |
| 	if (IS_ERR(plat)) {
 | |
| 		err = PTR_ERR(plat);
 | |
| 		goto disable_clks;
 | |
| 	}
 | |
| 
 | |
| 	plat->has_xgmac = 1;
 | |
| 	plat->flags |= STMMAC_FLAG_TSO_EN;
 | |
| 	plat->pmt = 1;
 | |
| 	plat->bsp_priv = mgbe;
 | |
| 
 | |
| 	if (!plat->mdio_node)
 | |
| 		plat->mdio_node = of_get_child_by_name(pdev->dev.of_node, "mdio");
 | |
| 
 | |
| 	if (!plat->mdio_bus_data) {
 | |
| 		plat->mdio_bus_data = devm_kzalloc(&pdev->dev, sizeof(*plat->mdio_bus_data),
 | |
| 						   GFP_KERNEL);
 | |
| 		if (!plat->mdio_bus_data) {
 | |
| 			err = -ENOMEM;
 | |
| 			goto disable_clks;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	plat->mdio_bus_data->needs_reset = true;
 | |
| 
 | |
| 	value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_STATUS);
 | |
| 	if ((value & XPCS_WRAP_UPHY_STATUS_TX_P_UP) == 0) {
 | |
| 		value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
 | |
| 		value |= XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN;
 | |
| 		writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
 | |
| 	}
 | |
| 
 | |
| 	err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL, value,
 | |
| 				 (value & XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN) == 0,
 | |
| 				 500, 500 * 2000);
 | |
| 	if (err < 0) {
 | |
| 		dev_err(mgbe->dev, "timeout waiting for TX lane to become enabled\n");
 | |
| 		goto disable_clks;
 | |
| 	}
 | |
| 
 | |
| 	plat->serdes_powerup = mgbe_uphy_lane_bringup_serdes_up;
 | |
| 	plat->serdes_powerdown = mgbe_uphy_lane_bringup_serdes_down;
 | |
| 
 | |
| 	/* Tx FIFO Size - 128KB */
 | |
| 	plat->tx_fifo_size = 131072;
 | |
| 	/* Rx FIFO Size - 192KB */
 | |
| 	plat->rx_fifo_size = 196608;
 | |
| 
 | |
| 	/* Enable common interrupt at wrapper level */
 | |
| 	writel(MAC_SBD_INTR, mgbe->regs + MGBE_WRAP_COMMON_INTR_ENABLE);
 | |
| 
 | |
| 	/* Program SID */
 | |
| 	writel(mgbe->iommu_sid, mgbe->hv + MGBE_WRAP_AXI_ASID0_CTRL);
 | |
| 
 | |
| 	plat->flags |= STMMAC_FLAG_SERDES_UP_AFTER_PHY_LINKUP;
 | |
| 
 | |
| 	err = stmmac_dvr_probe(&pdev->dev, plat, &res);
 | |
| 	if (err < 0)
 | |
| 		goto disable_clks;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| disable_clks:
 | |
| 	clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void tegra_mgbe_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(&pdev->dev);
 | |
| 
 | |
| 	clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
 | |
| 
 | |
| 	stmmac_pltfr_remove(pdev);
 | |
| }
 | |
| 
 | |
| static const struct of_device_id tegra_mgbe_match[] = {
 | |
| 	{ .compatible = "nvidia,tegra234-mgbe", },
 | |
| 	{ }
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, tegra_mgbe_match);
 | |
| 
 | |
| static SIMPLE_DEV_PM_OPS(tegra_mgbe_pm_ops, tegra_mgbe_suspend, tegra_mgbe_resume);
 | |
| 
 | |
| static struct platform_driver tegra_mgbe_driver = {
 | |
| 	.probe = tegra_mgbe_probe,
 | |
| 	.remove_new = tegra_mgbe_remove,
 | |
| 	.driver = {
 | |
| 		.name = "tegra-mgbe",
 | |
| 		.pm		= &tegra_mgbe_pm_ops,
 | |
| 		.of_match_table = tegra_mgbe_match,
 | |
| 	},
 | |
| };
 | |
| module_platform_driver(tegra_mgbe_driver);
 | |
| 
 | |
| MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
 | |
| MODULE_DESCRIPTION("NVIDIA Tegra MGBE driver");
 | |
| MODULE_LICENSE("GPL");
 |