* [PATCH v3 1/4] phy: qcom: qmp-pcie: Skip PHY reset if already up
2026-07-09 6:35 [PATCH v3 0/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
@ 2026-07-09 6:35 ` Krishna Chaitanya Chundru
2026-07-09 6:35 ` [PATCH v3 2/4] PCI: qcom: Keep PERST# GPIO state as-is during probe Krishna Chaitanya Chundru
` (3 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-07-09 6:35 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Philipp Zabel, Jingoo Han,
Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas
Cc: linux-arm-msm, linux-phy, linux-kernel, linux-pci,
Krishna Chaitanya Chundru, Abel Vesa, Konrad Dybcio, Qiang Yu
If the bootloader has already powered up the PCIe PHY, performing a
full reset and waiting for the PHY to come up again adds unnecessary
delay during boot.
Extend the existing skip_init handling by introducing a skip_reset
condition. When skip_init is active and the PHY status indicates that
the PHY is already operational, skip asserting and deasserting the
no-csr reset while still enabling the required resources during
power-on.
This allows reusing the bootloader-initialized PHY state and avoids
redundant PHY reinitialization and PCIe link retraining, which can
add hundred's of milliseconds of delay.
This relies on the assumption that when skip_init is enabled and the
PHY is reported as up, the bootloader has already configured the PHY
correctly and the link is in a usable state.
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-qmp-pcie.c | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c
index d3effad7a074..49e10151cb1e 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c
@@ -4862,6 +4862,7 @@ static int qmp_pcie_init(struct phy *phy)
struct qmp_pcie *qmp = phy_get_drvdata(phy);
const struct qmp_phy_cfg *cfg = qmp->cfg;
void __iomem *pcs = qmp->pcs;
+ bool skip_reset;
int ret;
/*
@@ -4877,6 +4878,9 @@ static int qmp_pcie_init(struct phy *phy)
qphy_checkbits(pcs, cfg->regs[QPHY_START_CTRL], SERDES_START | PCS_START) &&
qphy_checkbits(pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], cfg->pwrdn_ctrl);
+ skip_reset = qmp->skip_init && !qphy_checkbits(pcs, cfg->regs[QPHY_PCS_STATUS],
+ cfg->phy_status);
+
if (!qmp->skip_init && !cfg->tbls.serdes_num) {
dev_err(qmp->dev, "Init sequence not available\n");
return -ENODATA;
@@ -4900,13 +4904,15 @@ static int qmp_pcie_init(struct phy *phy)
}
}
- ret = reset_control_assert(qmp->nocsr_reset);
- if (ret) {
- dev_err(qmp->dev, "no-csr reset assert failed\n");
- goto err_assert_reset;
- }
+ if (!skip_reset) {
+ ret = reset_control_assert(qmp->nocsr_reset);
+ if (ret) {
+ dev_err(qmp->dev, "no-csr reset assert failed\n");
+ goto err_assert_reset;
+ }
- usleep_range(200, 300);
+ usleep_range(200, 300);
+ }
if (!qmp->skip_init) {
ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets);
@@ -4956,8 +4962,11 @@ static int qmp_pcie_power_on(struct phy *phy)
void __iomem *pcs = qmp->pcs;
void __iomem *status;
unsigned int mask, val;
+ bool skip_reset;
int ret;
+ skip_reset = qmp->skip_init && !qphy_checkbits(pcs, cfg->regs[QPHY_PCS_STATUS],
+ cfg->phy_status);
/*
* Write CSR register for PHY that doesn't support no_csr reset or has not
* been initialized.
@@ -4981,10 +4990,12 @@ static int qmp_pcie_power_on(struct phy *phy)
if (ret)
return ret;
- ret = reset_control_deassert(qmp->nocsr_reset);
- if (ret) {
- dev_err(qmp->dev, "no-csr reset deassert failed\n");
- goto err_disable_pipe_clk;
+ if (!skip_reset) {
+ ret = reset_control_deassert(qmp->nocsr_reset);
+ if (ret) {
+ dev_err(qmp->dev, "no-csr reset deassert failed\n");
+ goto err_disable_pipe_clk;
+ }
}
if (qmp->skip_init)
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v3 2/4] PCI: qcom: Keep PERST# GPIO state as-is during probe
2026-07-09 6:35 [PATCH v3 0/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
2026-07-09 6:35 ` [PATCH v3 1/4] phy: qcom: qmp-pcie: Skip PHY reset if already up Krishna Chaitanya Chundru
@ 2026-07-09 6:35 ` Krishna Chaitanya Chundru
2026-07-09 6:35 ` [PATCH v3 3/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
` (2 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-07-09 6:35 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Philipp Zabel, Jingoo Han,
Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas
Cc: linux-arm-msm, linux-phy, linux-kernel, linux-pci,
Krishna Chaitanya Chundru, Qiang Yu
The PERST# signal is used to reset PCIe devices. Requesting the GPIO with
GPIOD_OUT_HIGH forces the line high during probe, which can unintentionally
assert reset on devices already out of reset and break proper link
sequencing.
Change the request to use GPIOD_ASIS so the driver preserves the existing
GPIO state configured by the bootloader or firmware. This allows platforms
that manage PERST# externally to maintain correct reset sequencing. PERST#
is asserted explicitly later during qcom_pcie_host_init(), so forcing it
high at probe time is unnecessary.
Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index d8eb52857f69..9ca620d4746a 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -1841,7 +1841,7 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
goto parse_child_node;
reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(np), "reset",
- GPIOD_OUT_HIGH, "PERST#");
+ GPIOD_ASIS, "PERST#");
if (IS_ERR(reset)) {
/*
* FIXME: GPIOLIB currently supports exclusive GPIO access only.
@@ -1918,7 +1918,7 @@ static int qcom_pcie_parse_ports(struct qcom_pcie *pcie)
if (of_find_property(dev->of_node, "perst-gpios", NULL)) {
pcie->reset = devm_gpiod_get_optional(dev, "perst",
- GPIOD_OUT_HIGH);
+ GPIOD_ASIS);
if (IS_ERR(pcie->reset))
return PTR_ERR(pcie->reset);
}
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v3 3/4] PCI: qcom: Add link retention support
2026-07-09 6:35 [PATCH v3 0/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
2026-07-09 6:35 ` [PATCH v3 1/4] phy: qcom: qmp-pcie: Skip PHY reset if already up Krishna Chaitanya Chundru
2026-07-09 6:35 ` [PATCH v3 2/4] PCI: qcom: Keep PERST# GPIO state as-is during probe Krishna Chaitanya Chundru
@ 2026-07-09 6:35 ` Krishna Chaitanya Chundru
2026-07-13 4:50 ` Manivannan Sadhasivam
2026-07-09 6:35 ` [PATCH v3 4/4] PCI: qcom: enable Link retain logic for Hamoa Krishna Chaitanya Chundru
2026-07-20 11:43 ` (subset) [PATCH v3 0/4] PCI: qcom: Add link retention support Vinod Koul
4 siblings, 1 reply; 10+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-07-09 6:35 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Philipp Zabel, Jingoo Han,
Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas
Cc: linux-arm-msm, linux-phy, linux-kernel, linux-pci,
Krishna Chaitanya Chundru, Qiang Yu
Some platforms keep the PCIe link active across bootloader and kernel
handoff. Reinitializing the controller and toggling PERST# in such cases is
unnecessary when the driver does not need to retrain the link.
Introduce link_retain in both qcom_pcie_cfg and qcom_pcie to indicate when
link retention is supported. During initialization, check the LTSSM state;
if the link is already in L0 or L1 idle and LTSSM is enabled, set
link_retain and skip controller reset, PERST# toggling, and other post-
init steps.
The max-link-speed and num-lanes properties in Device Tree cap the link's
maximum capability. Retrain the link if the controller's max link speed or
width exceeds the Device Tree cap, unless the currently negotiated speed
or width is already within that cap, in which case the existing link can
still be retained.
Configure the DBI and ATU base addresses in the retention path, since the
bootloader may use different base addresses than those provided by the
device tree.
Set l1ss_support in the retention path as well, since it is otherwise only
set later in the normal init flow which is skipped when the link is
retained.
Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-designware.h | 1 +
drivers/pci/controller/dwc/pcie-qcom.c | 68 ++++++++++++++++++++++++++--
2 files changed, 66 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index de4b245b1758..0c98b914eb52 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -471,6 +471,7 @@ struct dw_pcie_rp {
bool native_ecam;
bool skip_l23_ready;
bool skip_pwrctrl_off;
+ bool link_retain;
};
struct dw_pcie_ep_ops {
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 9ca620d4746a..77d2d989e9da 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -260,12 +260,14 @@ struct qcom_pcie_ops {
* @override_no_snoop: Override NO_SNOOP attribute in TLP to enable cache
* snooping
* @firmware_managed: Set if the Root Complex is firmware managed
+ * @link_retain: Set if controller supports retaining link from bootloader
*/
struct qcom_pcie_cfg {
const struct qcom_pcie_ops *ops;
bool override_no_snoop;
bool firmware_managed;
bool no_l0s;
+ bool link_retain;
};
struct qcom_pcie_perst {
@@ -996,6 +998,50 @@ static int qcom_pcie_get_resources_2_7_0(struct qcom_pcie *pcie)
return 0;
}
+/*
+ * Determine whether the link established by the bootloader can be reused.
+ *
+ * The max-link-speed and num-lanes specified in Device Tree are meant to cap
+ * the link's maximum capability. Retrain the link if the controller's max
+ * link speed/width exceeds the Device Tree cap, unless the currently
+ * negotiated speed/width is already within that cap, in which case the
+ * existing link can be retained as-is.
+ */
+static bool qcom_pcie_check_link_retain(struct qcom_pcie *pcie)
+{
+ u32 cap, speed, cur_speed, val, ltssm, width, cur_width;
+ struct dw_pcie *pci = pcie->pci;
+ u8 offset;
+
+ val = readl(pcie->parf + PARF_LTSSM);
+ ltssm = val & 0x1f;
+ if ((val & LTSSM_EN) &&
+ (ltssm == DW_PCIE_LTSSM_L0 || ltssm == DW_PCIE_LTSSM_L1_IDLE)) {
+ qcom_pcie_configure_dbi_atu_base(pcie);
+
+ offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ cap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP);
+ speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, cap);
+ width = dw_pcie_link_get_max_link_width(pci);
+
+ val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
+ cur_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, val);
+ cur_width = FIELD_GET(PCI_EXP_LNKSTA_NLW, val);
+
+ if (pci->max_link_speed > 0 && speed > pci->max_link_speed &&
+ cur_speed > pci->max_link_speed)
+ return false;
+
+ if (pci->num_lanes > 0 && width > pci->num_lanes &&
+ cur_width > pci->num_lanes)
+ return false;
+
+ return true;
+ }
+
+ return false;
+}
+
static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0;
@@ -1014,6 +1060,15 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
if (ret < 0)
goto err_disable_regulators;
+ if (pcie->cfg->link_retain) {
+ pci->pp.link_retain = qcom_pcie_check_link_retain(pcie);
+ pci->l1ss_support = true;
+ if (pci->pp.link_retain) {
+ dev_info(dev, "Retaining PCIe link\n");
+ return 0;
+ }
+ }
+
ret = reset_control_assert(res->rst);
if (ret) {
dev_err(dev, "reset assert failed (%d)\n", ret);
@@ -1074,6 +1129,9 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie)
{
const struct qcom_pcie_cfg *pcie_cfg = pcie->cfg;
+ if (pcie->pci->pp.link_retain)
+ return 0;
+
if (pcie_cfg->override_no_snoop)
writel(WR_NO_SNOOP_OVERRIDE_EN | RD_NO_SNOOP_OVERRIDE_EN,
pcie->parf + PARF_NO_SNOOP_OVERRIDE);
@@ -1364,12 +1422,13 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
struct qcom_pcie *pcie = to_qcom_pcie(pci);
int ret;
- qcom_pcie_perst_assert(pcie);
-
ret = pcie->cfg->ops->init(pcie);
if (ret)
return ret;
+ if (!pp->link_retain)
+ qcom_pcie_perst_assert(pcie);
+
ret = qcom_pcie_phy_power_on(pcie);
if (ret)
goto err_deinit;
@@ -1398,7 +1457,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
qcom_pcie_configure_ports(pcie);
- qcom_pcie_perst_deassert(pcie);
+ if (!pp->link_retain)
+ qcom_pcie_perst_deassert(pcie);
if (pcie->cfg->ops->config_sid) {
ret = pcie->cfg->ops->config_sid(pcie);
@@ -1414,6 +1474,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
if (!pp->skip_pwrctrl_off)
pci_pwrctrl_power_off_devices(pci->dev);
err_pwrctrl_destroy:
+ if (pp->link_retain && ret == -EPROBE_DEFER)
+ return ret;
if (ret != -EPROBE_DEFER && !pci->suspended)
pci_pwrctrl_destroy_devices(pci->dev);
err_disable_phy:
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v3 3/4] PCI: qcom: Add link retention support
2026-07-09 6:35 ` [PATCH v3 3/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
@ 2026-07-13 4:50 ` Manivannan Sadhasivam
2026-07-29 4:07 ` Krishna Chaitanya Chundru
0 siblings, 1 reply; 10+ messages in thread
From: Manivannan Sadhasivam @ 2026-07-13 4:50 UTC (permalink / raw)
To: Krishna Chaitanya Chundru
Cc: Vinod Koul, Neil Armstrong, Philipp Zabel, Jingoo Han,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, linux-arm-msm, linux-phy, linux-kernel, linux-pci,
Qiang Yu
On Thu, Jul 09, 2026 at 12:05:18PM +0530, Krishna Chaitanya Chundru wrote:
> Some platforms keep the PCIe link active across bootloader and kernel
> handoff. Reinitializing the controller and toggling PERST# in such cases is
> unnecessary when the driver does not need to retrain the link.
>
> Introduce link_retain in both qcom_pcie_cfg and qcom_pcie to indicate when
> link retention is supported. During initialization, check the LTSSM state;
> if the link is already in L0 or L1 idle and LTSSM is enabled, set
> link_retain and skip controller reset, PERST# toggling, and other post-
> init steps.
>
> The max-link-speed and num-lanes properties in Device Tree cap the link's
> maximum capability. Retrain the link if the controller's max link speed or
> width exceeds the Device Tree cap, unless the currently negotiated speed
> or width is already within that cap, in which case the existing link can
> still be retained.
>
> Configure the DBI and ATU base addresses in the retention path, since the
> bootloader may use different base addresses than those provided by the
> device tree.
>
Where is this base address change?
> Set l1ss_support in the retention path as well, since it is otherwise only
> set later in the normal init flow which is skipped when the link is
> retained.
>
> Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
> Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
> ---
> drivers/pci/controller/dwc/pcie-designware.h | 1 +
> drivers/pci/controller/dwc/pcie-qcom.c | 68 ++++++++++++++++++++++++++--
> 2 files changed, 66 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> index de4b245b1758..0c98b914eb52 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.h
> +++ b/drivers/pci/controller/dwc/pcie-designware.h
> @@ -471,6 +471,7 @@ struct dw_pcie_rp {
> bool native_ecam;
> bool skip_l23_ready;
> bool skip_pwrctrl_off;
> + bool link_retain;
> };
>
> struct dw_pcie_ep_ops {
> diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
> index 9ca620d4746a..77d2d989e9da 100644
> --- a/drivers/pci/controller/dwc/pcie-qcom.c
> +++ b/drivers/pci/controller/dwc/pcie-qcom.c
> @@ -260,12 +260,14 @@ struct qcom_pcie_ops {
> * @override_no_snoop: Override NO_SNOOP attribute in TLP to enable cache
> * snooping
> * @firmware_managed: Set if the Root Complex is firmware managed
> + * @link_retain: Set if controller supports retaining link from bootloader
> */
> struct qcom_pcie_cfg {
> const struct qcom_pcie_ops *ops;
> bool override_no_snoop;
> bool firmware_managed;
> bool no_l0s;
> + bool link_retain;
> };
>
> struct qcom_pcie_perst {
> @@ -996,6 +998,50 @@ static int qcom_pcie_get_resources_2_7_0(struct qcom_pcie *pcie)
> return 0;
> }
>
> +/*
> + * Determine whether the link established by the bootloader can be reused.
> + *
> + * The max-link-speed and num-lanes specified in Device Tree are meant to cap
> + * the link's maximum capability. Retrain the link if the controller's max
> + * link speed/width exceeds the Device Tree cap, unless the currently
> + * negotiated speed/width is already within that cap, in which case the
> + * existing link can be retained as-is.
> + */
> +static bool qcom_pcie_check_link_retain(struct qcom_pcie *pcie)
> +{
> + u32 cap, speed, cur_speed, val, ltssm, width, cur_width;
> + struct dw_pcie *pci = pcie->pci;
> + u8 offset;
> +
> + val = readl(pcie->parf + PARF_LTSSM);
> + ltssm = val & 0x1f;
> + if ((val & LTSSM_EN) &&
> + (ltssm == DW_PCIE_LTSSM_L0 || ltssm == DW_PCIE_LTSSM_L1_IDLE)) {
> + qcom_pcie_configure_dbi_atu_base(pcie);
> +
> + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> + cap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP);
> + speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, cap);
> + width = dw_pcie_link_get_max_link_width(pci);
> +
> + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> + cur_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, val);
> + cur_width = FIELD_GET(PCI_EXP_LNKSTA_NLW, val);
> +
> + if (pci->max_link_speed > 0 && speed > pci->max_link_speed &&
> + cur_speed > pci->max_link_speed)
> + return false;
> +
> + if (pci->num_lanes > 0 && width > pci->num_lanes &&
> + cur_width > pci->num_lanes)
> + return false;
> +
> + return true;
> + }
> +
> + return false;
> +}
> +
> static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
> {
> struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0;
> @@ -1014,6 +1060,15 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
> if (ret < 0)
> goto err_disable_regulators;
>
> + if (pcie->cfg->link_retain) {
> + pci->pp.link_retain = qcom_pcie_check_link_retain(pcie);
> + pci->l1ss_support = true;
> + if (pci->pp.link_retain) {
> + dev_info(dev, "Retaining PCIe link\n");
> + return 0;
> + }
> + }
> +
> ret = reset_control_assert(res->rst);
> if (ret) {
> dev_err(dev, "reset assert failed (%d)\n", ret);
> @@ -1074,6 +1129,9 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie)
> {
> const struct qcom_pcie_cfg *pcie_cfg = pcie->cfg;
>
> + if (pcie->pci->pp.link_retain)
> + return 0;
> +
> if (pcie_cfg->override_no_snoop)
> writel(WR_NO_SNOOP_OVERRIDE_EN | RD_NO_SNOOP_OVERRIDE_EN,
> pcie->parf + PARF_NO_SNOOP_OVERRIDE);
> @@ -1364,12 +1422,13 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
> struct qcom_pcie *pcie = to_qcom_pcie(pci);
> int ret;
>
> - qcom_pcie_perst_assert(pcie);
> -
This is problematic. Most of the init callbacks do operations like reset the
PHY, enable clocks... If those operations affect the link state and the device
is not reset using PERST#, it will cause the device to crash.
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v3 3/4] PCI: qcom: Add link retention support
2026-07-13 4:50 ` Manivannan Sadhasivam
@ 2026-07-29 4:07 ` Krishna Chaitanya Chundru
2026-07-29 8:35 ` Manivannan Sadhasivam
0 siblings, 1 reply; 10+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-07-29 4:07 UTC (permalink / raw)
To: Manivannan Sadhasivam
Cc: Vinod Koul, Neil Armstrong, Philipp Zabel, Jingoo Han,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, linux-arm-msm, linux-phy, linux-kernel, linux-pci,
Qiang Yu
On 7/13/2026 10:20 AM, Manivannan Sadhasivam wrote:
> On Thu, Jul 09, 2026 at 12:05:18PM +0530, Krishna Chaitanya Chundru wrote:
>> Some platforms keep the PCIe link active across bootloader and kernel
>> handoff. Reinitializing the controller and toggling PERST# in such cases is
>> unnecessary when the driver does not need to retrain the link.
>>
>> Introduce link_retain in both qcom_pcie_cfg and qcom_pcie to indicate when
>> link retention is supported. During initialization, check the LTSSM state;
>> if the link is already in L0 or L1 idle and LTSSM is enabled, set
>> link_retain and skip controller reset, PERST# toggling, and other post-
>> init steps.
>>
>> The max-link-speed and num-lanes properties in Device Tree cap the link's
>> maximum capability. Retrain the link if the controller's max link speed or
>> width exceeds the Device Tree cap, unless the currently negotiated speed
>> or width is already within that cap, in which case the existing link can
>> still be retained.
>>
>> Configure the DBI and ATU base addresses in the retention path, since the
>> bootloader may use different base addresses than those provided by the
>> device tree.
>>
> Where is this base address change?
It is being covered as part of dw_pcie_setup_rc(), this part can be removed
as this being called unconditionally in this patch.
>> Set l1ss_support in the retention path as well, since it is otherwise only
>> set later in the normal init flow which is skipped when the link is
>> retained.
>>
>> Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
>> Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
>> ---
>> drivers/pci/controller/dwc/pcie-designware.h | 1 +
>> drivers/pci/controller/dwc/pcie-qcom.c | 68 ++++++++++++++++++++++++++--
>> 2 files changed, 66 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
>> index de4b245b1758..0c98b914eb52 100644
>> --- a/drivers/pci/controller/dwc/pcie-designware.h
>> +++ b/drivers/pci/controller/dwc/pcie-designware.h
>> @@ -471,6 +471,7 @@ struct dw_pcie_rp {
>> bool native_ecam;
>> bool skip_l23_ready;
>> bool skip_pwrctrl_off;
>> + bool link_retain;
>> };
>>
>> struct dw_pcie_ep_ops {
>> diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
>> index 9ca620d4746a..77d2d989e9da 100644
>> --- a/drivers/pci/controller/dwc/pcie-qcom.c
>> +++ b/drivers/pci/controller/dwc/pcie-qcom.c
>> @@ -260,12 +260,14 @@ struct qcom_pcie_ops {
>> * @override_no_snoop: Override NO_SNOOP attribute in TLP to enable cache
>> * snooping
>> * @firmware_managed: Set if the Root Complex is firmware managed
>> + * @link_retain: Set if controller supports retaining link from bootloader
>> */
>> struct qcom_pcie_cfg {
>> const struct qcom_pcie_ops *ops;
>> bool override_no_snoop;
>> bool firmware_managed;
>> bool no_l0s;
>> + bool link_retain;
>> };
>>
>> struct qcom_pcie_perst {
>> @@ -996,6 +998,50 @@ static int qcom_pcie_get_resources_2_7_0(struct qcom_pcie *pcie)
>> return 0;
>> }
>>
>> +/*
>> + * Determine whether the link established by the bootloader can be reused.
>> + *
>> + * The max-link-speed and num-lanes specified in Device Tree are meant to cap
>> + * the link's maximum capability. Retrain the link if the controller's max
>> + * link speed/width exceeds the Device Tree cap, unless the currently
>> + * negotiated speed/width is already within that cap, in which case the
>> + * existing link can be retained as-is.
>> + */
>> +static bool qcom_pcie_check_link_retain(struct qcom_pcie *pcie)
>> +{
>> + u32 cap, speed, cur_speed, val, ltssm, width, cur_width;
>> + struct dw_pcie *pci = pcie->pci;
>> + u8 offset;
>> +
>> + val = readl(pcie->parf + PARF_LTSSM);
>> + ltssm = val & 0x1f;
>> + if ((val & LTSSM_EN) &&
>> + (ltssm == DW_PCIE_LTSSM_L0 || ltssm == DW_PCIE_LTSSM_L1_IDLE)) {
>> + qcom_pcie_configure_dbi_atu_base(pcie);
>> +
>> + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
>> + cap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP);
>> + speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, cap);
>> + width = dw_pcie_link_get_max_link_width(pci);
>> +
>> + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
>> + cur_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, val);
>> + cur_width = FIELD_GET(PCI_EXP_LNKSTA_NLW, val);
>> +
>> + if (pci->max_link_speed > 0 && speed > pci->max_link_speed &&
>> + cur_speed > pci->max_link_speed)
>> + return false;
>> +
>> + if (pci->num_lanes > 0 && width > pci->num_lanes &&
>> + cur_width > pci->num_lanes)
>> + return false;
>> +
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
>> {
>> struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0;
>> @@ -1014,6 +1060,15 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
>> if (ret < 0)
>> goto err_disable_regulators;
>>
>> + if (pcie->cfg->link_retain) {
>> + pci->pp.link_retain = qcom_pcie_check_link_retain(pcie);
>> + pci->l1ss_support = true;
>> + if (pci->pp.link_retain) {
>> + dev_info(dev, "Retaining PCIe link\n");
>> + return 0;
>> + }
>> + }
>> +
>> ret = reset_control_assert(res->rst);
>> if (ret) {
>> dev_err(dev, "reset assert failed (%d)\n", ret);
>> @@ -1074,6 +1129,9 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie)
>> {
>> const struct qcom_pcie_cfg *pcie_cfg = pcie->cfg;
>>
>> + if (pcie->pci->pp.link_retain)
>> + return 0;
>> +
>> if (pcie_cfg->override_no_snoop)
>> writel(WR_NO_SNOOP_OVERRIDE_EN | RD_NO_SNOOP_OVERRIDE_EN,
>> pcie->parf + PARF_NO_SNOOP_OVERRIDE);
>> @@ -1364,12 +1422,13 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
>> struct qcom_pcie *pcie = to_qcom_pcie(pci);
>> int ret;
>>
>> - qcom_pcie_perst_assert(pcie);
>> -
> This is problematic. Most of the init callbacks do operations like reset the
> PHY, enable clocks... If those operations affect the link state and the device
> is not reset using PERST#, it will cause the device to crash.
PERST# is only for endpoint, not for internal controller so it will not cause
any crash.
More over the perst default state can be set be assert through pincntrl if you
still
suspect this can cause some issue on the pcie controller side.
- Krishna Chaitanya.
> - Mani
>
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v3 3/4] PCI: qcom: Add link retention support
2026-07-29 4:07 ` Krishna Chaitanya Chundru
@ 2026-07-29 8:35 ` Manivannan Sadhasivam
0 siblings, 0 replies; 10+ messages in thread
From: Manivannan Sadhasivam @ 2026-07-29 8:35 UTC (permalink / raw)
To: Krishna Chaitanya Chundru
Cc: Vinod Koul, Neil Armstrong, Philipp Zabel, Jingoo Han,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, linux-arm-msm, linux-phy, linux-kernel, linux-pci,
Qiang Yu
On Wed, Jul 29, 2026 at 09:37:47AM +0530, Krishna Chaitanya Chundru wrote:
>
>
> On 7/13/2026 10:20 AM, Manivannan Sadhasivam wrote:
> > On Thu, Jul 09, 2026 at 12:05:18PM +0530, Krishna Chaitanya Chundru wrote:
> >> Some platforms keep the PCIe link active across bootloader and kernel
> >> handoff. Reinitializing the controller and toggling PERST# in such cases is
> >> unnecessary when the driver does not need to retrain the link.
> >>
> >> Introduce link_retain in both qcom_pcie_cfg and qcom_pcie to indicate when
> >> link retention is supported. During initialization, check the LTSSM state;
> >> if the link is already in L0 or L1 idle and LTSSM is enabled, set
> >> link_retain and skip controller reset, PERST# toggling, and other post-
> >> init steps.
> >>
> >> The max-link-speed and num-lanes properties in Device Tree cap the link's
> >> maximum capability. Retrain the link if the controller's max link speed or
> >> width exceeds the Device Tree cap, unless the currently negotiated speed
> >> or width is already within that cap, in which case the existing link can
> >> still be retained.
> >>
> >> Configure the DBI and ATU base addresses in the retention path, since the
> >> bootloader may use different base addresses than those provided by the
> >> device tree.
> >>
> > Where is this base address change?
> It is being covered as part of dw_pcie_setup_rc(), this part can be removed
> as this being called unconditionally in this patch.
> >> Set l1ss_support in the retention path as well, since it is otherwise only
> >> set later in the normal init flow which is skipped when the link is
> >> retained.
> >>
> >> Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
> >> Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
> >> ---
> >> drivers/pci/controller/dwc/pcie-designware.h | 1 +
> >> drivers/pci/controller/dwc/pcie-qcom.c | 68 ++++++++++++++++++++++++++--
> >> 2 files changed, 66 insertions(+), 3 deletions(-)
> >>
> >> diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> >> index de4b245b1758..0c98b914eb52 100644
> >> --- a/drivers/pci/controller/dwc/pcie-designware.h
> >> +++ b/drivers/pci/controller/dwc/pcie-designware.h
> >> @@ -471,6 +471,7 @@ struct dw_pcie_rp {
> >> bool native_ecam;
> >> bool skip_l23_ready;
> >> bool skip_pwrctrl_off;
> >> + bool link_retain;
> >> };
> >>
> >> struct dw_pcie_ep_ops {
> >> diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
> >> index 9ca620d4746a..77d2d989e9da 100644
> >> --- a/drivers/pci/controller/dwc/pcie-qcom.c
> >> +++ b/drivers/pci/controller/dwc/pcie-qcom.c
> >> @@ -260,12 +260,14 @@ struct qcom_pcie_ops {
> >> * @override_no_snoop: Override NO_SNOOP attribute in TLP to enable cache
> >> * snooping
> >> * @firmware_managed: Set if the Root Complex is firmware managed
> >> + * @link_retain: Set if controller supports retaining link from bootloader
> >> */
> >> struct qcom_pcie_cfg {
> >> const struct qcom_pcie_ops *ops;
> >> bool override_no_snoop;
> >> bool firmware_managed;
> >> bool no_l0s;
> >> + bool link_retain;
> >> };
> >>
> >> struct qcom_pcie_perst {
> >> @@ -996,6 +998,50 @@ static int qcom_pcie_get_resources_2_7_0(struct qcom_pcie *pcie)
> >> return 0;
> >> }
> >>
> >> +/*
> >> + * Determine whether the link established by the bootloader can be reused.
> >> + *
> >> + * The max-link-speed and num-lanes specified in Device Tree are meant to cap
> >> + * the link's maximum capability. Retrain the link if the controller's max
> >> + * link speed/width exceeds the Device Tree cap, unless the currently
> >> + * negotiated speed/width is already within that cap, in which case the
> >> + * existing link can be retained as-is.
> >> + */
> >> +static bool qcom_pcie_check_link_retain(struct qcom_pcie *pcie)
> >> +{
> >> + u32 cap, speed, cur_speed, val, ltssm, width, cur_width;
> >> + struct dw_pcie *pci = pcie->pci;
> >> + u8 offset;
> >> +
> >> + val = readl(pcie->parf + PARF_LTSSM);
> >> + ltssm = val & 0x1f;
> >> + if ((val & LTSSM_EN) &&
> >> + (ltssm == DW_PCIE_LTSSM_L0 || ltssm == DW_PCIE_LTSSM_L1_IDLE)) {
> >> + qcom_pcie_configure_dbi_atu_base(pcie);
> >> +
> >> + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> >> + cap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP);
> >> + speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, cap);
> >> + width = dw_pcie_link_get_max_link_width(pci);
> >> +
> >> + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> >> + cur_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, val);
> >> + cur_width = FIELD_GET(PCI_EXP_LNKSTA_NLW, val);
> >> +
> >> + if (pci->max_link_speed > 0 && speed > pci->max_link_speed &&
> >> + cur_speed > pci->max_link_speed)
> >> + return false;
> >> +
> >> + if (pci->num_lanes > 0 && width > pci->num_lanes &&
> >> + cur_width > pci->num_lanes)
> >> + return false;
> >> +
> >> + return true;
> >> + }
> >> +
> >> + return false;
> >> +}
> >> +
> >> static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
> >> {
> >> struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0;
> >> @@ -1014,6 +1060,15 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
> >> if (ret < 0)
> >> goto err_disable_regulators;
> >>
> >> + if (pcie->cfg->link_retain) {
> >> + pci->pp.link_retain = qcom_pcie_check_link_retain(pcie);
> >> + pci->l1ss_support = true;
> >> + if (pci->pp.link_retain) {
> >> + dev_info(dev, "Retaining PCIe link\n");
> >> + return 0;
> >> + }
> >> + }
> >> +
> >> ret = reset_control_assert(res->rst);
> >> if (ret) {
> >> dev_err(dev, "reset assert failed (%d)\n", ret);
> >> @@ -1074,6 +1129,9 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie)
> >> {
> >> const struct qcom_pcie_cfg *pcie_cfg = pcie->cfg;
> >>
> >> + if (pcie->pci->pp.link_retain)
> >> + return 0;
> >> +
> >> if (pcie_cfg->override_no_snoop)
> >> writel(WR_NO_SNOOP_OVERRIDE_EN | RD_NO_SNOOP_OVERRIDE_EN,
> >> pcie->parf + PARF_NO_SNOOP_OVERRIDE);
> >> @@ -1364,12 +1422,13 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
> >> struct qcom_pcie *pcie = to_qcom_pcie(pci);
> >> int ret;
> >>
> >> - qcom_pcie_perst_assert(pcie);
> >> -
> > This is problematic. Most of the init callbacks do operations like reset the
> > PHY, enable clocks... If those operations affect the link state and the device
> > is not reset using PERST#, it will cause the device to crash.
> PERST# is only for endpoint, not for internal controller so it will not cause
> any crash.
If those operations affect the PCIe link state and REFCLK (most likely yes),
then it may end up crashing the device.
> More over the perst default state can be set be assert through pincntrl if you
> still
> suspect this can cause some issue on the pcie controller side.
>
'can be set' is not a guarantee.
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 4/4] PCI: qcom: enable Link retain logic for Hamoa
2026-07-09 6:35 [PATCH v3 0/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
` (2 preceding siblings ...)
2026-07-09 6:35 ` [PATCH v3 3/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
@ 2026-07-09 6:35 ` Krishna Chaitanya Chundru
2026-07-09 13:19 ` Konrad Dybcio
2026-07-20 11:43 ` (subset) [PATCH v3 0/4] PCI: qcom: Add link retention support Vinod Koul
4 siblings, 1 reply; 10+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-07-09 6:35 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Philipp Zabel, Jingoo Han,
Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas
Cc: linux-arm-msm, linux-phy, linux-kernel, linux-pci,
Krishna Chaitanya Chundru, Qiang Yu
The Hamoa platform supports keeping the PCIe link active across
bootloader and kernel handoff. To take advantage of this, introduce a
specific configuration (cfg_x1e80100) with link_retain = true and
update the device match table to use it.
Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 77d2d989e9da..0b345a16f4cf 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -1671,6 +1671,12 @@ static const struct qcom_pcie_cfg cfg_sc8280xp = {
.no_l0s = true,
};
+static const struct qcom_pcie_cfg cfg_x1e80100 = {
+ .ops = &ops_1_21_0,
+ .no_l0s = true,
+ .link_retain = true,
+};
+
static const struct qcom_pcie_cfg cfg_fw_managed = {
.firmware_managed = true,
};
@@ -2370,7 +2376,7 @@ static const struct of_device_id qcom_pcie_match[] = {
{ .compatible = "qcom,pcie-sm8450-pcie0", .data = &cfg_1_9_0 },
{ .compatible = "qcom,pcie-sm8450-pcie1", .data = &cfg_1_9_0 },
{ .compatible = "qcom,pcie-sm8550", .data = &cfg_1_9_0 },
- { .compatible = "qcom,pcie-x1e80100", .data = &cfg_sc8280xp },
+ { .compatible = "qcom,pcie-x1e80100", .data = &cfg_x1e80100 },
{ }
};
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v3 4/4] PCI: qcom: enable Link retain logic for Hamoa
2026-07-09 6:35 ` [PATCH v3 4/4] PCI: qcom: enable Link retain logic for Hamoa Krishna Chaitanya Chundru
@ 2026-07-09 13:19 ` Konrad Dybcio
0 siblings, 0 replies; 10+ messages in thread
From: Konrad Dybcio @ 2026-07-09 13:19 UTC (permalink / raw)
To: Krishna Chaitanya Chundru, Vinod Koul, Neil Armstrong,
Philipp Zabel, Jingoo Han, Manivannan Sadhasivam,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas
Cc: linux-arm-msm, linux-phy, linux-kernel, linux-pci, Qiang Yu
On 7/9/26 8:35 AM, Krishna Chaitanya Chundru wrote:
> The Hamoa platform supports keeping the PCIe link active across
> bootloader and kernel handoff. To take advantage of this, introduce a
> specific configuration (cfg_x1e80100) with link_retain = true and
> update the device match table to use it.
>
> Tested-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
> Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
> ---
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: (subset) [PATCH v3 0/4] PCI: qcom: Add link retention support
2026-07-09 6:35 [PATCH v3 0/4] PCI: qcom: Add link retention support Krishna Chaitanya Chundru
` (3 preceding siblings ...)
2026-07-09 6:35 ` [PATCH v3 4/4] PCI: qcom: enable Link retain logic for Hamoa Krishna Chaitanya Chundru
@ 2026-07-20 11:43 ` Vinod Koul
4 siblings, 0 replies; 10+ messages in thread
From: Vinod Koul @ 2026-07-20 11:43 UTC (permalink / raw)
To: Neil Armstrong, Philipp Zabel, Jingoo Han, Manivannan Sadhasivam,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, Krishna Chaitanya Chundru
Cc: linux-arm-msm, linux-phy, linux-kernel, linux-pci, Abel Vesa,
Konrad Dybcio, Qiang Yu
On Thu, 09 Jul 2026 12:05:15 +0530, Krishna Chaitanya Chundru wrote:
> This patch series introduces support for retaining the PCIe link across
> bootloader and kernel handoff on Qualcomm platforms, specifically
> X1E80100. The goal is to reduce boot time and avoid unnecessary link
> reinitialization when the link is already up.
>
> We are not enabling link retantion support for all the targets, as there
> is no guarantee that the bootloader on all targets has initialized the
> PCIe link in max supported speed. So we are enabling for hamoa & glymur
> target only for now based on the config flag.
>
> [...]
Applied, thanks!
[1/4] phy: qcom: qmp-pcie: Skip PHY reset if already up
commit: 910b828b22b7b91054b3bd4be676a017444b0e00
Best regards,
--
~Vinod
^ permalink raw reply [flat|nested] 10+ messages in thread