]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
PCI: qcom: Add D3cold support
authorKrishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
Wed, 29 Apr 2026 06:42:27 +0000 (12:12 +0530)
committerManivannan Sadhasivam <mani@kernel.org>
Thu, 21 May 2026 15:02:57 +0000 (20:32 +0530)
Add support for transitioning PCIe endpoints under host bridge into D3cold
by integrating with the DWC core suspend/resume helpers.

Implement PME_Turn_Off message generation via ELBI_SYS_CTRL and hook it
into the DWC host operations so the controller follows the standard
PME_Turn_Off based power-down sequence before entering D3cold.

When the device is suspended into D3cold, fully tear down interconnect
bandwidth and 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 preserve wakeup capability of the devices and also not to power on the
devices in the init path.

Finally, 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.

Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
[mani: commit log]
Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Link: https://patch.msgid.link/20260429-d3cold-v5-5-89e9735b9df6@oss.qualcomm.com
drivers/pci/controller/dwc/pcie-qcom.c

index 179d26038da8401c97e3a2888163178e306eb7c0..bc5a31efc0d071e1105be27038bfdb1960968aae 100644 (file)
 
 /* ELBI_SYS_CTRL register fields */
 #define ELBI_SYS_CTRL_LT_ENABLE                        BIT(0)
+#define ELBI_SYS_CTRL_PME_TURNOFF_MSG          BIT(4)
 
 /* ELBI_SYS_STTS register fields */
 #define ELBI_SYS_STTS_LTSSM_STATE_MASK         GENMASK(17, 12)
@@ -288,7 +289,6 @@ struct qcom_pcie {
        const struct qcom_pcie_cfg *cfg;
        struct dentry *debugfs;
        struct list_head ports;
-       bool suspended;
        bool use_pm_opp;
 };
 
@@ -1364,13 +1364,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);
@@ -1395,9 +1399,10 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
 err_assert_reset:
        qcom_pcie_perst_assert(pcie);
 err_pwrctrl_power_off:
-       pci_pwrctrl_power_off_devices(pci->dev);
+       if (!pp->skip_pwrctrl_off)
+               pci_pwrctrl_power_off_devices(pci->dev);
 err_pwrctrl_destroy:
-       if (ret != -EPROBE_DEFER)
+       if (ret != -EPROBE_DEFER && !pci->suspended)
                pci_pwrctrl_destroy_devices(pci->dev);
 err_disable_phy:
        qcom_pcie_phy_power_off(pcie);
@@ -1414,11 +1419,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);
 }
@@ -1432,10 +1440,18 @@ static void qcom_pcie_host_post_init(struct dw_pcie_rp *pp)
                pcie->cfg->ops->host_post_init(pcie);
 }
 
+static void qcom_pcie_host_pme_turn_off(struct dw_pcie_rp *pp)
+{
+       struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+
+       writel(ELBI_SYS_CTRL_PME_TURNOFF_MSG, pci->elbi_base + ELBI_SYS_CTRL);
+}
+
 static const struct dw_pcie_host_ops qcom_pcie_dw_ops = {
        .init           = qcom_pcie_host_init,
        .deinit         = qcom_pcie_host_deinit,
        .post_init      = qcom_pcie_host_post_init,
+       .pme_turn_off   = qcom_pcie_host_pme_turn_off,
 };
 
 /* Qcom IP rev.: 2.1.0 Synopsys IP rev.: 4.01a */
@@ -2104,53 +2120,51 @@ static int qcom_pcie_suspend_noirq(struct device *dev)
        if (!pcie)
                return 0;
 
-       /*
-        * Set minimum bandwidth required to keep data path functional during
-        * suspend.
-        */
-       if (pcie->icc_mem) {
-               ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
-               if (ret) {
-                       dev_err(dev,
-                               "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n",
-                               ret);
-                       return ret;
-               }
-       }
+       ret = dw_pcie_suspend_noirq(pcie->pci);
+       if (ret)
+               return ret;
 
