mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Nvidia's Tegra MGBE controllers require the IOMMU "Stream ID" (SID) to be
written to the MGBE_WRAP_AXI_ASID0_CTRL register.
The current driver is hard coded to use MGBE0's SID for all controllers.
This causes softirq time outs and kernel panics when using controllers
other than MGBE0.
Example dmesg errors when an ethernet cable is connected to MGBE1:
[ 116.133290] tegra-mgbe 6910000.ethernet eth1: Link is Up - 1Gbps/Full - flow control rx/tx
[ 121.851283] tegra-mgbe 6910000.ethernet eth1: NETDEV WATCHDOG: CPU: 5: transmit queue 0 timed out 5690 ms
[ 121.851782] tegra-mgbe 6910000.ethernet eth1: Reset adapter.
[ 121.892464] tegra-mgbe 6910000.ethernet eth1: Register MEM_TYPE_PAGE_POOL RxQ-0
[ 121.905920] tegra-mgbe 6910000.ethernet eth1: PHY [stmmac-1:00] driver [Aquantia AQR113] (irq=171)
[ 121.907356] tegra-mgbe 6910000.ethernet eth1: Enabling Safety Features
[ 121.907578] tegra-mgbe 6910000.ethernet eth1: IEEE 1588-2008 Advanced Timestamp supported
[ 121.908399] tegra-mgbe 6910000.ethernet eth1: registered PTP clock
[ 121.908582] tegra-mgbe 6910000.ethernet eth1: configuring for phy/10gbase-r link mode
[ 125.961292] tegra-mgbe 6910000.ethernet eth1: Link is Up - 1Gbps/Full - flow control rx/tx
[ 181.921198] rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
[ 181.921404] rcu: 7-....: (1 GPs behind) idle=540c/1/0x4000000000000002 softirq=1748/1749 fqs=2337
[ 181.921684] rcu: (detected by 4, t=6002 jiffies, g=1357, q=1254 ncpus=8)
[ 181.921878] Sending NMI from CPU 4 to CPUs 7:
[ 181.921886] NMI backtrace for cpu 7
[ 181.922131] CPU: 7 UID: 0 PID: 0 Comm: swapper/7 Kdump: loaded Not tainted 6.13.0-rc3+ #6
[ 181.922390] Hardware name: NVIDIA CTI Forge + Orin AGX/Jetson, BIOS 202402.1-Unknown 10/28/2024
[ 181.922658] pstate: 40400009 (nZcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 181.922847] pc : handle_softirqs+0x98/0x368
[ 181.922978] lr : __do_softirq+0x18/0x20
[ 181.923095] sp : ffff80008003bf50
[ 181.923189] x29: ffff80008003bf50 x28: 0000000000000008 x27: 0000000000000000
[ 181.923379] x26: ffffce78ea277000 x25: 0000000000000000 x24: 0000001c61befda0
[ 181.924486] x23: 0000000060400009 x22: ffffce78e99918bc x21: ffff80008018bd70
[ 181.925568] x20: ffffce78e8bb00d8 x19: ffff80008018bc20 x18: 0000000000000000
[ 181.926655] x17: ffff318ebe7d3000 x16: ffff800080038000 x15: 0000000000000000
[ 181.931455] x14: ffff000080816680 x13: ffff318ebe7d3000 x12: 000000003464d91d
[ 181.938628] x11: 0000000000000040 x10: ffff000080165a70 x9 : ffffce78e8bb0160
[ 181.945804] x8 : ffff8000827b3160 x7 : f9157b241586f343 x6 : eeb6502a01c81c74
[ 181.953068] x5 : a4acfcdd2e8096bb x4 : ffffce78ea277340 x3 : 00000000ffffd1e1
[ 181.960329] x2 : 0000000000000101 x1 : ffffce78ea277340 x0 : ffff318ebe7d3000
[ 181.967591] Call trace:
[ 181.970043] handle_softirqs+0x98/0x368 (P)
[ 181.974240] __do_softirq+0x18/0x20
[ 181.977743] ____do_softirq+0x14/0x28
[ 181.981415] call_on_irq_stack+0x24/0x30
[ 181.985180] do_softirq_own_stack+0x20/0x30
[ 181.989379] __irq_exit_rcu+0x114/0x140
[ 181.993142] irq_exit_rcu+0x14/0x28
[ 181.996816] el1_interrupt+0x44/0xb8
[ 182.000316] el1h_64_irq_handler+0x14/0x20
[ 182.004343] el1h_64_irq+0x80/0x88
[ 182.007755] cpuidle_enter_state+0xc4/0x4a8 (P)
[ 182.012305] cpuidle_enter+0x3c/0x58
[ 182.015980] cpuidle_idle_call+0x128/0x1c0
[ 182.020005] do_idle+0xe0/0xf0
[ 182.023155] cpu_startup_entry+0x3c/0x48
[ 182.026917] secondary_start_kernel+0xdc/0x120
[ 182.031379] __secondary_switched+0x74/0x78
[ 212.971162] rcu: INFO: rcu_preempt detected expedited stalls on CPUs/tasks: { 7-.... } 6103 jiffies s: 417 root: 0x80/.
[ 212.985935] rcu: blocking rcu_node structures (internal RCU debug):
[ 212.992758] Sending NMI from CPU 0 to CPUs 7:
[ 212.998539] NMI backtrace for cpu 7
[ 213.004304] CPU: 7 UID: 0 PID: 0 Comm: swapper/7 Kdump: loaded Not tainted 6.13.0-rc3+ #6
[ 213.016116] Hardware name: NVIDIA CTI Forge + Orin AGX/Jetson, BIOS 202402.1-Unknown 10/28/2024
[ 213.030817] pstate: 40400009 (nZcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 213.040528] pc : handle_softirqs+0x98/0x368
[ 213.046563] lr : __do_softirq+0x18/0x20
[ 213.051293] sp : ffff80008003bf50
[ 213.055839] x29: ffff80008003bf50 x28: 0000000000000008 x27: 0000000000000000
[ 213.067304] x26: ffffce78ea277000 x25: 0000000000000000 x24: 0000001c61befda0
[ 213.077014] x23: 0000000060400009 x22: ffffce78e99918bc x21: ffff80008018bd70
[ 213.087339] x20: ffffce78e8bb00d8 x19: ffff80008018bc20 x18: 0000000000000000
[ 213.097313] x17: ffff318ebe7d3000 x16: ffff800080038000 x15: 0000000000000000
[ 213.107201] x14: ffff000080816680 x13: ffff318ebe7d3000 x12: 000000003464d91d
[ 213.116651] x11: 0000000000000040 x10: ffff000080165a70 x9 : ffffce78e8bb0160
[ 213.127500] x8 : ffff8000827b3160 x7 : 0a37b344852820af x6 : 3f049caedd1ff608
[ 213.138002] x5 : cff7cfdbfaf31291 x4 : ffffce78ea277340 x3 : 00000000ffffde04
[ 213.150428] x2 : 0000000000000101 x1 : ffffce78ea277340 x0 : ffff318ebe7d3000
[ 213.162063] Call trace:
[ 213.165494] handle_softirqs+0x98/0x368 (P)
[ 213.171256] __do_softirq+0x18/0x20
[ 213.177291] ____do_softirq+0x14/0x28
[ 213.182017] call_on_irq_stack+0x24/0x30
[ 213.186565] do_softirq_own_stack+0x20/0x30
[ 213.191815] __irq_exit_rcu+0x114/0x140
[ 213.196891] irq_exit_rcu+0x14/0x28
[ 213.202401] el1_interrupt+0x44/0xb8
[ 213.207741] el1h_64_irq_handler+0x14/0x20
[ 213.213519] el1h_64_irq+0x80/0x88
[ 213.217541] cpuidle_enter_state+0xc4/0x4a8 (P)
[ 213.224364] cpuidle_enter+0x3c/0x58
[ 213.228653] cpuidle_idle_call+0x128/0x1c0
[ 213.233993] do_idle+0xe0/0xf0
[ 213.237928] cpu_startup_entry+0x3c/0x48
[ 213.243791] secondary_start_kernel+0xdc/0x120
[ 213.249830] __secondary_switched+0x74/0x78
This bug has existed since the dwmac-tegra driver was added in Dec 2022
(See Fixes tag below for commit hash).
The Tegra234 SOC has 4 MGBE controllers, however Nvidia's Developer Kit
only uses MGBE0 which is why the bug was not found previously. Connect Tech
has many products that use 2 (or more) MGBE controllers.
The solution is to read the controller's SID from the existing "iommus"
device tree property. The 2nd field of the "iommus" device tree property
is the controller's SID.
Device tree snippet from tegra234.dtsi showing MGBE1's "iommus" property:
smmu_niso0: iommu@12000000 {
compatible = "nvidia,tegra234-smmu", "nvidia,smmu-500";
...
}
/* MGBE1 */
ethernet@6900000 {
compatible = "nvidia,tegra234-mgbe";
...
iommus = <&smmu_niso0 TEGRA234_SID_MGBE_VF1>;
...
}
Nvidia's arm-smmu driver reads the "iommus" property and stores the SID in
the MGBE device's "fwspec" struct. The dwmac-tegra driver can access the
SID using the tegra_dev_iommu_get_stream_id() helper function found in
linux/iommu.h.
Calling tegra_dev_iommu_get_stream_id() should not fail unless the "iommus"
property is removed from the device tree or the IOMMU is disabled.
While the Tegra234 SOC technically supports bypassing the IOMMU, it is not
supported by the current firmware, has not been tested and not recommended.
More detailed discussion with Thierry Reding from Nvidia linked below.
Fixes: d8ca113724
("net: stmmac: tegra: Add MGBE support")
Link: https://lore.kernel.org/netdev/cover.1731685185.git.pnewman@connecttech.com
Signed-off-by: Parker Newman <pnewman@connecttech.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Acked-by: Thierry Reding <treding@nvidia.com>
Link: https://patch.msgid.link/6fb97f32cf4accb4f7cf92846f6b60064ba0a3bd.1736284360.git.pnewman@connecttech.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
403 lines
11 KiB
C
403 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 = 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");
|