Skip to content

Commit

Permalink
thunderbolt: Reset USB4 v2 host router
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
westeri committed Jun 16, 2023
1 parent 235d019 commit 0fc7088
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 8 deletions.
39 changes: 38 additions & 1 deletion drivers/thunderbolt/nhi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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");
Expand Down
19 changes: 12 additions & 7 deletions drivers/thunderbolt/nhi_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct ring_desc {
/* NHI registers in bar 0 */

/*
* 16 bytes per entry, one entry for every hop (REG_HOP_COUNT)
* 16 bytes per entry, one entry for every hop (REG_CAPS)
* 00: physical pointer to an array of struct ring_desc
* 08: ring tail (set by NHI)
* 10: ring head (index of first non posted descriptor)
Expand All @@ -46,7 +46,7 @@ struct ring_desc {
#define REG_TX_RING_BASE 0x00000

/*
* 16 bytes per entry, one entry for every hop (REG_HOP_COUNT)
* 16 bytes per entry, one entry for every hop (REG_CAPS)
* 00: physical pointer to an array of struct ring_desc
* 08: ring head (index of first not posted descriptor)
* 10: ring tail (set by NHI)
Expand All @@ -56,15 +56,15 @@ struct ring_desc {
#define REG_RX_RING_BASE 0x08000

/*
* 32 bytes per entry, one entry for every hop (REG_HOP_COUNT)
* 32 bytes per entry, one entry for every hop (REG_CAPS)
* 00: enum_ring_flags
* 04: isoch time stamp ?? (write 0)
* ..: unknown
*/
#define REG_TX_OPTIONS_BASE 0x19800

/*
* 32 bytes per entry, one entry for every hop (REG_HOP_COUNT)
* 32 bytes per entry, one entry for every hop (REG_CAPS)
* 00: enum ring_flags
* If RING_FLAG_E2E_FLOW_CONTROL is set then bits 13-23 must be set to
* the corresponding TX hop id.
Expand All @@ -77,7 +77,7 @@ struct ring_desc {

/*
* three bitfields: tx, rx, rx overflow
* Every bitfield contains one bit for every hop (REG_HOP_COUNT).
* Every bitfield contains one bit for every hop (REG_CAPS).
* New interrupts are fired only after ALL registers have been
* read (even those containing only disabled rings).
*/
Expand All @@ -87,7 +87,7 @@ struct ring_desc {

/*
* two bitfields: rx, tx
* Both bitfields contains one bit for every hop (REG_HOP_COUNT). To
* Both bitfields contains one bit for every hop (REG_CAPS). To
* enable/disable interrupts set/clear the corresponding bits.
*/
#define REG_RING_INTERRUPT_BASE 0x38200
Expand All @@ -104,12 +104,17 @@ struct ring_desc {
#define REG_INT_VEC_ALLOC_REGS (32 / REG_INT_VEC_ALLOC_BITS)

/* The last 11 bits contain the number of hops supported by the NHI port. */
#define REG_HOP_COUNT 0x39640
#define REG_CAPS 0x39640
#define REG_CAPS_VERSION_MASK GENMASK(23, 16)
#define REG_CAPS_VERSION_2 0x40

#define REG_DMA_MISC 0x39864
#define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2)
#define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17)

#define REG_RESET 0x39898
#define REG_RESET_HRR BIT(0)

#define REG_INMAIL_DATA 0x39900

#define REG_INMAIL_CMD 0x39904
Expand Down

0 comments on commit 0fc7088

Please sign in to comment.