diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2022-03-22 23:16:26 +0100 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2022-03-22 23:16:26 +0100 |
commit | 4b0f6ecaba3b90daa08b10d2936a82e901c3de16 (patch) | |
tree | 10a339ddd7ff8ae6abaa33876e580d7a34fcc5fb | |
parent | Merge branch 'remotes/lorenzo/pci/qcom' (diff) | |
parent | PCI: rcar: Use PCI_SET_ERROR_RESPONSE after read which triggered an exception (diff) | |
download | linux-4b0f6ecaba3b90daa08b10d2936a82e901c3de16.tar.xz linux-4b0f6ecaba3b90daa08b10d2936a82e901c3de16.zip |
Merge branch 'remotes/lorenzo/pci/rcar'
- Finish transition to L1 state in rcar_pcie_config_access() because R-Car
can't do it on its own (Marek Vasut)
- Return PCI_ERROR_RESPONSE for reads that trigger PCIe errors (Marek
Vasut)
* remotes/lorenzo/pci/rcar:
PCI: rcar: Use PCI_SET_ERROR_RESPONSE after read which triggered an exception
PCI: rcar: Finish transition to L1 state in rcar_pcie_config_access()
-rw-r--r-- | drivers/pci/controller/pcie-rcar-host.c | 130 |
1 files changed, 96 insertions, 34 deletions
diff --git a/drivers/pci/controller/pcie-rcar-host.c b/drivers/pci/controller/pcie-rcar-host.c index dfca59c4ae34..997c4df6a1e7 100644 --- a/drivers/pci/controller/pcie-rcar-host.c +++ b/drivers/pci/controller/pcie-rcar-host.c @@ -65,6 +65,42 @@ struct rcar_pcie_host { int (*phy_init_fn)(struct rcar_pcie_host *host); }; +static DEFINE_SPINLOCK(pmsr_lock); + +static int rcar_pcie_wakeup(struct device *pcie_dev, void __iomem *pcie_base) +{ + unsigned long flags; + u32 pmsr, val; + int ret = 0; + + spin_lock_irqsave(&pmsr_lock, flags); + + if (!pcie_base || pm_runtime_suspended(pcie_dev)) { + ret = -EINVAL; + 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 struct rcar_pcie_host *msi_to_host(struct rcar_msi *msi) { return container_of(msi, struct rcar_pcie_host, msi); @@ -78,6 +114,54 @@ static u32 rcar_read_conf(struct rcar_pcie *pcie, int where) return val >> shift; } +#ifdef CONFIG_ARM +#define __rcar_pci_rw_reg_workaround(instr) \ + " .arch armv7-a\n" \ + "1: " instr " %1, [%2]\n" \ + "2: isb\n" \ + "3: .pushsection .text.fixup,\"ax\"\n" \ + " .align 2\n" \ + "4: mov %0, #" __stringify(PCIBIOS_SET_FAILED) "\n" \ + " b 3b\n" \ + " .popsection\n" \ + " .pushsection __ex_table,\"a\"\n" \ + " .align 3\n" \ + " .long 1b, 4b\n" \ + " .long 2b, 4b\n" \ + " .popsection\n" +#endif + +static int rcar_pci_write_reg_workaround(struct rcar_pcie *pcie, u32 val, + unsigned int reg) +{ + int error = PCIBIOS_SUCCESSFUL; +#ifdef CONFIG_ARM + asm volatile( + __rcar_pci_rw_reg_workaround("str") + : "+r"(error):"r"(val), "r"(pcie->base + reg) : "memory"); +#else + rcar_pci_write_reg(pcie, val, reg); +#endif + return error; +} + +static int rcar_pci_read_reg_workaround(struct rcar_pcie *pcie, u32 *val, + unsigned int reg) +{ + int error = PCIBIOS_SUCCESSFUL; +#ifdef CONFIG_ARM + asm volatile( + __rcar_pci_rw_reg_workaround("ldr") + : "+r"(error), "=r"(*val) : "r"(pcie->base + reg) : "memory"); + + if (error != PCIBIOS_SUCCESSFUL) + PCI_SET_ERROR_RESPONSE(val); +#else + *val = rcar_pci_read_reg(pcie, reg); +#endif + return error; +} + /* Serialization is provided by 'pci_lock' in drivers/pci/access.c */ static int rcar_pcie_config_access(struct rcar_pcie_host *host, unsigned char access_type, struct pci_bus *bus, @@ -85,6 +169,14 @@ static int rcar_pcie_config_access(struct rcar_pcie_host *host, { struct rcar_pcie *pcie = &host->pcie; unsigned int dev, func, reg, index; + int ret; + + /* Wake the bus up in case it is in L1 state. */ + ret = rcar_pcie_wakeup(pcie->dev, pcie->base); + if (ret) { + PCI_SET_ERROR_RESPONSE(data); + return PCIBIOS_SET_FAILED; + } dev = PCI_SLOT(devfn); func = PCI_FUNC(devfn); @@ -141,14 +233,14 @@ static int rcar_pcie_config_access(struct rcar_pcie_host *host, return PCIBIOS_DEVICE_NOT_FOUND; if (access_type == RCAR_PCI_ACCESS_READ) - *data = rcar_pci_read_reg(pcie, PCIECDR); + ret = rcar_pci_read_reg_workaround(pcie, data, PCIECDR); else - rcar_pci_write_reg(pcie, *data, PCIECDR); + ret = rcar_pci_write_reg_workaround(pcie, *data, PCIECDR); /* Disable the configuration access */ rcar_pci_write_reg(pcie, 0, PCIECCTLR); - return PCIBIOS_SUCCESSFUL; + return ret; } static int rcar_pcie_read_conf(struct pci_bus *bus, unsigned int devfn, @@ -1050,40 +1142,10 @@ static struct platform_driver rcar_pcie_driver = { }; #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 || pm_runtime_suspended(pcie_dev)) { - 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; + return !fixup_exception(regs); } static const struct of_device_id rcar_pcie_abort_handler_of_match[] __initconst = { |