Merge branch 'pci/controller/dwc-qcom'

- Only collect reset GPIOs from PCI bridge DT nodes to avoid stealing
  resets used by downstream devices like USB controllers (Christopher
  Obbard)

- Block accesses to downstream devices on link down to reduce a storm of
  Completion Timeout AER interrupts (Qiang Yu)

- Skip system suspend/resume for firmware-managed PCIe, where firmware
  manages clocks, regulators, resets, etc (Mrinmay Sarkar)

- Honor IOMMU provider's #iommu-cells in qcom_pcie_config_sid_1_9_0()
  (Manivannan Sadhasivam)

- Deinitialize endpoint on remove (Guangshuo Li)

- Treat Root Port error recovery as successful even if link doesn't come up
  after reset because the device was hot-unplugged (Konrad Dybcio)

- Skip unnecessary iMSI-RX initialization if DT 'msi-map' is present,
  indicating an external MSI controller (Ayiluri Naga Rashmi)

* pci/controller/dwc-qcom:
  PCI: qcom: Skip DWC MSI host initialization when DT 'msi-map' is present
  PCI: qcom: Ignore -ENODEV after Root Port reset
  PCI: qcom-ep: Deinitialize endpoint on remove
  PCI: qcom: Honor IOMMU provider's #iommu-cells in qcom_pcie_config_sid_1_9_0()
  PCI: qcom: Skip system suspend/resume for firmware-managed PCIe
  PCI: qcom: Block accesses to downstream devices on link down
  PCI: qcom: Only check bridge nodes for PERST# GPIOs
This commit is contained in:
Bjorn Helgaas 2026-10-21 13:20:34 -05:00
commit d27e6ec04a
2 changed files with 155 additions and 31 deletions

View File

@ -945,6 +945,8 @@ static void qcom_pcie_ep_remove(struct platform_device *pdev)
if (pcie_ep->link_status == QCOM_PCIE_EP_LINK_DISABLED)
return;
pci_epc_deinit_notify(pcie_ep->pci.ep.epc);
dw_pcie_ep_deinit(&pcie_ep->pci.ep);
qcom_pcie_disable_resources(pcie_ep);
}

View File

