[PATCH 06/11] PCI: rcar-gen4: Add Root Port reset support

From: Koichiro Den

Date: Thu Sep 17 2026 - 23:23:33 EST


Secondary Bus Reset is a hot reset signalled over the link, so it is of
no use for recovering from a link down, and it does not reset the
controller either. Implement the host bridge reset_root_port() callback
instead: it re-runs the hardware initialization probe uses, redoes the
DesignWare Root Port setup and restarts link training. The PCI core
routes every Secondary Bus Reset of the Root Port through it, including
AER recovery and the link-down handling added later.

Rather than tracking which APP interrupt enables survive the power
reset, derive them from software state through a single helper. A state
bit keeps the sources masked while the controller is being
reinitialized.

Serialize the reset with a mutex, as not all callers hold the Root
Port's device lock: pci_try_reset_function() on a downstream device only
locks that device before falling back to a parent bus reset.

Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
For reviewers:
- scoped_guard() is used deliberately in rcar_gen4_pcie_quiesce_irqs()
and rcar_gen4_pcie_resume_irqs(): the next patch adds code after the
locked section, and keeping that shape here makes the diff easier to
read.

drivers/pci/controller/dwc/pcie-rcar-gen4.c | 118 ++++++++++++++++++--
1 file changed, 111 insertions(+), 7 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
index 8a85f5f45cda..5b983ef6dc47 100644
--- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c
+++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
@@ -15,6 +15,7 @@
#include <linux/iopoll.h>
#include <linux/irqchip/arm-gic-v3.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
@@ -90,12 +91,22 @@ struct rcar_gen4_pcie_drvdata {
enum dw_pcie_device_mode mode;
};

+enum rcar_gen4_pcie_state {
+ /* The controller is being reset and reinitialized */
+ RCAR_PCIE_RESETTING,
+};
+
struct rcar_gen4_pcie {
struct dw_pcie dw;
void __iomem *base;
void __iomem *phy_base;
struct platform_device *pdev;
const struct rcar_gen4_pcie_drvdata *drvdata;
+ unsigned long state;
+ /* Protects APP interrupt enable registers and their software state. */
+ raw_spinlock_t app_lock;
+ /* Serializes Root Port hardware reinitialization. */
+ struct mutex reset_lock;
};
#define to_rcar_gen4_pcie(_dw) container_of(_dw, struct rcar_gen4_pcie, dw)

@@ -344,6 +355,33 @@ static int rcar_gen4_pcie_host_msi_addr(struct dw_pcie_rp *pp, u32 *msi_addr)
return 0;
}

+/* Whether the APP interrupt sources may currently be enabled. */
+static bool rcar_gen4_pcie_irqs_blocked(struct rcar_gen4_pcie *rcar)
+{
+ return !!rcar->state;
+}
+
+static void rcar_gen4_pcie_app_irq_sync_locked(struct rcar_gen4_pcie *rcar)
+{
+ bool armed = !rcar_gen4_pcie_irqs_blocked(rcar);
+ u32 val;
+
+ lockdep_assert_held(&rcar->app_lock);
+
+ val = readl(rcar->base + PCIEINTSTS0EN);
+ val &= ~MSI_CTRL_INT;
+ if (armed && pci_msi_enabled())
+ val |= MSI_CTRL_INT;
+ writel(val, rcar->base + PCIEINTSTS0EN);
+}
+
+static void rcar_gen4_pcie_app_irq_sync(struct rcar_gen4_pcie *rcar)
+{
+ guard(raw_spinlock_irqsave)(&rcar->app_lock);
+
+ rcar_gen4_pcie_app_irq_sync_locked(rcar);
+}
+
static int rcar_gen4_pcie_host_msi_init(struct dw_pcie_rp *pp)
{
struct dw_pcie *dw = to_dw_pcie_from_pp(pp);
@@ -374,12 +412,7 @@ static int rcar_gen4_pcie_host_msi_init(struct dw_pcie_rp *pp)
}

/* Configure MSI interrupt signal */
- val = readl(rcar->base + PCIEINTSTS0EN);
- if (pci_msi_enabled())
- val |= MSI_CTRL_INT;
- else
- val &= ~MSI_CTRL_INT;
- writel(val, rcar->base + PCIEINTSTS0EN);
+ rcar_gen4_pcie_app_irq_sync(rcar);

