Skip to content
Permalink
Browse files
PCI: rcar: Finish transition to L1 state in rcar_pcie_config_access()
In case the controller is transitioning to L1 in rcar_pcie_config_access(),
any read/write access to PCIECDR triggers asynchronous external abort. This
is because the transition to L1 link state must be manually finished by the
driver. The PCIe IP can transition back from L1 state to L0 on its own.

Avoid triggering the abort in rcar_pcie_config_access() by checking whether
the controller is in the transition state, and if so, finish the transition
right away. This prevents a lot of unnecessary exceptions, although not all
of them.

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Krzysztof Wilczyński <kw@linux.com>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Wolfram Sang <wsa@the-dreams.de>
Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Cc: linux-renesas-soc@vger.kernel.org
  • Loading branch information
Marek Vasut authored and intel-lab-lkp committed Jan 16, 2022
1 parent 87c7193 commit dff324f279a78c9ca7f04c3fcf603b40d5a893fa
Showing 1 changed file with 40 additions and 21 deletions.
@@ -54,6 +54,34 @@ static void __iomem *pcie_base;
* device is runtime suspended or not.
*/
static struct device *pcie_dev;

static DEFINE_SPINLOCK(pmsr_lock);
static int rcar_pcie_wakeup(struct device *pcie_dev, void __iomem *pcie_base)
{
u32 pmsr, val;
int ret = 0;

if (!pcie_base || pm_runtime_suspended(pcie_dev))
return 1;

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);
}

return ret;
}
#endif

/* Structure representing the PCIe interface */
@@ -85,6 +113,15 @@ static int rcar_pcie_config_access(struct rcar_pcie_host *host,
{
struct rcar_pcie *pcie = &host->pcie;
unsigned int dev, func, reg, index;
unsigned long flags;
int ret;

/* Wake the bus up in case it is in L1 state. */
spin_lock_irqsave(&pmsr_lock, flags);
ret = rcar_pcie_wakeup(pcie->dev, pcie->base);
spin_unlock_irqrestore(&pmsr_lock, flags);
if (ret)
return ret;

dev = PCI_SLOT(devfn);
func = PCI_FUNC(devfn);
@@ -1050,36 +1087,18 @@ 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;
ret = rcar_pcie_wakeup(pcie_dev, pcie_base);
spin_unlock_irqrestore(&pmsr_lock, flags);
if (ret)
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);

0 comments on commit dff324f

Please sign in to comment.