From aad953fb4eed0df5486cd54ccad80ac197678e01 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 19 Mar 2026 17:08:44 +0800 Subject: [PATCH 1/8] PCI: imx6: Fix IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling The IMX6SX_GPR12_PCIE_TEST_POWERDOWN bit does not control the PCIe reference clock on i.MX6SX. Instead, it is part of i.MX6SX PCIe core reset sequence. Move the IMX6SX_GPR12_PCIE_TEST_POWERDOWN assertion/deassertion into the core reset functions to properly reflect its purpose. Remove the .enable_ref_clk() callback for i.MX6SX since it was incorrectly manipulating this bit. Fixes: e3c06cd063d6 ("PCI: imx6: Add initial imx6sx support") Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260319090844.444987-1-hongxing.zhu@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index e35044cc5218..1034ac5c5f5c 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -665,14 +665,6 @@ static int imx_pcie_attach_pd(struct device *dev) return 0; } -static int imx6sx_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) -{ - regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6SX_GPR12_PCIE_TEST_POWERDOWN, - enable ? 0 : IMX6SX_GPR12_PCIE_TEST_POWERDOWN); - return 0; -} - static int imx6q_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) { if (enable) { @@ -786,6 +778,9 @@ static int imx6sx_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) if (assert) regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_TEST_POWERDOWN); + else + regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_TEST_POWERDOWN); /* Force PCIe PHY reset */ regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET, @@ -1877,7 +1872,6 @@ static const struct imx_pcie_drvdata drvdata[] = { .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, .init_phy = imx6sx_pcie_init_phy, - .enable_ref_clk = imx6sx_pcie_enable_ref_clk, .core_reset = imx6sx_pcie_core_reset, .ops = &imx_pcie_host_ops, }, From e3d334156b593c465153700b3f9fbdcac5af9cbd Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:38 +0800 Subject: [PATCH 2/8] dt-bindings: PCI: fsl,imx6q-pcie: Add reset GPIO in Root Port node Update fsl,imx6q-pcie.yaml to include the standard reset-gpios property for the Root Port node. The reset-gpios property is already defined in pci-bus-common.yaml for PERST#, so use it instead of the local reset-gpio property. Keep the existing reset-gpio property in the bridge node for backward compatibility, but mark it as deprecated. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260422093549.407022-2-sherry.sun@nxp.com --- .../bindings/pci/fsl,imx6q-pcie.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml index 9d1349855b42..e8b8131f5f23 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml @@ -66,16 +66,34 @@ properties: - const: dma reset-gpio: + deprecated: true description: Should specify the GPIO for controlling the PCI bus device reset signal. It's not polarity aware and defaults to active-low reset sequence (L=reset state, H=operation state) (optional required). + This property is deprecated, instead of referencing this property from the + host bridge node, use the reset-gpios property from the root port node. reset-gpio-active-high: + deprecated: true description: If present then the reset sequence using the GPIO specified in the "reset-gpio" property is reversed (H=reset state, L=operation state) (optional required). + This property is deprecated along with the reset-gpio property above, use + the reset-gpios property from the root port node. type: boolean + pcie@0: + description: + Describe the i.MX6 PCIe Root Port. + type: object + $ref: /schemas/pci/pci-pci-bridge.yaml# + + properties: + reg: + maxItems: 1 + + unevaluatedProperties: false + required: - compatible - reg @@ -236,6 +254,7 @@ unevaluatedProperties: false examples: - | #include + #include #include pcie: pcie@1ffc000 { @@ -262,5 +281,18 @@ examples: <&clks IMX6QDL_CLK_LVDS1_GATE>, <&clks IMX6QDL_CLK_PCIE_REF_125M>; clock-names = "pcie", "pcie_bus", "pcie_phy"; + + pcie_port0: pcie@0 { + compatible = "pciclass,0604"; + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>; + }; }; ... From b269bb5e4cc3cf04a592d516a3dc260d9d893f24 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:39 +0800 Subject: [PATCH 3/8] PCI: host-generic: Add common helpers for parsing Root Port properties Introduce generic helper functions to parse Root Port device tree nodes and extract common properties like reset GPIOs. This allows multiple PCI host controller drivers to share the same parsing logic. Define struct pci_host_port to hold common Root Port properties (currently only list of PERST# GPIO descriptors) and add pci_host_common_parse_ports() to parse Root Port nodes from device tree. Also add the 'ports' list to struct pci_host_bridge to better maintain parsed Root Port information. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260422093549.407022-3-sherry.sun@nxp.com --- drivers/pci/controller/pci-host-common.c | 169 +++++++++++++++++++++++ drivers/pci/controller/pci-host-common.h | 28 ++++ drivers/pci/probe.c | 1 + include/linux/pci.h | 1 + 4 files changed, 199 insertions(+) diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index d6258c1cffe5..9bf66c256f81 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,174 @@ #include "pci-host-common.h" +/** + * pci_host_common_delete_ports - Cleanup function for port list + * @data: Pointer to the port list head + */ +void pci_host_common_delete_ports(void *data) +{ + struct list_head *ports = data; + struct pci_host_perst *perst, *tmp_perst; + struct pci_host_port *port, *tmp_port; + + list_for_each_entry_safe(port, tmp_port, ports, list) { + list_for_each_entry_safe(perst, tmp_perst, &port->perst, list) + list_del(&perst->list); + list_del(&port->list); + } +} +EXPORT_SYMBOL_GPL(pci_host_common_delete_ports); + +/** + * pci_host_common_parse_perst - Parse PERST# from all nodes, depth first + * @dev: Device pointer + * @port: PCI host port + * @np: Device tree node to start parsing from + * + * Recursively parse PERST# GPIO from all PCIe bridge nodes starting from + * @np in a depth-first manner. + * + * Return: 0 on success, negative error code on failure. + */ +static int pci_host_common_parse_perst(struct device *dev, + struct pci_host_port *port, + struct device_node *np) +{ + struct pci_host_perst *perst; + struct gpio_desc *reset; + int ret; + + if (!of_property_present(np, "reset-gpios")) + goto parse_child_node; + + reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(np), "reset", + GPIOD_ASIS, "PERST#"); + if (IS_ERR(reset)) { + /* + * FIXME: GPIOLIB currently supports exclusive GPIO access only. + * Non exclusive access is broken. But shared PERST# requires + * non-exclusive access. So once GPIOLIB properly supports it, + * implement it here. + */ + if (PTR_ERR(reset) == -EBUSY) + dev_err(dev, "Shared PERST# is not supported\n"); + + return PTR_ERR(reset); + } + + perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL); + if (!perst) + return -ENOMEM; + + INIT_LIST_HEAD(&perst->list); + perst->desc = reset; + list_add_tail(&perst->list, &port->perst); + +parse_child_node: + for_each_available_child_of_node_scoped(np, child) { + ret = pci_host_common_parse_perst(dev, port, child); + if (ret) + return ret; + } + + return 0; +} + +/** + * pci_host_common_parse_port - Parse a single Root Port node + * @dev: Device pointer + * @bridge: PCI host bridge + * @node: Device tree node of the Root Port + * + * Parse Root Port properties from the device tree. Currently it only + * handles the PERST# GPIO (including PERST# GPIOs from all PCIe bridge + * nodes under this Root Port), which is optional. + * + * NOTE: This helper fetches resources (like PERST# GPIO) optionally. If a + * controller driver has a hard dependency on certain resources (PHY, + * clocks, regulators, etc.), those resources MUST be modeled correctly in + * the DT binding and validated in DTS. This helper cannot enforce such + * 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. + */ +static int pci_host_common_parse_port(struct device *dev, + struct pci_host_bridge *bridge, + struct device_node *node) +{ + struct pci_host_port *port; + int ret; + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + INIT_LIST_HEAD(&port->perst); + + ret = pci_host_common_parse_perst(dev, port, node); + 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); + + return 0; +} + +/** + * pci_host_common_parse_ports - Parse Root Port nodes from device tree + * @dev: Device pointer + * @bridge: PCI host bridge + * + * 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. + */ +int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *bridge) +{ + int ret = -ENODEV; + + for_each_available_child_of_node_scoped(dev->of_node, of_port) { + if (!of_node_is_type(of_port, "pci")) + continue; + ret = pci_host_common_parse_port(dev, bridge, of_port); + if (ret) + goto err_cleanup; + } + + if (ret) + return ret; + + return devm_add_action_or_reset(dev, pci_host_common_delete_ports, + &bridge->ports); + +err_cleanup: + pci_host_common_delete_ports(&bridge->ports); + return ret; +} +EXPORT_SYMBOL_GPL(pci_host_common_parse_ports); + static void gen_pci_unmap_cfg(void *ptr) { pci_ecam_free((struct pci_config_window *)ptr); diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h index b5075d4bd7eb..0f5fcc02e778 100644 --- a/drivers/pci/controller/pci-host-common.h +++ b/drivers/pci/controller/pci-host-common.h @@ -12,6 +12,34 @@ struct pci_ecam_ops; +/** + * struct pci_host_perst - PERST# GPIO descriptor + * @list: List node for linking multiple PERST# GPIOs + * @desc: GPIO descriptor for PERST# signal + * + * This structure holds a single PERST# GPIO descriptor. + */ +struct pci_host_perst { + struct list_head list; + struct gpio_desc *desc; +}; + +/** + * struct pci_host_port - Generic Root Port properties + * @list: List node for linking multiple ports + * @perst: List of PERST# GPIO descriptors for this port and its children + * + * This structure contains common properties that can be parsed from + * Root Port device tree nodes. + */ +struct pci_host_port { + struct list_head list; + struct list_head perst; +}; + +void pci_host_common_delete_ports(void *data); +int pci_host_common_parse_ports(struct device *dev, + struct pci_host_bridge *bridge); int pci_host_common_probe(struct platform_device *pdev); int pci_host_common_init(struct platform_device *pdev, struct pci_host_bridge *bridge, diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index b63cd0c310bc..6094b6c1fc90 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -660,6 +660,7 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge) { INIT_LIST_HEAD(&bridge->windows); INIT_LIST_HEAD(&bridge->dma_ranges); + INIT_LIST_HEAD(&bridge->ports); /* * We assume we can manage these PCIe features. Some systems may diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c11..cb5f3e7e8e48 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -636,6 +636,7 @@ struct pci_host_bridge { int domain_nr; struct list_head windows; /* resource_entry */ struct list_head dma_ranges; /* dma ranges resource list */ + struct list_head ports; /* Root Port list (pci_host_port) */ #ifdef CONFIG_PCI_IDE u16 nr_ide_streams; /* Max streams possibly active in @ide_stream_ida */ struct ida ide_stream_ida; From 610fa91d9863d9bc0ce496c5129328459c75174b Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:40 +0800 Subject: [PATCH 4/8] PCI: imx6: Assert PERST# before enabling regulators The PCIe endpoint may start responding or driving signals as soon as its supply is enabled, even before the reference clock is stable. Asserting PERST# before enabling the regulator ensures that the endpoint remains in reset throughout the entire power-up sequence, until both power and refclk are known to be stable and link initialization can safely begin. Currently, the driver enables the vpcie3v3aux regulator in imx_pcie_probe() before PERST# is asserted in imx_pcie_host_init(), which may cause PCIe endpoint undefined behavior during early power-up. However, there is no issue so far because PERST# is requested as GPIOD_OUT_HIGH in imx_pcie_probe(), which guarantees that PERST# is asserted before enabling the vpcie3v3aux regulator. This prepares for an upcoming changes that will parse the reset property using the new Root Port binding, which will use GPIOD_ASIS when requesting the reset GPIO. With GPIOD_ASIS, the GPIO state is not guaranteed, so explicit sequencing is required. Fix the power sequencing by: 1. Moving vpcie3v3aux regulator enable from probe to imx_pcie_host_init(), where it can be properly sequenced with PERST#. 2. Moving imx_pcie_assert_perst() before regulator and clock enable to ensure correct ordering. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu Link: https://patch.msgid.link/20260422093549.407022-4-sherry.sun@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 50 +++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 1034ac5c5f5c..5335c2b140fb 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -168,6 +168,8 @@ struct imx_pcie { u32 tx_swing_full; u32 tx_swing_low; struct regulator *vpcie; + struct regulator *vpcie_aux; + bool vpcie_aux_enabled; struct regulator *vph; void __iomem *phy_base; @@ -1217,6 +1219,13 @@ static void imx_pcie_disable_device(struct pci_host_bridge *bridge, imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev)); } +static void imx_pcie_vpcie_aux_disable(void *data) +{ + struct regulator *vpcie_aux = data; + + regulator_disable(vpcie_aux); +} + static void imx_pcie_assert_perst(struct imx_pcie *imx_pcie, bool assert) { if (assert) { @@ -1237,6 +1246,24 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) struct imx_pcie *imx_pcie = to_imx_pcie(pci); int ret; + imx_pcie_assert_perst(imx_pcie, true); + + /* Keep 3.3Vaux supply enabled for entire PCIe controller lifecycle */ + if (imx_pcie->vpcie_aux && !imx_pcie->vpcie_aux_enabled) { + ret = regulator_enable(imx_pcie->vpcie_aux); + if (ret) { + dev_err(dev, "failed to enable vpcie_aux regulator: %d\n", + ret); + return ret; + } + imx_pcie->vpcie_aux_enabled = true; + + ret = devm_add_action_or_reset(dev, imx_pcie_vpcie_aux_disable, + imx_pcie->vpcie_aux); + if (ret) + return ret; + } + if (imx_pcie->vpcie) { ret = regulator_enable(imx_pcie->vpcie); if (ret) { @@ -1246,25 +1273,24 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } + ret = imx_pcie_clk_enable(imx_pcie); + if (ret) { + dev_err(dev, "unable to enable pcie clocks: %d\n", ret); + goto err_reg_disable; + } + if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { pp->bridge->enable_device = imx_pcie_enable_device; pp->bridge->disable_device = imx_pcie_disable_device; } imx_pcie_assert_core_reset(imx_pcie); - imx_pcie_assert_perst(imx_pcie, true); if (imx_pcie->drvdata->init_phy) imx_pcie->drvdata->init_phy(imx_pcie); imx_pcie_configure_type(imx_pcie); - ret = imx_pcie_clk_enable(imx_pcie); - if (ret) { - dev_err(dev, "unable to enable pcie clocks: %d\n", ret); - goto err_reg_disable; - } - if (imx_pcie->phy) { ret = phy_init(imx_pcie->phy); if (ret) { @@ -1777,9 +1803,13 @@ static int imx_pcie_probe(struct platform_device *pdev) of_property_read_u32(node, "fsl,max-link-speed", &pci->max_link_speed); imx_pcie->supports_clkreq = of_property_read_bool(node, "supports-clkreq"); - ret = devm_regulator_get_enable_optional(&pdev->dev, "vpcie3v3aux"); - if (ret < 0 && ret != -ENODEV) - return dev_err_probe(dev, ret, "failed to enable Vaux supply\n"); + imx_pcie->vpcie_aux = devm_regulator_get_optional(&pdev->dev, + "vpcie3v3aux"); + if (IS_ERR(imx_pcie->vpcie_aux)) { + if (PTR_ERR(imx_pcie->vpcie_aux) != -ENODEV) + return PTR_ERR(imx_pcie->vpcie_aux); + imx_pcie->vpcie_aux = NULL; + } imx_pcie->vpcie = devm_regulator_get_optional(&pdev->dev, "vpcie"); if (IS_ERR(imx_pcie->vpcie)) { From 250eea5c06f5291603612cdf0c60326db4e19141 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:41 +0800 Subject: [PATCH 5/8] PCI: imx6: Parse 'reset-gpios' in Root Port nodes The current DT binding for pci-imx6 specifies the 'reset-gpios' property in the host bridge node. However, the PERST# signal logically belongs to individual Root Ports rather than the host bridge itself. This becomes important when supporting PCIe Key E connector and the PCI power control framework for pci-imx6 driver, which requires properties to be specified in Root Port nodes. Parse 'reset-gpios' from Root Port nodes and the PCIe bridge nodes under the Root Port using the common helper pci_host_common_parse_ports(), and update the reset GPIO handling to use the parsed port list from bridge->ports. To maintain DT backwards compatibility, fall back to the legacy method of parsing the host bridge node if the reset property is not present in the Root Port nodes. Since now the reset GPIO is obtained with GPIOD_ASIS flag, it may be in input mode, so use gpiod_direction_output() instead of gpiod_set_value_cansleep() to ensure the reset GPIO is properly configured as output before setting its value. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu Link: https://patch.msgid.link/20260422093549.407022-5-sherry.sun@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 88 ++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 5335c2b140fb..773ab65b2afa 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -34,6 +34,7 @@ #include #include "../../pci.h" +#include "../pci-host-common.h" #include "pcie-designware.h" #define IMX8MQ_GPR_PCIE_REF_USE_PAD BIT(9) @@ -152,7 +153,6 @@ struct imx_lut_data { struct imx_pcie { struct dw_pcie *pci; - struct gpio_desc *reset_gpiod; struct clk_bulk_data *clks; int num_clks; bool supports_clkreq; @@ -1219,6 +1219,41 @@ static void imx_pcie_disable_device(struct pci_host_bridge *bridge, imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev)); } +static int imx_pcie_parse_legacy_binding(struct imx_pcie *pcie) +{ + struct device *dev = pcie->pci->dev; + struct pci_host_bridge *bridge = pcie->pci->pp.bridge; + struct pci_host_port *port; + struct pci_host_perst *perst; + struct gpio_desc *reset; + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + if (!reset) + return 0; + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL); + if (!perst) + return -ENOMEM; + + INIT_LIST_HEAD(&port->perst); + perst->desc = reset; + INIT_LIST_HEAD(&perst->list); + list_add_tail(&perst->list, &port->perst); + + INIT_LIST_HEAD(&port->list); + list_add_tail(&port->list, &bridge->ports); + + return devm_add_action_or_reset(dev, pci_host_common_delete_ports, + &bridge->ports); +} + static void imx_pcie_vpcie_aux_disable(void *data) { struct regulator *vpcie_aux = data; @@ -1228,14 +1263,26 @@ static void imx_pcie_vpcie_aux_disable(void *data) static void imx_pcie_assert_perst(struct imx_pcie *imx_pcie, bool assert) { + struct dw_pcie *pci = imx_pcie->pci; + struct pci_host_bridge *bridge = pci->pp.bridge; + struct pci_host_perst *perst; + struct pci_host_port *port; + + if (!bridge || list_empty(&bridge->ports)) + return; + if (assert) { - gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 1); - } else { - if (imx_pcie->reset_gpiod) { - msleep(PCIE_T_PVPERL_MS); - gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 0); - msleep(PCIE_RESET_CONFIG_WAIT_MS); + list_for_each_entry(port, &bridge->ports, list) { + list_for_each_entry(perst, &port->perst, list) + gpiod_direction_output(perst->desc, 1); } + } else { + mdelay(PCIE_T_PVPERL_MS); + list_for_each_entry(port, &bridge->ports, list) { + list_for_each_entry(perst, &port->perst, list) + gpiod_direction_output(perst->desc, 0); + } + mdelay(PCIE_RESET_CONFIG_WAIT_MS); } } @@ -1244,8 +1291,28 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct device *dev = pci->dev; struct imx_pcie *imx_pcie = to_imx_pcie(pci); + struct pci_host_bridge *bridge = pp->bridge; int ret; + if (bridge && list_empty(&bridge->ports)) { + /* 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; + } + + /* + * Fall back to legacy binding for DT backwards + * compatibility + */ + ret = imx_pcie_parse_legacy_binding(imx_pcie); + if (ret) + return ret; + } + } + imx_pcie_assert_perst(imx_pcie, true); /* Keep 3.3Vaux supply enabled for entire PCIe controller lifecycle */ @@ -1699,13 +1766,6 @@ static int imx_pcie_probe(struct platform_device *pdev) return PTR_ERR(imx_pcie->phy_base); } - /* Fetch GPIOs */ - imx_pcie->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(imx_pcie->reset_gpiod)) - return dev_err_probe(dev, PTR_ERR(imx_pcie->reset_gpiod), - "unable to get reset gpio\n"); - gpiod_set_consumer_name(imx_pcie->reset_gpiod, "PCIe reset"); - /* Fetch clocks */ imx_pcie->num_clks = devm_clk_bulk_get_all(dev, &imx_pcie->clks); if (imx_pcie->num_clks < 0) From 0c26b1c34d12d4debfb5363cc0be6cdf68e87ba2 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 18 May 2026 15:27:14 +0800 Subject: [PATCH 6/8] PCI: imx6: Configure REF_USE_PAD before PHY reset for i.MX95 According to the i.MX95 PCIe PHY Databook, the ref_use_pad signal in the Common Block Signals section selects the reference clock source connected to the PHY pads. Per the specification, any change to this input must be followed by a PHY reset assertion to take effect. Move the REF_USE_PAD configuration before the PHY reset toggle to comply with the required initialization sequence. Fixes: 47f54a902dcd ("PCI: imx6: Toggle the core reset for i.MX95 PCIe") Signed-off-by: Richard Zhu [mani: renamed the callback and helper to match the usecase] Signed-off-by: Manivannan Sadhasivam Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260518072715.3166514-2-hongxing.zhu@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 773ab65b2afa..b32748816ac8 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -138,6 +138,7 @@ struct imx_pcie_drvdata { const u32 mode_off[IMX_PCIE_MAX_INSTANCES]; const u32 mode_mask[IMX_PCIE_MAX_INSTANCES]; const struct pci_epc_features *epc_features; + int (*select_ref_clk_src)(struct imx_pcie *pcie); int (*init_phy)(struct imx_pcie *pcie); int (*enable_ref_clk)(struct imx_pcie *pcie, bool enable); int (*core_reset)(struct imx_pcie *pcie, bool assert); @@ -249,6 +250,24 @@ static unsigned int imx_pcie_grp_offset(const struct imx_pcie *imx_pcie) return imx_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14; } +static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie) +{ + bool ext = imx_pcie->enable_ext_refclk; + + /* + * Regarding the Signal Descriptions of i.MX95 PCIe PHY, ref_use_pad is + * used to select reference clock connected to a pair of pads. + * + * Any change in this input must be followed by phy_reset assertion. + */ + + regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL, + IMX95_PCIE_REF_USE_PAD, + ext ? IMX95_PCIE_REF_USE_PAD : 0); + + return 0; +} + static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) { bool ext = imx_pcie->enable_ext_refclk; @@ -271,9 +290,6 @@ static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) IMX95_PCIE_PHY_CR_PARA_SEL, IMX95_PCIE_PHY_CR_PARA_SEL); - regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL, - IMX95_PCIE_REF_USE_PAD, - ext ? IMX95_PCIE_REF_USE_PAD : 0); regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, IMX95_PCIE_REF_CLKEN, ext ? 0 : IMX95_PCIE_REF_CLKEN); @@ -1351,6 +1367,9 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) pp->bridge->disable_device = imx_pcie_disable_device; } + if (imx_pcie->drvdata->select_ref_clk_src) + imx_pcie->drvdata->select_ref_clk_src(imx_pcie); + imx_pcie_assert_core_reset(imx_pcie); if (imx_pcie->drvdata->init_phy) @@ -2051,6 +2070,7 @@ static const struct imx_pcie_drvdata drvdata[] = { .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, .core_reset = imx95_pcie_core_reset, .init_phy = imx95_pcie_init_phy, + .select_ref_clk_src = imx95_pcie_select_ref_clk_src, .wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock, .enable_ref_clk = imx95_pcie_enable_ref_clk, .clr_clkreq_override = imx95_pcie_clr_clkreq_override, @@ -2106,6 +2126,7 @@ static const struct imx_pcie_drvdata drvdata[] = { .ltssm_mask = IMX95_PCIE_LTSSM_EN, .mode_off[0] = IMX95_PE0_GEN_CTRL_1, .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, + .select_ref_clk_src = imx95_pcie_select_ref_clk_src, .init_phy = imx95_pcie_init_phy, .core_reset = imx95_pcie_core_reset, .wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock, From 9dda3f83ba677b9cc2613cecd9120123000ae50f Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 18 May 2026 15:27:15 +0800 Subject: [PATCH 7/8] PCI: imx6: Assert ref_clk_en after reference clock stabilizes on i.MX95 According to the PHY Databook Common Block Signals section, the ref_clk_en signal must remain de-asserted until the reference clock is running at the appropriate frequency. Once the clock is stable, ref_clk_en can be asserted. For lower power states where the reference clock to the PHY is disabled, ref_clk_en should also be de-asserted. Move the ref_clk_en bit manipulation into imx95_pcie_enable_ref_clk() to ensure the reference clock stabilizes before ref_clk_en is asserted and before the PHY reset is de-asserted. This aligns with the timing requirements specified in the PHY documentation. Fixes: d8574ce57d76 ("PCI: imx6: Add external reference clock input mode support") Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260518072715.3166514-3-hongxing.zhu@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 28 +++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index b32748816ac8..cce78a7a1624 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -270,8 +270,6 @@ static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie) static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) { - bool ext = imx_pcie->enable_ext_refclk; - /* * ERR051624: The Controller Without Vaux Cannot Exit L23 Ready * Through Beacon or PERST# De-assertion @@ -290,10 +288,6 @@ static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) IMX95_PCIE_PHY_CR_PARA_SEL, IMX95_PCIE_PHY_CR_PARA_SEL); - regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, - IMX95_PCIE_REF_CLKEN, - ext ? 0 : IMX95_PCIE_REF_CLKEN); - return 0; } @@ -742,7 +736,29 @@ static void imx95_pcie_clkreq_override(struct imx_pcie *imx_pcie, bool enable) static int imx95_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) { + bool ext = imx_pcie->enable_ext_refclk; + imx95_pcie_clkreq_override(imx_pcie, enable); + /* + * The ref_clk_en signal must remain de-asserted until the + * reference clock is running at appropriate frequency, at which + * point this bit can be asserted. For lower power states where + * the reference clock to the PHY is disabled, it may also be + * de-asserted. + * +------------------- -+--------+----------------+ + * | External clock mode | Enable | PCIE_REF_CLKEN | + * +---------------------+--------+----------------+ + * | TRUE | X | 1b'0 | + * +---------------------+--------+----------------+ + * | FALSE | TRUE | 1b'1 | + * +---------------------+--------+----------------+ + * | FALSE | FALSE | 1b'0 | + * +---------------------+--------+----------------+ + */ + regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, + IMX95_PCIE_REF_CLKEN, + ext || !enable ? 0 : IMX95_PCIE_REF_CLKEN); + return 0; } From 85c1fcfa740d4c737f5575fc7251883e54227a51 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 20 May 2026 16:48:57 +0800 Subject: [PATCH 8/8] PCI: imx6: Integrate new pwrctrl API Integrate the PCI pwrctrl framework into the pci-imx6 driver to provide standardized power management for PCI devices. Legacy regulator handling (vpcie-supply at controller level) is maintained for backward compatibility with existing device trees. New device trees should specify power supplies at the Root Port level to utilize the pwrctrl framework. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260520084904.2424253-2-sherry.sun@oss.nxp.com --- drivers/pci/controller/dwc/Kconfig | 1 + drivers/pci/controller/dwc/pci-imx6.c | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index f2fde13107f2..327b0dc65550 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -114,6 +114,7 @@ config PCI_IMX6_HOST depends on PCI_MSI select PCIE_DW_HOST select PCI_IMX6 + select PCI_PWRCTRL_GENERIC help Enables support for the PCIe controller in the i.MX SoCs to work in Root Complex mode. The PCI controller on i.MX is based diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index cce78a7a1624..63637fa5f108 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1363,6 +1364,7 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) return ret; } + /* Legacy regulator handling for DT backward compatibility. */ if (imx_pcie->vpcie) { ret = regulator_enable(imx_pcie->vpcie); if (ret) { @@ -1372,10 +1374,22 @@ 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; + } + ret = imx_pcie_clk_enable(imx_pcie); if (ret) { dev_err(dev, "unable to enable pcie clocks: %d\n", ret); - goto err_reg_disable; + goto err_pwrctrl_power_off; } if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { @@ -1437,6 +1451,11 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) phy_exit(imx_pcie->phy); err_clk_disable: 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); @@ -1455,6 +1474,7 @@ 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 (imx_pcie->vpcie) regulator_disable(imx_pcie->vpcie); } @@ -1966,6 +1986,8 @@ static void imx_pcie_shutdown(struct platform_device *pdev) /* bring down link, so bootloader gets clean state in case of reboot */ imx_pcie_assert_core_reset(imx_pcie); imx_pcie_assert_perst(imx_pcie, true); + pci_pwrctrl_power_off_devices(&pdev->dev); + pci_pwrctrl_destroy_devices(&pdev->dev); } static const struct imx_pcie_drvdata drvdata[] = {