mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
Add a new file encapsulating most of the PCI NHI specifics (intentionally leaving some odd cookies behind to make the layering simpler). Most notably, separate out nhi_probe() to make it easier to register other types of NHIs. Also, fold in Intel Icelake (nhi_ops.c) support to contain all PCIe-related bits in pci.c. Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
623 lines
17 KiB
C
623 lines
17 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Thunderbolt driver - PCI NHI driver
|
|
*
|
|
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
|
|
* Copyright (C) 2018, Intel Corporation
|
|
*/
|
|
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/property.h>
|
|
#include <linux/string_helpers.h>
|
|
#include <linux/suspend.h>
|
|
|
|
#include "nhi.h"
|
|
#include "nhi_regs.h"
|
|
#include "tb.h"
|
|
|
|
/**
|
|
* struct tb_nhi_pci - NHI device connected over PCIe
|
|
* @nhi: NHI device
|
|
* @msix_ida: Used to allocate MSI-X vectors for rings
|
|
*/
|
|
struct tb_nhi_pci {
|
|
struct tb_nhi nhi;
|
|
struct ida msix_ida;
|
|
};
|
|
|
|
static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi)
|
|
{
|
|
return container_of(nhi, struct tb_nhi_pci, nhi);
|
|
}
|
|
|
|
static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci)
|
|
{
|
|
struct tb_nhi *nhi = &nhi_pci->nhi;
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
|
|
if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
|
|
/*
|
|
* Intel hardware supports auto clear of the interrupt
|
|
* status register right after interrupt is being
|
|
* issued.
|
|
*/
|
|
nhi->quirks |= QUIRK_AUTO_CLEAR_INT;
|
|
|
|
switch (pdev->device) {
|
|
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
|
|
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
|
|
/*
|
|
* Falcon Ridge controller needs the end-to-end
|
|
* flow control workaround to avoid losing Rx
|
|
* packets when RING_FLAG_E2E is set.
|
|
*/
|
|
nhi->quirks |= QUIRK_E2E;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int nhi_pci_check_iommu_pdev(struct pci_dev *pdev, void *data)
|
|
{
|
|
if (!pdev->external_facing ||
|
|
!device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION))
|
|
return 0;
|
|
*(bool *)data = true;
|
|
return 1; /* Stop walking */
|
|
}
|
|
|
|
static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci)
|
|
{
|
|
struct tb_nhi *nhi = &nhi_pci->nhi;
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
struct pci_bus *bus = pdev->bus;
|
|
bool port_ok = false;
|
|
|
|
/*
|
|
* Ideally what we'd do here is grab every PCI device that
|
|
* represents a tunnelling adapter for this NHI and check their
|
|
* status directly, but unfortunately USB4 seems to make it
|
|
* obnoxiously difficult to reliably make any correlation.
|
|
*
|
|
* So for now we'll have to bodge it... Hoping that the system
|
|
* is at least sane enough that an adapter is in the same PCI
|
|
* segment as its NHI, if we can find *something* on that segment
|
|
* which meets the requirements for Kernel DMA Protection, we'll
|
|
* take that to imply that firmware is aware and has (hopefully)
|
|
* done the right thing in general. We need to know that the PCI
|
|
* layer has seen the ExternalFacingPort property which will then
|
|
* inform the IOMMU layer to enforce the complete "untrusted DMA"
|
|
* flow, but also that the IOMMU driver itself can be trusted not
|
|
* to have been subverted by a pre-boot DMA attack.
|
|
*/
|
|
while (bus->parent)
|
|
bus = bus->parent;
|
|
|
|
pci_walk_bus(bus, nhi_pci_check_iommu_pdev, &port_ok);
|
|
|
|
nhi->iommu_dma_protection = port_ok;
|
|
dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n",
|
|
str_enabled_disabled(port_ok));
|
|
}
|
|
|
|
static int nhi_pci_init_msi(struct tb_nhi *nhi)
|
|
{
|
|
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
struct device *dev = &pdev->dev;
|
|
int res, irq, nvec;
|
|
|
|
ida_init(&nhi_pci->msix_ida);
|
|
|
|
/*
|
|
* The NHI has 16 MSI-X vectors or a single MSI. We first try to
|
|
* get all MSI-X vectors and if we succeed, each ring will have
|
|
* one MSI-X. If for some reason that does not work out, we
|
|
* fallback to a single MSI.
|
|
*/
|
|
nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS,
|
|
PCI_IRQ_MSIX);
|
|
if (nvec < 0) {
|
|
nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
|
|
if (nvec < 0)
|
|
return nvec;
|
|
|
|
INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
|
|
|
|
irq = pci_irq_vector(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
res = devm_request_irq(&pdev->dev, irq, nhi_msi,
|
|
IRQF_NO_SUSPEND, "thunderbolt", nhi);
|
|
if (res)
|
|
return dev_err_probe(dev, res, "request_irq failed, aborting\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool nhi_pci_imr_valid(struct pci_dev *pdev)
|
|
{
|
|
u8 val;
|
|
|
|
if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val))
|
|
return !!val;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void nhi_pci_start_dma_port(struct tb_nhi *nhi)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
struct pci_dev *root_port;
|
|
|
|
/*
|
|
* During host router NVM upgrade we should not allow root port to
|
|
* go into D3cold because some root ports cannot trigger PME
|
|
* itself. To be on the safe side keep the root port in D0 during
|
|
* the whole upgrade process.
|
|
*/
|
|
root_port = pcie_find_root_port(pdev);
|
|
if (root_port)
|
|
pm_runtime_get_noresume(&root_port->dev);
|
|
}
|
|
|
|
static void nhi_pci_complete_dma_port(struct tb_nhi *nhi)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
struct pci_dev *root_port;
|
|
|
|
root_port = pcie_find_root_port(pdev);
|
|
if (root_port)
|
|
pm_runtime_put(&root_port->dev);
|
|
}
|
|
|
|
static int nhi_pci_ring_request_msix(struct tb_ring *ring, bool no_suspend)
|
|
{
|
|
struct tb_nhi *nhi = ring->nhi;
|
|
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
unsigned long irqflags;
|
|
int ret;
|
|
|
|
if (!pdev->msix_enabled)
|
|
return 0;
|
|
|
|
ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ring->vector = ret;
|
|
|
|
ret = pci_irq_vector(pdev, ring->vector);
|
|
if (ret < 0)
|
|
goto err_ida_remove;
|
|
|
|
ring->irq = ret;
|
|
|
|
irqflags = no_suspend ? IRQF_NO_SUSPEND : 0;
|
|
ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring);
|
|
if (ret)
|
|
goto err_ida_remove;
|
|
|
|
return 0;
|
|
|
|
err_ida_remove:
|
|
ida_free(&nhi_pci->msix_ida, ring->vector);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void nhi_pci_ring_release_msix(struct tb_ring *ring)
|
|
{
|
|
struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi);
|
|
|
|
if (ring->irq <= 0)
|
|
return;
|
|
|
|
free_irq(ring->irq, ring);
|
|
ida_free(&nhi_pci->msix_ida, ring->vector);
|
|
ring->vector = 0;
|
|
ring->irq = 0;
|
|
}
|
|
|
|
static void nhi_pci_shutdown(struct tb_nhi *nhi)
|
|
{
|
|
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
|
|
/*
|
|
* We have to release the irq before calling flush_work. Otherwise an
|
|
* already executing IRQ handler could call schedule_work again.
|
|
*/
|
|
if (!pdev->msix_enabled) {
|
|
devm_free_irq(nhi->dev, pdev->irq, nhi);
|
|
flush_work(&nhi->interrupt_work);
|
|
}
|
|
ida_destroy(&nhi_pci->msix_ida);
|
|
}
|
|
|
|
static bool nhi_pci_is_present(struct tb_nhi *nhi)
|
|
{
|
|
return pci_device_is_present(to_pci_dev(nhi->dev));
|
|
}
|
|
|
|
static const struct tb_nhi_ops pci_nhi_default_ops = {
|
|
.pre_nvm_auth = nhi_pci_start_dma_port,
|
|
.post_nvm_auth = nhi_pci_complete_dma_port,
|
|
.request_ring_irq = nhi_pci_ring_request_msix,
|
|
.release_ring_irq = nhi_pci_ring_release_msix,
|
|
.shutdown = nhi_pci_shutdown,
|
|
.is_present = nhi_pci_is_present,
|
|
.init_interrupts = nhi_pci_init_msi,
|
|
};
|
|
|
|
/* Ice Lake specific NHI operations */
|
|
|
|
#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */
|
|
|
|
static int check_for_device(struct device *dev, void *data)
|
|
{
|
|
return tb_is_switch(dev);
|
|
}
|
|
|
|
static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
|
|
{
|
|
struct tb *tb = dev_get_drvdata(nhi->dev);
|
|
int ret;
|
|
|
|
ret = device_for_each_child(&tb->root_switch->dev, NULL,
|
|
check_for_device);
|
|
return ret > 0;
|
|
}
|
|
|
|
static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
u32 vs_cap;
|
|
|
|
/*
|
|
* The Thunderbolt host controller is present always in Ice Lake
|
|
* but the firmware may not be loaded and running (depending
|
|
* whether there is device connected and so on). Each time the
|
|
* controller is used we need to "Force Power" it first and wait
|
|
* for the firmware to indicate it is up and running. This "Force
|
|
* Power" is really not about actually powering on/off the
|
|
* controller so it is accessible even if "Force Power" is off.
|
|
*
|
|
* The actual power management happens inside shared ACPI power
|
|
* resources using standard ACPI methods.
|
|
*/
|
|
pci_read_config_dword(pdev, VS_CAP_22, &vs_cap);
|
|
if (power) {
|
|
vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
|
|
vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
|
|
vs_cap |= VS_CAP_22_FORCE_POWER;
|
|
} else {
|
|
vs_cap &= ~VS_CAP_22_FORCE_POWER;
|
|
}
|
|
pci_write_config_dword(pdev, VS_CAP_22, vs_cap);
|
|
|
|
if (power) {
|
|
unsigned int retries = 350;
|
|
u32 val;
|
|
|
|
/* Wait until the firmware tells it is up and running */
|
|
do {
|
|
pci_read_config_dword(pdev, VS_CAP_9, &val);
|
|
if (val & VS_CAP_9_FW_READY)
|
|
return 0;
|
|
usleep_range(3000, 3100);
|
|
} while (--retries);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
u32 data;
|
|
|
|
data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
|
|
pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID);
|
|
}
|
|
|
|
static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
unsigned long end;
|
|
u32 data;
|
|
|
|
if (!timeout)
|
|
goto clear;
|
|
|
|
end = jiffies + msecs_to_jiffies(timeout);
|
|
do {
|
|
pci_read_config_dword(pdev, VS_CAP_18, &data);
|
|
if (data & VS_CAP_18_DONE)
|
|
goto clear;
|
|
usleep_range(1000, 1100);
|
|
} while (time_before(jiffies, end));
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
clear:
|
|
/* Clear the valid bit */
|
|
pci_write_config_dword(pdev, VS_CAP_19, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void icl_nhi_set_ltr(struct tb_nhi *nhi)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
|
u32 max_ltr, ltr;
|
|
|
|
pci_read_config_dword(pdev, VS_CAP_16, &max_ltr);
|
|
max_ltr &= 0xffff;
|
|
/* Program the same value for both snoop and no-snoop */
|
|
ltr = max_ltr << 16 | max_ltr;
|
|
pci_write_config_dword(pdev, VS_CAP_15, ltr);
|
|
}
|
|
|
|
static int icl_nhi_suspend(struct tb_nhi *nhi)
|
|
{
|
|
struct tb *tb = dev_get_drvdata(nhi->dev);
|
|
int ret;
|
|
|
|
if (icl_nhi_is_device_connected(nhi))
|
|
return 0;
|
|
|
|
if (tb_switch_is_icm(tb->root_switch)) {
|
|
/*
|
|
* If there is no device connected we need to perform
|
|
* both: a handshake through LC mailbox and force power
|
|
* down before entering D3.
|
|
*/
|
|
icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
|
|
ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return icl_nhi_force_power(nhi, false);
|
|
}
|
|
|
|
static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
|
|
{
|
|
struct tb *tb = dev_get_drvdata(nhi->dev);
|
|
enum icl_lc_mailbox_cmd cmd;
|
|
|
|
if (!pm_suspend_via_firmware())
|
|
return icl_nhi_suspend(nhi);
|
|
|
|
if (!tb_switch_is_icm(tb->root_switch))
|
|
return 0;
|
|
|
|
cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
|
|
icl_nhi_lc_mailbox_cmd(nhi, cmd);
|
|
return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
|
|
}
|
|
|
|
static int icl_nhi_resume(struct tb_nhi *nhi)
|
|
{
|
|
int ret;
|
|
|
|
ret = icl_nhi_force_power(nhi, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
icl_nhi_set_ltr(nhi);
|
|
return 0;
|
|
}
|
|
|
|
static void icl_nhi_shutdown(struct tb_nhi *nhi)
|
|
{
|
|
nhi_pci_shutdown(nhi);
|
|
|
|
icl_nhi_force_power(nhi, false);
|
|
}
|
|
|
|
static const struct tb_nhi_ops icl_nhi_ops = {
|
|
.init = icl_nhi_resume,
|
|
.suspend_noirq = icl_nhi_suspend_noirq,
|
|
.resume_noirq = icl_nhi_resume,
|
|
.runtime_suspend = icl_nhi_suspend,
|
|
.runtime_resume = icl_nhi_resume,
|
|
.shutdown = icl_nhi_shutdown,
|
|
.pre_nvm_auth = nhi_pci_start_dma_port,
|
|
.post_nvm_auth = nhi_pci_complete_dma_port,
|
|
.request_ring_irq = nhi_pci_ring_request_msix,
|
|
.release_ring_irq = nhi_pci_ring_release_msix,
|
|
.is_present = nhi_pci_is_present,
|
|
.init_interrupts = nhi_pci_init_msi,
|
|
};
|
|
|
|
static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct tb_nhi_pci *nhi_pci;
|
|
struct tb_nhi *nhi;
|
|
int res;
|
|
|
|
if (!nhi_pci_imr_valid(pdev))
|
|
return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n");
|
|
|
|
res = pcim_enable_device(pdev);
|
|
if (res)
|
|
return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n");
|
|
|
|
nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL);
|
|
if (!nhi_pci)
|
|
return -ENOMEM;
|
|
|
|
nhi = &nhi_pci->nhi;
|
|
nhi->dev = dev;
|
|
nhi->ops = (const struct tb_nhi_ops *)id->driver_data ?: &pci_nhi_default_ops;
|
|
|
|
nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt");
|
|
res = PTR_ERR_OR_ZERO(nhi->iobase);
|
|
if (res)
|
|
return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
|
|
|
|
nhi_pci_check_quirks(nhi_pci);
|
|
nhi_pci_check_iommu(nhi_pci);
|
|
|
|
pci_set_master(pdev);
|
|
|
|
return nhi_probe(&nhi_pci->nhi);
|
|
}
|
|
|
|
static void nhi_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct tb *tb = pci_get_drvdata(pdev);
|
|
struct tb_nhi *nhi = tb->nhi;
|
|
|
|
pm_runtime_get_sync(&pdev->dev);
|
|
pm_runtime_dont_use_autosuspend(&pdev->dev);
|
|
pm_runtime_forbid(&pdev->dev);
|
|
|
|
tb_domain_remove(tb);
|
|
wait_for_completion(&nhi->domain_released);
|
|
nhi_shutdown(nhi);
|
|
}
|
|
|
|
static struct pci_device_id nhi_ids[] = {
|
|
/*
|
|
* We have to specify class, the TB bridges use the same device and
|
|
* vendor (sub)id on gen 1 and gen 2 controllers.
|
|
*/
|
|
{
|
|
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_INTEL,
|
|
.device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE,
|
|
.subvendor = 0x2222, .subdevice = 0x1111,
|
|
},
|
|
{
|
|
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_INTEL,
|
|
.device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
|
|
.subvendor = 0x2222, .subdevice = 0x1111,
|
|
},
|
|
{
|
|
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_INTEL,
|
|
.device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI,
|
|
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_INTEL,
|
|
.device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI,
|
|
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
|
|
},
|
|
|
|
/* Thunderbolt 3 */
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
/* Thunderbolt 4 */
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0),
|
|
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) },
|
|
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) },
|
|
|
|
/* Any USB4 compliant host */
|
|
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) },
|
|
|
|
{ 0,}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, nhi_ids);
|
|
MODULE_DESCRIPTION("Thunderbolt/USB4 core driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static struct pci_driver nhi_driver = {
|
|
.name = "thunderbolt",
|
|
.id_table = nhi_ids,
|
|
.probe = nhi_pci_probe,
|
|
.remove = nhi_pci_remove,
|
|
.shutdown = nhi_pci_remove,
|
|
.driver.pm = &nhi_pm_ops,
|
|
};
|
|
|
|
static int __init nhi_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = tb_domain_init();
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = pci_register_driver(&nhi_driver);
|
|
if (ret)
|
|
tb_domain_exit();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit nhi_unload(void)
|
|
{
|
|
pci_unregister_driver(&nhi_driver);
|
|
tb_domain_exit();
|
|
}
|
|
|
|
rootfs_initcall(nhi_init);
|
|
module_exit(nhi_unload);
|