return 0;

@@ -394,6 +427,7 @@ static int rcar_gen4_pcie_host_msi_init(struct dw_pcie_rp *pp)
writel(0, rcar->base + AXIINTCCONT);

/* Deconfigure MSI interrupt signal */
+ guard(raw_spinlock_irqsave)(&rcar->app_lock);
val = readl(rcar->base + PCIEINTSTS0EN);
val &= ~MSI_CTRL_INT;
writel(val, rcar->base + PCIEINTSTS0EN);
@@ -484,14 +518,77 @@ static int rcar_gen4_pcie_host_hw_init(struct dw_pcie_rp *pp)
return 0;
}

+static void rcar_gen4_pcie_quiesce_irqs(struct rcar_gen4_pcie *rcar)
+{
+ scoped_guard(raw_spinlock_irqsave, &rcar->app_lock) {
+ set_bit(RCAR_PCIE_RESETTING, &rcar->state);
+ rcar_gen4_pcie_app_irq_sync_locked(rcar);
+ }
+}
+
+static void rcar_gen4_pcie_resume_irqs(struct rcar_gen4_pcie *rcar)
+{
+ scoped_guard(raw_spinlock_irqsave, &rcar->app_lock) {
+ clear_bit(RCAR_PCIE_RESETTING, &rcar->state);
+ rcar_gen4_pcie_app_irq_sync_locked(rcar);
+ }
+}
+
+/*
+ * R-Car Gen4 controllers have a single Root Port per instance, so the
+ * 'pci_dev' is ignored and the whole controller is reset.
+ */
+static int rcar_gen4_pcie_reset_root_port(struct pci_host_bridge *bridge,
+ struct pci_dev *pdev)
+{
+ struct rcar_gen4_pcie *rcar = dev_get_drvdata(bridge->dev.parent);
+ struct dw_pcie *dw = &rcar->dw;
+ struct dw_pcie_rp *pp = &dw->pp;
+ struct device *dev = dw->dev;
+ int ret;
+
+ guard(mutex)(&rcar->reset_lock);
+
+ rcar_gen4_pcie_quiesce_irqs(rcar);
+
+ dw_pcie_stop_link(dw);
+
+ ret = rcar_gen4_pcie_host_hw_init(pp);
+ if (ret) {
+ dev_err(dev, "Host init failed: %d\n", ret);
+ goto out;
+ }
+
+ ret = dw_pcie_setup_rc(pp);
+ if (ret) {
+ dev_err(dev, "Failed to setup RC: %d\n", ret);
+ goto out;
+ }
+
+ ret = dw_pcie_start_link(dw);
+ if (ret)
+ goto out;
+
+ /* Ignore errors, the link may come up later */
+ dw_pcie_wait_for_link(dw);
+ dev_dbg(dev, "Root Port reset completed\n");
+
+out:
+ rcar_gen4_pcie_resume_irqs(rcar);
+
+ return ret;
+}
+
static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp)
{
struct dw_pcie *dw = to_dw_pcie_from_pp(pp);
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
int ret;

- if (pp->bridge)
+ if (pp->bridge) {
pp->bridge->enable_device = rcar_gen4_pcie_enable_device;
+ pp->bridge->reset_root_port = rcar_gen4_pcie_reset_root_port;
+ }

ret = rcar_gen4_pcie_clk_enable(rcar);
if (ret)
@@ -525,10 +622,17 @@ static const struct dw_pcie_host_ops rcar_gen4_pcie_host_ops = {
static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
{
struct dw_pcie_rp *pp = &rcar->dw.pp;
+ int ret;

if (!IS_ENABLED(CONFIG_PCIE_RCAR_GEN4_HOST))
return -ENODEV;

+ ret = devm_mutex_init(rcar->dw.dev, &rcar->reset_lock);
+ if (ret)
+ return ret;
+
+ raw_spin_lock_init(&rcar->app_lock);
+
pp->num_vectors = MAX_MSI_IRQS;
pp->ops = &rcar_gen4_pcie_host_ops;

--
2.51.0