From 3567bcf0e985a5b50c88d7fb18934c3ae7440cc0 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Mon, 25 May 2026 14:54:42 +0800 Subject: [PATCH 1/6] PCI: host-generic: Move legacy DT binding fallback decision to caller of pci_host_common_parse_ports() pci_host_common_parse_ports() returns -ENODEV if the bridge nodes (RP) are not present or PERST# is only found in the Root Complex node. Then the callers (currently just pci-imx6) assume that they need to fall back to parsing the legacy DT binding. But this behavior won't scale across Root Complex designs because PERST# is not the only optional property that the callers would need to consider for falling back to legacy binding. There could be many properties and the API cannot incorporate all of them. So to keep the API implementation simple, just return 0 when bridge nodes were not found. Then it is up to the caller to use its own logic to decide whether to fall back to legacy binding or not. Since there is only one caller now, update the caller to skip -ENODEV check and check for the PERST# GPIO in any of the bridge nodes and fall back to legacy binding if not found. Signed-off-by: Sherry Sun [mani: squashed imx6 patch to avoid bisectability issue, commit message] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Richard Zhu Link: https://patch.msgid.link/20260525065443.2338629-2-sherry.sun@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 25 +++++++++++++------- drivers/pci/controller/pci-host-common.c | 29 ++++-------------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 98e1db751132..f55a68f60eea 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1318,6 +1318,18 @@ static void imx_pcie_assert_perst(struct imx_pcie *imx_pcie, bool assert) } } +static bool imx_pcie_perst_found(struct pci_host_bridge *bridge) +{ + struct pci_host_port *port; + + list_for_each_entry(port, &bridge->ports, list) { + if (!list_empty(&port->perst)) + return true; + } + + return false; +} + static int imx_pcie_host_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); @@ -1330,15 +1342,12 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) /* Parse Root Port nodes if present */ ret = pci_host_common_parse_ports(dev, bridge); if (ret) { - if (ret != -ENODEV) { - dev_err(dev, "Failed to parse Root Port nodes: %d\n", ret); - return ret; - } + dev_err(dev, "Failed to parse Root Port nodes: %d\n", ret); + return ret; + } - /* - * Fall back to legacy binding for DT backwards - * compatibility - */ + /* Fall back to legacy binding for DT backwards compatibility */ + if (!imx_pcie_perst_found(bridge)) { ret = imx_pcie_parse_legacy_binding(imx_pcie); if (ret) return ret; diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index 2ce6f4b66133..da6636505c74 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -108,8 +108,7 @@ static int pci_host_common_parse_perst(struct device *dev, * dependencies and the driver may fail to operate if required resources * are missing. * - * Return: 0 on success, -ENODEV if PERST# found in RC node (legacy binding - * should be used), Other negative error codes on failure. + * Return: 0 on success, negative error codes on failure. */ static int pci_host_common_parse_port(struct device *dev, struct pci_host_bridge *bridge, @@ -128,22 +127,6 @@ static int pci_host_common_parse_port(struct device *dev, if (ret) return ret; - /* - * 1. PERST# found in RP or its child nodes - list is not empty, - * continue - * - * 2. PERST# not found in RP/children, but found in RC node - - * return -ENODEV to fallback legacy binding - * - * 3. PERST# not found anywhere - list is empty, continue (optional - * PERST#) - */ - if (list_empty(&port->perst)) { - if (of_property_present(dev->of_node, "reset-gpios") || - of_property_present(dev->of_node, "reset-gpio")) - return -ENODEV; - } - INIT_LIST_HEAD(&port->list); list_add_tail(&port->list, &bridge->ports); @@ -158,13 +141,11 @@ static int pci_host_common_parse_port(struct device *dev, * Iterate through child nodes of the host bridge and parse Root Port * properties (currently only reset GPIOs). * - * Return: 0 on success, -ENODEV if no ports found or PERST# found in RC - * node (legacy binding should be used), Other negative error codes on - * failure. + * Return: 0 on success or ports not found, negative error codes on failure. */ int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *bridge) { - int ret = -ENODEV; + int ret = 0; for_each_available_child_of_node_scoped(dev->of_node, of_port) { if (!of_node_is_type(of_port, "pci")) @@ -174,8 +155,8 @@ int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *brid goto err_cleanup; } - if (ret) - return ret; + if (list_empty(&bridge->ports)) + return 0; return devm_add_action_or_reset(dev, pci_host_common_delete_ports, &bridge->ports); From d4e0984f12ea958e0f2def90ddb1e193a896932b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 16 Jun 2026 18:39:59 +0200 Subject: [PATCH 2/6] PCI: imx6: Fix building against PCI_HOST_COMMON When CONFIG_PCI_HOST_COMMON is set to =m, the i.MX6 PCIe driver fails to link. This can happen when only i.MX endpoint mode is enabled but not host mode, which would indirectly enable the host-common driver itself. ld.lld: error: undefined symbol: pci_host_common_parse_ports >>> referenced by pci-imx6.c >>> drivers/pci/controller/dwc/pci-imx6.o:(imx_pcie_host_init) in archive vmlinux.a ld.lld: error: undefined symbol: pci_host_common_delete_ports >>> referenced by pci-imx6.c >>> drivers/pci/controller/dwc/pci-imx6.o:(imx_pcie_host_init) in archive vmlinux.a >>> referenced by pci-imx6.c >>> drivers/pci/controller/dwc/pci-imx6.o:(imx_pcie_host_init) in archive vmlinux.a Select the common module from the endpoint support directly. Fixes: 250eea5c06f5 ("PCI: imx6: Parse 'reset-gpios' in Root Port nodes") Signed-off-by: Arnd Bergmann Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260616164049.3656435-1-arnd@kernel.org --- drivers/pci/controller/dwc/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index aa0b784c85b4..7d49027c6736 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -126,6 +126,7 @@ config PCI_IMX6_EP depends on ARCH_MXC || COMPILE_TEST depends on PCI_ENDPOINT select PCIE_DW_EP + select PCI_HOST_COMMON select PCI_IMX6 help Enables support for the PCIe controller in the i.MX SoCs to From 7f4d9901eb1fdd3d2e56b514dcc325b33185b8e1 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 18 Jun 2026 16:36:08 +0200 Subject: [PATCH 3/6] PCI: imx6: Fix building against PCI_PWRCTRL_GENERIC When endpoint mode is built-in, but pwrctrl support is in a loadable module, the imx driver fails to build because the unused host support still tries to link against pwrctrl: ld.lld: error: undefined symbol: pci_pwrctrl_power_off_devices >>> referenced by pci-imx6.c:1988 (drivers/pci/controller/dwc/pci-imx6.c:1988) >>> drivers/pci/controller/dwc/pci-imx6.o:(imx_pcie_shutdown) in archive vmlinux.a Add one more select for this. Fixes: 85c1fcfa740d ("PCI: imx6: Integrate new pwrctrl API") Signed-off-by: Arnd Bergmann Signed-off-by: Manivannan Sadhasivam Reviewed-by: Sherry Sun Link: https://patch.msgid.link/20260618143629.2035247-1-arnd@kernel.org --- drivers/pci/controller/dwc/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index 7d49027c6736..49a7a2c50ca1 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -128,6 +128,7 @@ config PCI_IMX6_EP select PCIE_DW_EP select PCI_HOST_COMMON select PCI_IMX6 + select PCI_PWRCTRL_GENERIC help Enables support for the PCIe controller in the i.MX SoCs to work in endpoint mode. The PCI controller on i.MX is based From 2c5768344f88b86188c5915327bb00e6aad24990 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Mon, 13 Jul 2026 10:34:32 +0800 Subject: [PATCH 4/6] PCI: imx6: Move pci_pwrctrl_create_devices() to imx_pcie_probe() Previously, pci_pwrctrl_create_devices() was placed in imx_pcie_host_init(), which is the .init callback of dw_pcie_host_ops. This callback is invoked not only during probe, but also during resume. This caused pci_pwrctrl_create_devices() to be called multiple times across suspend/resume cycles, which is unnecessary since the pwrctrl devices only need to be created once. Move pci_pwrctrl_create_devices() to imx_pcie_probe() so that it is only called once during probe, similar to other regulator_get calls. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260713023435.235765-2-sherry.sun@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index f55a68f60eea..92f8e4a299e8 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1382,16 +1382,10 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } - ret = pci_pwrctrl_create_devices(dev); - if (ret) { - dev_err(dev, "failed to create pwrctrl devices\n"); - goto err_reg_disable; - } - ret = pci_pwrctrl_power_on_devices(dev); if (ret) { dev_err(dev, "failed to power on pwrctrl devices\n"); - goto err_pwrctrl_destroy; + goto err_reg_disable; } ret = imx_pcie_clk_enable(imx_pcie); @@ -1461,9 +1455,6 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) imx_pcie_clk_disable(imx_pcie); err_pwrctrl_power_off: pci_pwrctrl_power_off_devices(dev); -err_pwrctrl_destroy: - if (ret != -EPROBE_DEFER) - pci_pwrctrl_destroy_devices(dev); err_reg_disable: if (imx_pcie->vpcie) regulator_disable(imx_pcie->vpcie); @@ -1954,11 +1945,15 @@ static int imx_pcie_probe(struct platform_device *pdev) if (ret) return ret; + ret = pci_pwrctrl_create_devices(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to create pwrctrl devices\n"); + pci->use_parent_dt_ranges = true; if (imx_pcie->drvdata->mode == DW_PCIE_EP_TYPE) { ret = imx_add_pcie_ep(imx_pcie, pdev); if (ret < 0) - return ret; + goto err_pwrctrl_destroy; /* * FIXME: Only single Device (EPF) is supported due to the @@ -1973,7 +1968,7 @@ static int imx_pcie_probe(struct platform_device *pdev) pci->pp.use_atu_msg = true; ret = dw_pcie_host_init(&pci->pp); if (ret < 0) - return ret; + goto err_pwrctrl_destroy; if (pci_msi_enabled()) { u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_MSI); @@ -1985,6 +1980,11 @@ static int imx_pcie_probe(struct platform_device *pdev) } return 0; + +err_pwrctrl_destroy: + if (ret != -EPROBE_DEFER) + pci_pwrctrl_destroy_devices(dev); + return ret; } static void imx_pcie_shutdown(struct platform_device *pdev) From f26b1c697a2bd72b1b68dca78ab1e95701a0ffb6 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Mon, 13 Jul 2026 10:34:33 +0800 Subject: [PATCH 5/6] PCI: imx6: Add 'skip_pwrctrl_off' flag support Use 'dw_pcie_rp->skip_pwrctrl_off' to avoid powering off devices during suspend to preserve wakeup capability of the devices and also not to power on the devices in the init path. This allows controller power-off to be skipped when some devices (e.g. M.2 Key E cards without auxiliary power) need to support PCIe L2 link state and wake-up mechanisms. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260713023435.235765-3-sherry.sun@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 92f8e4a299e8..afcf3b6bf3cd 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1382,10 +1382,12 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } - ret = pci_pwrctrl_power_on_devices(dev); - if (ret) { - dev_err(dev, "failed to power on pwrctrl devices\n"); - goto err_reg_disable; + if (!pp->skip_pwrctrl_off) { + ret = pci_pwrctrl_power_on_devices(dev); + if (ret) { + dev_err(dev, "failed to power on pwrctrl devices\n"); + goto err_reg_disable; + } } ret = imx_pcie_clk_enable(imx_pcie); @@ -1454,7 +1456,8 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) err_clk_disable: imx_pcie_clk_disable(imx_pcie); err_pwrctrl_power_off: - pci_pwrctrl_power_off_devices(dev); + if (!pp->skip_pwrctrl_off) + pci_pwrctrl_power_off_devices(dev); err_reg_disable: if (imx_pcie->vpcie) regulator_disable(imx_pcie->vpcie); @@ -1473,7 +1476,8 @@ static void imx_pcie_host_exit(struct dw_pcie_rp *pp) } imx_pcie_clk_disable(imx_pcie); - pci_pwrctrl_power_off_devices(pci->dev); + if (!pci->pp.skip_pwrctrl_off) + pci_pwrctrl_power_off_devices(pci->dev); if (imx_pcie->vpcie) regulator_disable(imx_pcie->vpcie); } From c2b9620359b759d2c73be47689b3a344cb4e091b Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Wed, 15 Jul 2026 15:30:24 +0800 Subject: [PATCH 6/6] PCI: imx6: Add runtime PM support for i.MX95 Enable runtime PM support for i.MX95 PCIe Root Complex to allow dynamic power management when the PCIe link is idle. The i.MX95 PCIe controller supports entering D3hot state when PCIe devices are not actively in use. This implementation uses pm_runtime_no_callbacks() to leverage the PCI core's generic runtime PM handling. The PCI core automatically manages D-state transitions based on the runtime PM state of connected endpoint devices. Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260715073024.1377228-1-hongxing.zhu@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index afcf3b6bf3cd..19a7b7349296 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -121,6 +121,7 @@ enum imx_pcie_variants { #define IMX_PCIE_FLAG_SKIP_L23_READY BIT(12) /* Preserve MSI capability for platforms that require it */ #define IMX_PCIE_FLAG_KEEP_MSI_CAP BIT(13) +#define IMX_PCIE_FLAG_PM_RUNTIME BIT(14) #define imx_check_flag(pci, val) (pci->drvdata->flags & val) @@ -1965,6 +1966,13 @@ static int imx_pcie_probe(struct platform_device *pdev) */ imx_pcie_add_lut_by_rid(imx_pcie, 0); } else { + if (imx_pcie->drvdata->flags & IMX_PCIE_FLAG_PM_RUNTIME) { + pm_runtime_no_callbacks(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret < 0) + return ret; + } + if (imx_check_flag(imx_pcie, IMX_PCIE_FLAG_SKIP_L23_READY)) pci->pp.skip_l23_ready = true; if (imx_check_flag(imx_pcie, IMX_PCIE_FLAG_KEEP_MSI_CAP)) @@ -2113,6 +2121,7 @@ static const struct imx_pcie_drvdata drvdata[] = { .flags = IMX_PCIE_FLAG_HAS_SERDES | IMX_PCIE_FLAG_HAS_LUT | IMX_PCIE_FLAG_8GT_ECN_ERR051586 | + IMX_PCIE_FLAG_PM_RUNTIME | IMX_PCIE_FLAG_SUPPORTS_SUSPEND, .ltssm_off = IMX95_PE0_GEN_CTRL_3, .ltssm_mask = IMX95_PCIE_LTSSM_EN,