From: Richard Zhu Date: Tue, 28 Jul 2026 03:51:59 +0000 (+0800) Subject: PCI: imx6: Fix i.MX6Q/DL boot hang caused by improper PHY power sequencing X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7678e81498e20e78d7d5f64e552cd153117c1d66;p=thirdparty%2Flinux.git PCI: imx6: Fix i.MX6Q/DL boot hang caused by improper PHY power sequencing commit 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") introduced a boot hang on i.MX6Q/DL variants by reordering imx_pcie_host_init() to call imx6q_pcie_enable_ref_clk() (which powered up the PHY) before imx6q_pcie_core_reset() (which powered it back down). Before 610fa91d9863, the sequence was: 1. imx_pcie_assert_core_reset() - power down PHY (set TEST_PD), set REF_CLK_EN 2. imx_pcie_clk_enable() - power up PHY (clear TEST_PD), set REF_CLK_EN 3. Link training starts with PHY powered up (TEST_PD cleared) 4. Link training succeeds After 610fa91d9863, the sequence became: 1. imx_pcie_clk_enable() - power up PHY (clear TEST_PD), set REF_CLK_EN 2. imx_pcie_assert_core_reset() - power down PHY (set TEST_PD), set REF_CLK_EN 3. imx_pcie_deassert_core_reset() - does nothing 4. Link training starts with PHY powered down (TEST_PD set) 5. Link training fails and boot hangs when PHY register accesses hang To fix this: - Remove TEST_PD PHY power control from imx6q_pcie_enable_ref_clk() - Remove REF_CLK_EN control from imx6q_pcie_core_reset() - Add TEST_PD PHY power control to imx6qp_pcie_core_reset(), which previously relied on imx6q_pcie_enable_ref_clk() to power up the PHY by clearing TEST_PD - Clear TEST_PD to power on PHY in imx_pcie_deassert_core_reset() These changes together ensure the correct sequence: 1. REF_CLK_EN set in clk_enable() (TEST_PD untouched) 2. TEST_PD set in assert_core_reset() (PHY power off) 3. TEST_PD cleared in deassert_core_reset() (PHY power on) 4. Link training starts with proper PHY state The i.MX6Q/DL PCIe PHY requires approximately 120us between TEST_PD de-assertion and link training start. Add usleep_range(200, 500) in imx6q_pcie_core_reset() after clearing TEST_PD to satisfy this requirement. Add explicit imx_pcie_assert_core_reset() calls in error paths and host_exit() to ensure no power leak. Fixes: 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") Reported-by: Leonardo Costa Closes: https://lore.kernel.org/lkml/20260629143439.361560-1-leoreis.costa@gmail.com/ Signed-off-by: Richard Zhu Signed-off-by: Bjorn Helgaas Tested-by: Leonardo Costa Reviewed-by: Frank Li Link: https://patch.msgid.link/20260728035159.2702021-1-hongxing.zhu@oss.nxp.com --- diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index dba27eac6bff..f7389b5437df 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -680,21 +680,12 @@ static int imx_pcie_attach_pd(struct device *dev) static int imx6q_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) { - if (enable) { - /* power up core phy and enable ref clock */ - regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_TEST_PD); - /* - * The async reset input need ref clock to sync internally, - * when the ref clock comes after reset, internal synced - * reset time is too short, cannot meet the requirement. - * Add a ~10us delay here. - */ - usleep_range(10, 100); - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_REF_CLK_EN); - } else { - regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_REF_CLK_EN); - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_TEST_PD); - } + if (enable) + regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN); + else + regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN); return 0; } @@ -825,8 +816,16 @@ static int imx6sx_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) static int imx6qp_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) { + if (assert) + regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD); + else + regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD); + regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_SW_RST, assert ? IMX6Q_GPR1_PCIE_SW_RST : 0); + if (!assert) usleep_range(200, 500); @@ -835,11 +834,15 @@ static int imx6qp_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) static int imx6q_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) { - if (!assert) - return 0; + if (assert) + regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD); + else + regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD); - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_TEST_PD); - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_REF_CLK_EN); + if (!assert) + usleep_range(200, 500); return 0; } @@ -1445,6 +1448,7 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) return 0; err_phy_off: + imx_pcie_assert_core_reset(imx_pcie); phy_power_off(imx_pcie->phy); err_phy_exit: phy_exit(imx_pcie->phy); @@ -1466,6 +1470,7 @@ static void imx_pcie_host_exit(struct dw_pcie_rp *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct imx_pcie *imx_pcie = to_imx_pcie(pci); + imx_pcie_assert_core_reset(imx_pcie); if (imx_pcie->phy) { if (phy_power_off(imx_pcie->phy)) dev_err(pci->dev, "unable to power off PHY\n");