mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
d27e6ec04a
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user