-       /*
-        * Turn OFF the resources only for controllers without active PCIe
-        * devices. For controllers with active devices, the resources are kept
-        * ON and the link is expected to be in L0/L1 (sub)states.
-        *
-        * Turning OFF the resources for controllers with active PCIe devices
-        * will trigger access violation during the end of the suspend cycle,
-        * as kernel tries to access the PCIe devices config space for masking
-        * MSIs.
-        *
-        * Also, it is not desirable to put the link into L2/L3 state as that
-        * implies VDD supply will be removed and the devices may go into
-        * powerdown state. This will affect the lifetime of the storage devices
-        * like NVMe.
-        */
-       if (!dw_pcie_link_up(pcie->pci)) {
-               qcom_pcie_host_deinit(&pcie->pci->pp);
-               pcie->suspended = true;
-       }
+       if (pcie->pci->suspended) {
+               ret = icc_disable(pcie->icc_mem);
+               if (ret)
+                       dev_err(dev, "Failed to disable PCIe-MEM interconnect path: %d\n", ret);
 
-       /*
-        * Only disable CPU-PCIe interconnect path if the suspend is non-S2RAM.
-        * Because on some platforms, DBI access can happen very late during the
-        * S2RAM and a non-active CPU-PCIe interconnect path may lead to NoC
-        * error.
-        */
-       if (pm_suspend_target_state != PM_SUSPEND_MEM) {
                ret = icc_disable(pcie->icc_cpu);
                if (ret)
                        dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ret);
 
                if (pcie->use_pm_opp)
                        dev_pm_opp_set_opp(pcie->pci->dev, NULL);
+       } else {
+               /*
+                * Set minimum bandwidth required to keep data path
+                * functional during suspend.
+                */
+               if (pcie->icc_mem) {
+                       ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
+                       if (ret) {
+                               dev_err(dev,
+                                       "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n",
+                                       ret);
+                               return ret;
+                       }
+               }
+
+               /*
+                * Only disable CPU-PCIe interconnect path if the suspend
+                * is non-S2RAM.  On some platforms, DBI access can happen
+                * very late during S2RAM and a non-active CPU-PCIe
+                * interconnect path may lead to NoC error.
+                */
+               if (pm_suspend_target_state != PM_SUSPEND_MEM) {
+                       ret = icc_disable(pcie->icc_cpu);
+                       if (ret)
+                               dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n",
+                                       ret);
+
+                       if (pcie->use_pm_opp)
+                               dev_pm_opp_set_opp(pcie->pci->dev, NULL);
+               }
        }
        return ret;
 }
@@ -2164,7 +2178,7 @@ static int qcom_pcie_resume_noirq(struct device *dev)
        if (!pcie)
                return 0;
 
-       if (pm_suspend_target_state != PM_SUSPEND_MEM) {
+       if (pcie->pci->suspended) {
                if (pcie->use_pm_opp) {
                        ret = qcom_pcie_set_max_opp(dev);
                        if (ret) {
@@ -2178,19 +2192,48 @@ static int qcom_pcie_resume_noirq(struct device *dev)
                        dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret);
                        return ret;
                }
-       }
 
-       if (pcie->suspended) {
-               ret = qcom_pcie_host_init(&pcie->pci->pp);
-               if (ret)
-                       return ret;
+               ret = icc_enable(pcie->icc_mem);
+               if (ret) {
+                       dev_err(dev, "Failed to enable PCIe-MEM interconnect path: %d\n", 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 != -ENODEV && ret != -EIO)
+                       goto disable_icc_mem;
+       } else {
+               if (pm_suspend_target_state != PM_SUSPEND_MEM) {
+                       if (pcie->use_pm_opp) {
+                               ret = qcom_pcie_set_max_opp(dev);
+                               if (ret) {
+                                       dev_err(dev, "Failed to set max OPP: %d\n", ret);
+                                       return ret;
+                               }
+                       }
 
-               pcie->suspended = false;
+                       ret = icc_enable(pcie->icc_cpu);
+                       if (ret) {
+                               dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n",
+                                       ret);
+                               return ret;
+                       }
+               }
        }
 
        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[] = {