diff options
Diffstat (limited to 'drivers/pci/controller/pcie-rcar-host.c')
-rw-r--r-- | drivers/pci/controller/pcie-rcar-host.c | 94 |
1 files changed, 89 insertions, 5 deletions
diff --git a/drivers/pci/controller/pcie-rcar-host.c b/drivers/pci/controller/pcie-rcar-host.c index 765cf2b45e24..8f3131844e77 100644 --- a/drivers/pci/controller/pcie-rcar-host.c +++ b/drivers/pci/controller/pcie-rcar-host.c @@ -13,12 +13,14 @@ #include <linux/bitops.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/irqdomain.h> #include <linux/kernel.h> #include <linux/init.h> +#include <linux/iopoll.h> #include <linux/msi.h> #include <linux/of_address.h> #include <linux/of_irq.h> @@ -41,6 +43,21 @@ struct rcar_msi { int irq2; }; +#ifdef CONFIG_ARM +/* + * Here we keep a static copy of the remapped PCIe controller address. + * This is only used on aarch32 systems, all of which have one single + * PCIe controller, to provide quick access to the PCIe controller in + * the L1 link state fixup function, called from the ARM fault handler. + */ +static void __iomem *pcie_base; +/* + * Static copy of bus clock pointer, so we can check whether the clock + * is enabled or not. + */ +static struct clk *pcie_bus_clk; +#endif + /* Structure representing the PCIe interface */ struct rcar_pcie_host { struct rcar_pcie pcie; @@ -486,12 +503,10 @@ static irqreturn_t rcar_pcie_msi_irq(int irq, void *data) while (reg) { unsigned int index = find_first_bit(®, 32); - unsigned int msi_irq; + int ret; - msi_irq = irq_find_mapping(msi->domain->parent, index); - if (msi_irq) { - generic_handle_irq(msi_irq); - } else { + ret = generic_handle_domain_irq(msi->domain->parent, index); + if (ret) { /* Unknown MSI, just clear it */ dev_dbg(dev, "unexpected MSI\n"); rcar_pci_write_reg(pcie, BIT(index), PCIEMSIFR); @@ -776,6 +791,12 @@ static int rcar_pcie_get_resources(struct rcar_pcie_host *host) } host->msi.irq2 = i; +#ifdef CONFIG_ARM + /* Cache static copy for L1 link state fixup hook on aarch32 */ + pcie_base = pcie->base; + pcie_bus_clk = host->bus_clk; +#endif + return 0; err_irq2: @@ -1031,4 +1052,67 @@ static struct platform_driver rcar_pcie_driver = { }, .probe = rcar_pcie_probe, }; + +#ifdef CONFIG_ARM +static DEFINE_SPINLOCK(pmsr_lock); +static int rcar_pcie_aarch32_abort_handler(unsigned long addr, + unsigned int fsr, struct pt_regs *regs) +{ + unsigned long flags; + u32 pmsr, val; + int ret = 0; + + spin_lock_irqsave(&pmsr_lock, flags); + + if (!pcie_base || !__clk_is_enabled(pcie_bus_clk)) { + ret = 1; + goto unlock_exit; + } + + pmsr = readl(pcie_base + PMSR); + + /* + * Test if the PCIe controller received PM_ENTER_L1 DLLP and + * the PCIe controller is not in L1 link state. If true, apply + * fix, which will put the controller into L1 link state, from + * which it can return to L0s/L0 on its own. + */ + if ((pmsr & PMEL1RX) && ((pmsr & PMSTATE) != PMSTATE_L1)) { + writel(L1IATN, pcie_base + PMCTLR); + ret = readl_poll_timeout_atomic(pcie_base + PMSR, val, + val & L1FAEG, 10, 1000); + WARN(ret, "Timeout waiting for L1 link state, ret=%d\n", ret); + writel(L1FAEG | PMEL1RX, pcie_base + PMSR); + } + +unlock_exit: + spin_unlock_irqrestore(&pmsr_lock, flags); + return ret; +} + +static const struct of_device_id rcar_pcie_abort_handler_of_match[] __initconst = { + { .compatible = "renesas,pcie-r8a7779" }, + { .compatible = "renesas,pcie-r8a7790" }, + { .compatible = "renesas,pcie-r8a7791" }, + { .compatible = "renesas,pcie-rcar-gen2" }, + {}, +}; + +static int __init rcar_pcie_init(void) +{ + if (of_find_matching_node(NULL, rcar_pcie_abort_handler_of_match)) { +#ifdef CONFIG_ARM_LPAE + hook_fault_code(17, rcar_pcie_aarch32_abort_handler, SIGBUS, 0, + "asynchronous external abort"); +#else + hook_fault_code(22, rcar_pcie_aarch32_abort_handler, SIGBUS, 0, + "imprecise external abort"); +#endif + } + + return platform_driver_register(&rcar_pcie_driver); +} +device_initcall(rcar_pcie_init); +#else builtin_platform_driver(rcar_pcie_driver); +#endif |