@ -20,6 +20,7 @@
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_pci.h>
#include <linux/pci.h>
@ -73,6 +74,23 @@
#define PARF_BDF_TO_SID_TABLE_N 0x2000
#define PARF_BDF_TO_SID_CFG 0x2c00
/*
* Outbound blocker range registers. The blocked range has a write pair
* (WR_BASE/WR_LIMIT) and a read pair (RD_BASE/RD_LIMIT); each address is
* split into a low (32-bit) and a HI (upper 32-bit) register.
*/
#define PARF_BLOCK_SLV_AXI_WR_BASE 0x360
#define PARF_BLOCK_SLV_AXI_WR_BASE_HI 0x364
#define PARF_BLOCK_SLV_AXI_WR_LIMIT 0x368
#define PARF_BLOCK_SLV_AXI_WR_LIMIT_HI 0x36c
#define PARF_BLOCK_SLV_AXI_RD_BASE 0x370
#define PARF_BLOCK_SLV_AXI_RD_BASE_HI 0x374
#define PARF_BLOCK_SLV_AXI_RD_LIMIT 0x378
#define PARF_BLOCK_SLV_AXI_RD_LIMIT_HI 0x37c
#define PARF_ECAM_BASE 0x380
#define PARF_ECAM_BASE_HI 0x384
/* ELBI registers */
#define ELBI_SYS_CTRL 0x04
#define ELBI_SYS_STTS 0x08
@ -90,6 +108,7 @@
/* PARF_SYS_CTRL register fields */
#define MAC_PHY_POWERDOWN_IN_P2_D_MUX_EN BIT(29)
#define OUTBOUND_BLOCKER_EN BIT(26)
#define MST_WAKEUP_EN BIT(13)
#define SLV_WAKEUP_EN BIT(12)
#define MSTR_ACLK_CGC_DIS BIT(10)
@ -308,6 +327,7 @@ struct qcom_pcie {
struct gpio_desc *reset;
int global_irq;
bool use_pm_opp;
struct mutex reset_lock;
};
#define to_qcom_pcie(x) dev_get_drvdata((x)->dev)
@ -445,6 +465,25 @@ static void qcom_pcie_configure_dbi_atu_base(struct qcom_pcie *pcie)
}
}
static void qcom_pcie_init_outbound_blocker(struct qcom_pcie *pcie)
{
struct dw_pcie *pci = pcie->pci;
/* ECAM base must match the DBI base address */
writel(lower_32_bits(pci->dbi_phys_addr), pcie->parf + PARF_ECAM_BASE);
writel(upper_32_bits(pci->dbi_phys_addr), pcie->parf + PARF_ECAM_BASE_HI);
writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_WR_BASE);
writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_WR_BASE_HI);
writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_WR_LIMIT);
writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_WR_LIMIT_HI);
writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_RD_BASE);
writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_RD_BASE_HI);
writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_RD_LIMIT);
writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_RD_LIMIT_HI);
}
static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie)
{
struct dw_pcie *pci = pcie->pci;
@ -990,6 +1029,8 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie)
dw_pcie_dbi_ro_wr_dis(pci);
qcom_pcie_init_outbound_blocker(pcie);
return 0;
}
@ -1104,6 +1145,8 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie)
qcom_pcie_set_slot_cap(pcie->pci);
qcom_pcie_init_outbound_blocker(pcie);
return 0;
}
@ -1143,50 +1186,90 @@ static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie)
static int qcom_pcie_config_sid_1_9_0(struct qcom_pcie *pcie)
{
/* iommu map structure */
struct {
u32 bdf;
u32 phandle;
u32 smmu_sid;
u32 smmu_sid_len;
} *map;
void __iomem *bdf_to_sid_base = pcie->parf + PARF_BDF_TO_SID_TABLE_N;
struct device *dev = pcie->pci->dev;
struct device_node *iommu_np;
u8 qcom_pcie_crc8_table[CRC8_TABLE_SIZE];
int i, nr_map, size = 0;
u32 smmu_sid_base;
const __be32 *map;
u32 iommu_cells, entry_cells, phandle, smmu_sid_base;
int i, nr_cells, nr_map, size = 0;
u32 val;
of_get_property(dev->of_node, "iommu-map", &size);
if (!size)
map = of_get_property(dev->of_node, "iommu-map", &size);
if (!map || !size)
return 0;
if (size % sizeof(*map)) {
dev_err(dev, "Malformed iommu-map property\n");
return -EINVAL;
}
nr_cells = size / sizeof(*map);
/*
* Each iommu-map entry is: rid-base (1 cell), phandle (1 cell),
* IOMMU specifier (#iommu-cells cells), length (1 cell). Read
* #iommu-cells from the IOMMU provider referenced by the first
* entry to compute the per-entry stride.
*/
phandle = be32_to_cpu(map[1]);
iommu_np = of_find_node_by_phandle(phandle);
if (!iommu_np) {
dev_err(dev, "Failed to find IOMMU node in iommu-map\n");
return -ENODEV;
}
if (of_property_read_u32(iommu_np, "#iommu-cells", &iommu_cells))
iommu_cells = 1;
of_node_put(iommu_np);
entry_cells = 3 + iommu_cells;
/*
* Retain backward compatibility with DTs that describe iommu-map
* with 4-cell entries against an IOMMU declaring #iommu-cells = 2,
* matching the fallback in drivers/of/base.c::of_check_bad_map().
*/
if (iommu_cells == 2 && !(nr_cells % 4)) {
bool legacy = true;
for (i = 0; i < nr_cells; i += 4) {
if (be32_to_cpu(map[i + 1]) != phandle ||
be32_to_cpu(map[i + 3]) != 1) {
legacy = false;
break;
}
}
if (legacy) {
dev_warn_once(dev, "iommu-map has 1-cell entries with #iommu-cells=2, using 1-cell\n");
entry_cells = 4;
}
}
if (nr_cells % entry_cells) {
dev_err(dev, "Malformed iommu-map property\n");
return -EINVAL;
}
nr_map = nr_cells / entry_cells;
/* Enable BDF to SID translation by disabling bypass mode (default) */
val = readl(pcie->parf + PARF_BDF_TO_SID_CFG);
val &= ~BDF_TO_SID_BYPASS;
writel(val, pcie->parf + PARF_BDF_TO_SID_CFG);
map = kzalloc(size, GFP_KERNEL);
if (!map)
return -ENOMEM;
of_property_read_u32_array(dev->of_node, "iommu-map", (u32 *)map,
size / sizeof(u32));
nr_map = size / (sizeof(*map));
crc8_populate_msb(qcom_pcie_crc8_table, QCOM_PCIE_CRC8_POLYNOMIAL);
/* Registers need to be zero out first */
memset_io(bdf_to_sid_base, 0, CRC8_TABLE_SIZE * sizeof(u32));
/* Extract the SMMU SID base from the first entry of iommu-map */
smmu_sid_base = map[0].smmu_sid;
smmu_sid_base = be32_to_cpu(map[2]);
/* Look for an available entry to hold the mapping */
for (i = 0; i < nr_map; i++) {
__be16 bdf_be = cpu_to_be16(map[i].bdf);
u32 val;
u32 bdf = be32_to_cpu(map[i * entry_cells]);
u32 sid = be32_to_cpu(map[i * entry_cells + 2]);
__be16 bdf_be = cpu_to_be16(bdf);
u8 hash;
hash = crc8(qcom_pcie_crc8_table, (u8 *)&bdf_be, sizeof(bdf_be), 0);
@ -1208,12 +1291,10 @@ static int qcom_pcie_config_sid_1_9_0(struct qcom_pcie *pcie)
}
/* BDF [31:16] | SID [15:8] | NEXT [7:0] */
val = map[i].bdf << 16 | (map[i].smmu_sid - smmu_sid_base) << 8 | 0;
val = bdf << 16 | (sid - smmu_sid_base) << 8 | 0;
writel(val, bdf_to_sid_base + hash * sizeof(u32));
}
kfree(map);
return 0;
}
@ -1322,6 +1403,8 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie)
for (i = 0; i < 256; i++)
writel(0, pcie->parf + PARF_BDF_TO_SID_TABLE_N + (4 * i));
qcom_pcie_init_outbound_blocker(pcie);
return 0;
}
@ -1382,6 +1465,18 @@ static void qcom_pcie_configure_ports(struct qcom_pcie *pcie)
dw_pcie_program_t_power_on(pcie->pci, port->l1ss_t_power_on);
}
static void qcom_pcie_enable_outbound_blocker(struct qcom_pcie *pcie)
{
u32 sys_ctrl;
sys_ctrl = readl(pcie->parf + PARF_SYS_CTRL);
sys_ctrl |= OUTBOUND_BLOCKER_EN;
writel(sys_ctrl, pcie->parf + PARF_SYS_CTRL);
/* Flush the write so the blocker is enabled before this function returns */
readl(pcie->parf + PARF_SYS_CTRL);
}
static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
{
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
@ -1775,6 +1870,8 @@ static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge,
u32 val;
int ret;
guard(mutex)(&pcie->reset_lock);
/* Wait for the pending transactions to be completed */
ret = readl_relaxed_poll_timeout(pcie->parf + PARF_STATUS, val,
val & FLUSH_COMPLETED, 10,
@ -1821,6 +1918,9 @@ static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge,
qcom_pcie_start_link(pci);
ret = dw_pcie_wait_for_link(pci);
/* This linkdown might have been a hot-unplug */
if (ret == -ENODEV)
ret = 0;
if (ret)
return ret;
@ -1878,6 +1978,11 @@ static irqreturn_t qcom_pcie_global_irq_thread(int irq, void *data)
if (test_and_clear_bit(INT_ALL_LINK_DOWN, &status)) {
dev_dbg(dev, "Received Link down event\n");
mutex_lock(&pcie->reset_lock);
qcom_pcie_enable_outbound_blocker(pcie);
mutex_unlock(&pcie->reset_lock);
for_each_pci_bridge(port, pp->bridge->bus) {
if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT)
pci_host_handle_link_down(port);
@ -1917,11 +2022,13 @@ static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg)
*/
raw_spin_lock_init(&pp->lock);
ret = dw_pcie_msi_host_init(pp);
if (ret)
return ret;
pp->use_imsi_rx = !pci_host_of_has_msi_map(dev);
if (pp->use_imsi_rx) {
ret = dw_pcie_msi_host_init(pp);
if (ret)
return ret;
}
pp->use_imsi_rx = true;
dw_pcie_msi_init(pp);
return devm_add_action_or_reset(dev, qcom_pci_free_msi, pp);
@ -1953,7 +2060,7 @@ static bool qcom_pcie_is_child_node(struct device *dev,
return false;
}
/* Parse PERST# from all nodes in depth first manner starting from @np */
/* Collect PERST# GPIOs from PCI bridge nodes depth-first, starting at @np */
static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
struct qcom_pcie_port *port,
struct device_node *np)
@ -2019,6 +2126,9 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
parse_child_node:
for_each_available_child_of_node_scoped(np, child) {
if (!of_node_is_type(child, "pci"))
continue;
ret = qcom_pcie_parse_perst(pcie, port, child);
if (ret)
return ret;
@ -2204,6 +2314,10 @@ static int qcom_pcie_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&pcie->ports);
ret = devm_mutex_init(dev, &pcie->reset_lock);
if (ret)
goto err_pm_runtime_put;
pci->dev = dev;
pci->ops = &dw_pcie_ops;
pp = &pci->pp;
@ -2341,6 +2455,10 @@ static int qcom_pcie_suspend_noirq(struct device *dev)
{
struct qcom_pcie *pcie;
int ret = 0;
const struct qcom_pcie_cfg *pcie_cfg = of_device_get_match_data(dev);
if (pcie_cfg && pcie_cfg->firmware_managed)
return 0;
pcie = dev_get_drvdata(dev);
if (!pcie)
@ -2399,6 +2517,10 @@ static int qcom_pcie_resume_noirq(struct device *dev)
{
struct qcom_pcie *pcie;
int ret;
const struct qcom_pcie_cfg *pcie_cfg = of_device_get_match_data(dev);
if (pcie_cfg && pcie_cfg->firmware_managed)
return 0;
pcie = dev_get_drvdata(dev);
if (!pcie)