diff options
author | Mika Westerberg <mika.westerberg@linux.intel.com> | 2022-12-23 16:59:00 +0200 |
---|---|---|
committer | Mika Westerberg <mika.westerberg@linux.intel.com> | 2023-06-16 09:53:28 +0300 |
commit | 0fc70886569c8459c4494ba9ef8c4ef34b81e781 (patch) | |
tree | 2b8edf84560af532b539af34a37f2b8fe3de3cfe /drivers/thunderbolt/nhi.c | |
parent | 235d019481bcaa38a1d407f8513c54209aa387b8 (diff) | |
download | lwn-0fc70886569c8459c4494ba9ef8c4ef34b81e781.tar.gz lwn-0fc70886569c8459c4494ba9ef8c4ef34b81e781.zip |
thunderbolt: Reset USB4 v2 host router
USB4 v2 added a bit that can be used to reset the host router so we use
this to trigger reset when the driver probes. This will reset the
already connected topology as well but doing this simplifies things a
lot if for instance the link is already set to asymmetric. We also add
a module parameter to prevent this in case of problems.
While there rename the REG_HOP_COUNT to REG_CAPS to match the USB4 spec
naming better.
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Diffstat (limited to 'drivers/thunderbolt/nhi.c')
-rw-r--r-- | drivers/thunderbolt/nhi.c | 39 |
1 files changed, 38 insertions, 1 deletions
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index a979f47109e3..116016695a6a 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -46,6 +46,10 @@ #define QUIRK_AUTO_CLEAR_INT BIT(0) #define QUIRK_E2E BIT(1) +static bool host_reset = true; +module_param(host_reset, bool, 0444); +MODULE_PARM_DESC(host_reset, "reset USBv2 host router (default: true)"); + static int ring_interrupt_index(const struct tb_ring *ring) { int bit = ring->hop; @@ -1217,6 +1221,37 @@ static void nhi_check_iommu(struct tb_nhi *nhi) str_enabled_disabled(port_ok)); } +static void nhi_reset(struct tb_nhi *nhi) +{ + ktime_t timeout; + u32 val; + + val = ioread32(nhi->iobase + REG_CAPS); + /* Reset only v2 and later routers */ + if (FIELD_GET(REG_CAPS_VERSION_MASK, val) < REG_CAPS_VERSION_2) + return; + + if (!host_reset) { + dev_dbg(&nhi->pdev->dev, "skipping host router reset\n"); + return; + } + + iowrite32(REG_RESET_HRR, nhi->iobase + REG_RESET); + msleep(100); + + timeout = ktime_add_ms(ktime_get(), 500); + do { + val = ioread32(nhi->iobase + REG_RESET); + if (!(val & REG_RESET_HRR)) { + dev_warn(&nhi->pdev->dev, "host router reset successful\n"); + return; + } + usleep_range(10, 20); + } while (ktime_before(ktime_get(), timeout)); + + dev_warn(&nhi->pdev->dev, "timeout resetting host router\n"); +} + static int nhi_init_msi(struct tb_nhi *nhi) { struct pci_dev *pdev = nhi->pdev; @@ -1317,7 +1352,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) nhi->ops = (const struct tb_nhi_ops *)id->driver_data; /* cannot fail - table is allocated in pcim_iomap_regions */ nhi->iobase = pcim_iomap_table(pdev)[0]; - nhi->hop_count = ioread32(nhi->iobase + REG_HOP_COUNT) & 0x3ff; + nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff; dev_dbg(dev, "total paths: %d\n", nhi->hop_count); nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, @@ -1330,6 +1365,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) nhi_check_quirks(nhi); nhi_check_iommu(nhi); + nhi_reset(nhi); + res = nhi_init_msi(nhi); if (res) return dev_err_probe(dev, res, "cannot enable MSI, aborting\n"); |