From d126cc61d3364534cab2fb8b3f561eb84fb998eb Mon Sep 17 00:00:00 2001 From: Ziyue Zhang Date: Tue, 14 Apr 2026 18:14:13 +0800 Subject: [PATCH] FROMLIST: PCI: qcom: Add D3cold support Add support for transitioning PCIe endpoints under host bridge into D3cold by integrating with the DWC core suspend/resume helpers. Implement PME_TurnOff message generation via ELBI_SYS_CTRL and hook it into the DWC host operations so the controller follows the standard PME_TurnOff-based power-down sequence before entering D3cold. When the device is suspended into D3cold, fully tear down interconnect bandwidth, OPP votes. If D3cold is not entered, retain existing behavior by keeping the required interconnect and OPP votes. Use dw_pcie::skip_pwrctrl_off to avoid powering off devices during suspend to preseve wakeup capability of the devices and also not to power on the devices in the init path. Drop the qcom_pcie::suspended flag and rely on the existing dw_pcie::suspended state, which now drives both the power-management flow and the interconnect/OPP handling. Link: https://lore.kernel.org/all/20260407-d3cold-v4-5-bb171f75b465@oss.qualcomm.com/ Signed-off-by: Krishna Chaitanya Chundru --- .../pci/controller/dwc/pcie-designware-host.c | 3 ++ drivers/pci/controller/dwc/pcie-designware.h | 1 + drivers/pci/controller/dwc/pcie-qcom.c | 46 +++++++++++++------ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 60620cc0dedcf..6bad30e330d4c 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -1157,6 +1157,8 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) if (!pci_host_common_can_enter_d3cold(pci->pp.bridge)) return 0; + pci->pp.skip_pwrctrl_off = true; + if (pci->pp.ops->pme_turn_off) { pci->pp.ops->pme_turn_off(&pci->pp); } else { @@ -1212,6 +1214,7 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci) return 0; pci->suspended = false; + pci->pp.skip_pwrctrl_off = false; if (pci->pp.ops->init) { ret = pci->pp.ops->init(&pci->pp); diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 57ca6b8de55bc..bc34858c0851f 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -436,6 +436,7 @@ struct dw_pcie_rp { bool ecam_enabled; bool native_ecam; bool skip_l23_ready; + bool skip_pwrctrl_off; }; struct dw_pcie_ep_ops { diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 83e973623f55b..143c280c5fc05 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1308,13 +1308,17 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) if (ret) goto err_deinit; - ret = pci_pwrctrl_create_devices(pci->dev); - if (ret) - goto err_disable_phy; + if (!pci->suspended) { + ret = pci_pwrctrl_create_devices(pci->dev); + if (ret) + goto err_disable_phy; + } - ret = pci_pwrctrl_power_on_devices(pci->dev); - if (ret) - goto err_pwrctrl_destroy; + if (!pp->skip_pwrctrl_off) { + ret = pci_pwrctrl_power_on_devices(pci->dev); + if (ret) + goto err_pwrctrl_destroy; + } if (pcie->cfg->ops->post_init) { ret = pcie->cfg->ops->post_init(pcie); @@ -1356,11 +1360,14 @@ static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp) qcom_pcie_perst_assert(pcie); - /* - * No need to destroy pwrctrl devices as this function only gets called - * during system suspend as of now. - */ - pci_pwrctrl_power_off_devices(pci->dev); + if (!pci->pp.skip_pwrctrl_off) { + /* + * No need to destroy pwrctrl devices as this function only gets called + * during system suspend as of now. + */ + pci_pwrctrl_power_off_devices(pci->dev); + } + qcom_pcie_phy_power_off(pcie); pcie->cfg->ops->deinit(pcie); } @@ -2050,11 +2057,16 @@ static int qcom_pcie_resume_noirq(struct device *dev) ret = icc_enable(pcie->icc_mem); if (ret) { dev_err(dev, "Failed to enable PCIe-MEM interconnect path: %d\n", ret); - return ret; + goto disable_icc_cpu; } + + /* + * Ignore -ENODEV & -EIO here since it is expected when no endpoint is + * connected to the PCIe link. + */ ret = dw_pcie_resume_noirq(pcie->pci); - if (ret && (ret != -ETIMEDOUT)) - return ret; + if (ret && ret != -ENODEV && ret != -EIO) + goto disable_icc_mem; } else { if (pm_suspend_target_state != PM_SUSPEND_MEM) { ret = icc_enable(pcie->icc_cpu); @@ -2069,6 +2081,12 @@ static int qcom_pcie_resume_noirq(struct device *dev) qcom_pcie_icc_opp_update(pcie); return 0; +disable_icc_mem: + icc_disable(pcie->icc_mem); +disable_icc_cpu: + icc_disable(pcie->icc_cpu); + + return ret; } static const struct of_device_id qcom_pcie_match[] = {