| // SPDX-License-Identifier: GPL-2.0-only |
| #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; |
| |
| 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 |
| #define MGBE_SID 0x6 |
| |
| 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_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); |
| |
| 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_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"); |