* [PATCH v3 1/5] PCI: host-common: Add helper to determine host bridge D3cold eligibility
2026-03-11 10:26 [PATCH v3 0/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
@ 2026-03-11 10:26 ` Krishna Chaitanya Chundru
2026-03-16 5:17 ` Manivannan Sadhasivam
2026-03-11 10:26 ` [PATCH v3 2/5] PCI: dwc: Use common D3cold eligibility helper in suspend path Krishna Chaitanya Chundru
` (3 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-03-11 10:26 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
Add a common helper, pci_host_common_d3cold_possible(), to determine
whether a PCI host bridge can safely transition to D3cold.
This helper is intended to be used by PCI host controller drivers to
decide whether they may safely put the host bridge into D3cold based on
the power state and wakeup capabilities of downstream endpoints.
The helper walks all devices on the bridge's primary bus and only allows
the host bridge to enter D3cold if all PCIe endpoints are already in
PCI_D3hot. This ensures that we do not power off the host bridge while
any active endpoint still requires the link to remain powered.
For devices that may wake the system, the helper additionally requires
that the device supports PME wake from D3cold (via WAKE#). Devices that
do not have wakeup enabled are not restricted by this check and do not
block the host bridge from entering D3cold.
Devices without a bound driver and with PCI not enabled via sysfs are
treated as inactive and therefore do not prevent the host bridge from
entering D3cold. This allows controllers to power down more aggressively
when there are no actively managed endpoints.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/pci-host-common.c | 47 ++++++++++++++++++++++++++++++++
drivers/pci/controller/pci-host-common.h | 2 ++
2 files changed, 49 insertions(+)
diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c
index d6258c1cffe5ec480fd2a7e50b3af39ef6ac4c8c..bff23bcdb5d032d2781d963eebe4a3fac0505517 100644
--- a/drivers/pci/controller/pci-host-common.c
+++ b/drivers/pci/controller/pci-host-common.c
@@ -106,5 +106,52 @@ void pci_host_common_remove(struct platform_device *pdev)
}
EXPORT_SYMBOL_GPL(pci_host_common_remove);
+static int __pci_host_common_d3cold_possible(struct pci_dev *pdev, void *userdata)
+{
+ bool *d3cold_possible = userdata;
+
+ if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT)
+ return 0;
+
+ if (!pdev->dev.driver && !pci_is_enabled(pdev))
+ return 0;
+
+ if (pdev->current_state != PCI_D3hot)
+ goto exit;
+
+ if (device_may_wakeup(&pdev->dev) && !pci_pme_capable(pdev, PCI_D3cold))
+ goto exit;
+
+ return 0;
+
+exit:
+ *d3cold_possible = false;
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * pci_host_common_d3cold_possible - Determine whether a host bridge can enter D3cold
+ * @bridge: PCI host bridge to check
+ *
+ * Walk downstream PCIe endpoint devices and determine whether the host bridge
+ * is permitted to transition to D3cold.
+ *
+ * The host bridge can enter D3cold only if all active PCIe endpoints are in
+ * PCI_D3hot and any wakeup-enabled endpoint is capable of generating PME from
+ * D3cold. Inactive endpoints are ignored.
+ *
+ * Return: %true if the host bridge may enter D3cold, otherwise %false.
+ */
+bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge)
+{
+ bool d3cold_allow = true;
+
+ pci_walk_bus(bridge->bus, __pci_host_common_d3cold_possible, &d3cold_allow);
+
+ return d3cold_allow;
+}
+EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible);
+
MODULE_DESCRIPTION("Common library for PCI host controller drivers");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h
index b5075d4bd7eb31fbf1dc946ef1a6afd5afb5b3c6..c0ef2a2bf3994ce66686b3f8eae25538f3a902b4 100644
--- a/drivers/pci/controller/pci-host-common.h
+++ b/drivers/pci/controller/pci-host-common.h
@@ -20,4 +20,6 @@ void pci_host_common_remove(struct platform_device *pdev);
struct pci_config_window *pci_host_common_ecam_create(struct device *dev,
struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops);
+
+bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge);
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v3 1/5] PCI: host-common: Add helper to determine host bridge D3cold eligibility
2026-03-11 10:26 ` [PATCH v3 1/5] PCI: host-common: Add helper to determine host bridge D3cold eligibility Krishna Chaitanya Chundru
@ 2026-03-16 5:17 ` Manivannan Sadhasivam
0 siblings, 0 replies; 12+ messages in thread
From: Manivannan Sadhasivam @ 2026-03-16 5:17 UTC (permalink / raw)
To: Krishna Chaitanya Chundru
Cc: Jingoo Han, Lorenzo Pieralisi, Krzysztof Wilczyński,
Rob Herring, Bjorn Helgaas, Will Deacon, linux-pci, linux-kernel,
linux-arm-msm, linux-arm-kernel, jonathanh, bjorn.andersson
On Wed, Mar 11, 2026 at 03:56:46PM +0530, Krishna Chaitanya Chundru wrote:
> Add a common helper, pci_host_common_d3cold_possible(), to determine
> whether a PCI host bridge can safely transition to D3cold.
>
Host bridge cannot be transitioned to D3Cold, it can only transition the
devices to D3Cold state. You need to reword it throughout the patch.
> This helper is intended to be used by PCI host controller drivers to
> decide whether they may safely put the host bridge into D3cold based on
> the power state and wakeup capabilities of downstream endpoints.
>
> The helper walks all devices on the bridge's primary bus
What do you mean by 'primary bus' here? The helper is supposed to walk through
all busses starting from the Root bus, no?
> and only allows
> the host bridge to enter D3cold if all PCIe endpoints are already in
> PCI_D3hot. This ensures that we do not power off the host bridge while
> any active endpoint still requires the link to remain powered.
>
> For devices that may wake the system, the helper additionally requires
> that the device supports PME wake from D3cold (via WAKE#). Devices that
> do not have wakeup enabled are not restricted by this check and do not
> block the host bridge from entering D3cold.
>
> Devices without a bound driver and with PCI not enabled via sysfs are
> treated as inactive and therefore do not prevent the host bridge from
> entering D3cold. This allows controllers to power down more aggressively
> when there are no actively managed endpoints.
>
> Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
> ---
> drivers/pci/controller/pci-host-common.c | 47 ++++++++++++++++++++++++++++++++
> drivers/pci/controller/pci-host-common.h | 2 ++
> 2 files changed, 49 insertions(+)
>
> diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c
> index d6258c1cffe5ec480fd2a7e50b3af39ef6ac4c8c..bff23bcdb5d032d2781d963eebe4a3fac0505517 100644
> --- a/drivers/pci/controller/pci-host-common.c
> +++ b/drivers/pci/controller/pci-host-common.c
> @@ -106,5 +106,52 @@ void pci_host_common_remove(struct platform_device *pdev)
> }
> EXPORT_SYMBOL_GPL(pci_host_common_remove);
>
> +static int __pci_host_common_d3cold_possible(struct pci_dev *pdev, void *userdata)
> +{
> + bool *d3cold_possible = userdata;
> +
> + if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT)
> + return 0;
> +
> + if (!pdev->dev.driver && !pci_is_enabled(pdev))
> + return 0;
> +
> + if (pdev->current_state != PCI_D3hot)
> + goto exit;
> +
> + if (device_may_wakeup(&pdev->dev) && !pci_pme_capable(pdev, PCI_D3cold))
> + goto exit;
> +
> + return 0;
> +
> +exit:
> + *d3cold_possible = false;
> +
> + return -EOPNOTSUPP;
> +}
> +
> +/**
> + * pci_host_common_d3cold_possible - Determine whether a host bridge can enter D3cold
"Determine whether the host bridge can transition the devices into D3Cold"
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 2/5] PCI: dwc: Use common D3cold eligibility helper in suspend path
2026-03-11 10:26 [PATCH v3 0/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
2026-03-11 10:26 ` [PATCH v3 1/5] PCI: host-common: Add helper to determine host bridge D3cold eligibility Krishna Chaitanya Chundru
@ 2026-03-11 10:26 ` Krishna Chaitanya Chundru
2026-03-11 10:26 ` [PATCH v3 3/5] PCI: qcom: Add .get_ltssm() helper Krishna Chaitanya Chundru
` (2 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-03-11 10:26 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
Previously, the driver skipped putting the link into L2/device state in
D3cold whenever L1 ASPM was enabled, since some devices (e.g. NVMe) expect
low resume latency and may not tolerate deeper power states. However, such
devices typically remain in D0 and are already covered by the new helper's
requirement that all endpoints be in D3hot before the host bridge may
enter D3cold.
So, replace the local L1/L1SS-based check in dw_pcie_suspend_noirq() with
the shared pci_host_common_d3cold_possible() helper to decide whether the
DesignWare host bridge can safely transition to D3cold.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-designware-host.c | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index 6ae6189e9b8a9021c99ece17504834650debd86b..8e81f0208c76fb22aa908de4e1ff379ec0cae2ff 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -16,9 +16,11 @@
#include <linux/msi.h>
#include <linux/of_address.h>
#include <linux/of_pci.h>
+#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/platform_device.h>
+#include "../pci-host-common.h"
#include "../../pci.h"
#include "pcie-designware.h"
@@ -1218,18 +1220,13 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci)
int dw_pcie_suspend_noirq(struct dw_pcie *pci)
{
- u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
int ret = 0;
u32 val;
if (!dw_pcie_link_up(pci))
goto stop_link;
- /*
- * If L1SS is supported, then do not put the link into L2 as some
- * devices such as NVMe expect low resume latency.
- */
- if (dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM_L1)
+ if (!pci_host_common_d3cold_possible(pci->pp.bridge))
return 0;
if (pci->pp.ops->pme_turn_off) {
--
2.34.1
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v3 3/5] PCI: qcom: Add .get_ltssm() helper
2026-03-11 10:26 [PATCH v3 0/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
2026-03-11 10:26 ` [PATCH v3 1/5] PCI: host-common: Add helper to determine host bridge D3cold eligibility Krishna Chaitanya Chundru
2026-03-11 10:26 ` [PATCH v3 2/5] PCI: dwc: Use common D3cold eligibility helper in suspend path Krishna Chaitanya Chundru
@ 2026-03-11 10:26 ` Krishna Chaitanya Chundru
2026-03-11 10:26 ` [PATCH v3 4/5] PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks Krishna Chaitanya Chundru
2026-03-11 10:26 ` [PATCH v3 5/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
4 siblings, 0 replies; 12+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-03-11 10:26 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
For older targets like sc7280, we see reading DBI after sending PME
turn off message is causing NOC error.
To avoid unsafe DBI accesses, introduce qcom_pcie_get_ltssm(), which
retrieves the LTSSM state from the PARF_LTSSM register instead.
This helper is used in place of direct DBI-based link state checks in
the D3cold path after sending PME turn-off message, ensuring the LTSSM
state can be queried safely even after DBI access is no longer valid.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 67a16af69ddc75fca1b123e70715e692a91a9135..b00bf46637a5ff803a845719c5b0b5b82739244b 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -131,6 +131,7 @@
/* PARF_LTSSM register fields */
#define LTSSM_EN BIT(8)
+#define PARF_LTSSM_STATE_MASK GENMASK(5, 0)
/* PARF_NO_SNOOP_OVERRIDE register fields */
#define WR_NO_SNOOP_OVERRIDE_EN BIT(1)
@@ -1255,6 +1256,16 @@ static bool qcom_pcie_link_up(struct dw_pcie *pci)
return val & PCI_EXP_LNKSTA_DLLLA;
}
+static enum dw_pcie_ltssm qcom_pcie_get_ltssm(struct dw_pcie *pci)
+{
+ struct qcom_pcie *pcie = to_qcom_pcie(pci);
+ u32 val;
+
+ val = readl(pcie->parf + PARF_LTSSM);
+
+ return (enum dw_pcie_ltssm)FIELD_GET(PARF_LTSSM_STATE_MASK, val);
+}
+
static void qcom_pcie_phy_power_off(struct qcom_pcie *pcie)
{
struct qcom_pcie_port *port;
@@ -1507,6 +1518,7 @@ static const struct qcom_pcie_cfg cfg_fw_managed = {
static const struct dw_pcie_ops dw_pcie_ops = {
.link_up = qcom_pcie_link_up,
.start_link = qcom_pcie_start_link,
+ .get_ltssm = qcom_pcie_get_ltssm,
};
static int qcom_pcie_icc_init(struct qcom_pcie *pcie)
--
2.34.1
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v3 4/5] PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks
2026-03-11 10:26 [PATCH v3 0/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
` (2 preceding siblings ...)
2026-03-11 10:26 ` [PATCH v3 3/5] PCI: qcom: Add .get_ltssm() helper Krishna Chaitanya Chundru
@ 2026-03-11 10:26 ` Krishna Chaitanya Chundru
2026-03-11 10:26 ` [PATCH v3 5/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
4 siblings, 0 replies; 12+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-03-11 10:26 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
Some Qcom PCIe controller variants bring the PHY out of test power-down
(PHY_TEST_PWR_DOWN) during init. When the link is later transitioned
towards D3cold and the driver disables PCIe clocks and/or regulators
without explicitly re-asserting PHY_TEST_PWR_DOWN, the PHY can remain
partially powered, leading to avoidable power leakage.
Update the init-path comments to reflect that PARF_PHY_CTRL is used to
power the PHY on. Also, for controller revisions that enable PHY power
in init (2.3.2, 2.3.3, 2.7.0 and 2.9.0), explicitly power the PHY down
via PARF_PHY_CTRL in the deinit path before disabling clocks/regulators.
This ensures the PHY is put into a defined low-power state prior to
removing its supplies, preventing leakage when entering D3cold.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 32 +++++++++++++++++++++++++++++---
1 file changed, 29 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index b00bf46637a5ff803a845719c5b0b5b82739244b..c14c3eb70f356b6ad8a2ffe48b107327d2babf77 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -513,7 +513,7 @@ static int qcom_pcie_post_init_2_1_0(struct qcom_pcie *pcie)
u32 val;
int ret;
- /* enable PCIe clocks and resets */
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -680,6 +680,12 @@ static int qcom_pcie_get_resources_2_3_2(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_3_2(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_3_2 *res = &pcie->res.v2_3_2;
+ u32 val;
+
+ /* Force PHY to lowest power state*/
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
regulator_bulk_disable(ARRAY_SIZE(res->supplies), res->supplies);
@@ -712,7 +718,7 @@ static int qcom_pcie_post_init_2_3_2(struct qcom_pcie *pcie)
{
u32 val;
- /* enable PCIe clocks and resets */
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -844,6 +850,12 @@ static int qcom_pcie_get_resources_2_3_3(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_3_3(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_3_3 *res = &pcie->res.v2_3_3;
+ u32 val;
+
+ /* Force PHY to lowest power state */
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
}
@@ -899,6 +911,7 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie)
u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
u32 val;
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -994,7 +1007,7 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
/* configure PCIe to RC mode */
writel(DEVICE_TYPE_RC, pcie->parf + PARF_DEVICE_TYPE);
- /* enable PCIe clocks and resets */
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -1065,6 +1078,12 @@ static void qcom_pcie_host_post_init_2_7_0(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0;
+ u32 val;
+
+ /* Force PHY to lowest power state */
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
@@ -1169,6 +1188,12 @@ static int qcom_pcie_get_resources_2_9_0(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_9_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0;
+ u32 val;
+
+ /* Force PHY to lowest power state */
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
}
@@ -1209,6 +1234,7 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie)
u32 val;
int i;
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
--
2.34.1
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v3 5/5] PCI: qcom: Add D3cold support
2026-03-11 10:26 [PATCH v3 0/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
` (3 preceding siblings ...)
2026-03-11 10:26 ` [PATCH v3 4/5] PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks Krishna Chaitanya Chundru
@ 2026-03-11 10:26 ` Krishna Chaitanya Chundru
2026-03-17 9:15 ` Konrad Dybcio
4 siblings, 1 reply; 12+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-03-11 10:26 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
Add support for transitioning PCIe endpoints & bridges 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.
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>
---
drivers/pci/controller/dwc/pcie-qcom.c | 121 ++++++++++++++++++++-------------
1 file changed, 74 insertions(+), 47 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index c14c3eb70f356b6ad8a2ffe48b107327d2babf77..6c5e9e541d55ad7f90a2203b97847d15d56b59c3 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -145,6 +145,7 @@
/* ELBI_SYS_CTRL register fields */
#define ELBI_SYS_CTRL_LT_ENABLE BIT(0)
+#define ELBI_SYS_CTRL_PME_TURNOFF_MSG BIT(4)
/* AXI_MSTR_RESP_COMP_CTRL0 register fields */
#define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_2K 0x4
@@ -283,7 +284,6 @@ struct qcom_pcie {
const struct qcom_pcie_cfg *cfg;
struct dentry *debugfs;
struct list_head ports;
- bool suspended;
bool use_pm_opp;
};
@@ -1404,10 +1404,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 */
@@ -2072,53 +2080,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.
+ * 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);
+ }
}
return ret;
}
@@ -2132,25 +2138,46 @@ 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) {
ret = icc_enable(pcie->icc_cpu);
if (ret) {
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;
+ }
- pcie->suspended = false;
+ /*
+ * 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) {
+ 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[] = {
--
2.34.1
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v3 5/5] PCI: qcom: Add D3cold support
2026-03-11 10:26 ` [PATCH v3 5/5] PCI: qcom: Add D3cold support Krishna Chaitanya Chundru
@ 2026-03-17 9:15 ` Konrad Dybcio
2026-04-06 9:08 ` Krishna Chaitanya Chundru
0 siblings, 1 reply; 12+ messages in thread
From: Konrad Dybcio @ 2026-03-17 9:15 UTC (permalink / raw)
To: Krishna Chaitanya Chundru, Jingoo Han, Manivannan Sadhasivam,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson
On 3/11/26 11:26 AM, Krishna Chaitanya Chundru wrote:
> Add support for transitioning PCIe endpoints & bridges 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.
>
> 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>
> ---
[...]
> 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);
Does calling .suspend not drop the vote by itself?
[...]
> @@ -2132,25 +2138,46 @@ static int qcom_pcie_resume_noirq(struct device *dev)
Similarly, I'd expect that .resume restores it (and you didn't do it manually)
Konrad
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 5/5] PCI: qcom: Add D3cold support
2026-03-17 9:15 ` Konrad Dybcio
@ 2026-04-06 9:08 ` Krishna Chaitanya Chundru
2026-04-07 11:36 ` Konrad Dybcio
0 siblings, 1 reply; 12+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-06 9:08 UTC (permalink / raw)
To: Konrad Dybcio, Jingoo Han, Manivannan Sadhasivam,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson
On 3/17/2026 2:45 PM, Konrad Dybcio wrote:
> On 3/11/26 11:26 AM, Krishna Chaitanya Chundru wrote:
>> Add support for transitioning PCIe endpoints & bridges 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.
>>
>> 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>
>> ---
> [...]
>
>> 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);
> Does calling .suspend not drop the vote by itself?
No, unlike genpd framework for power domains, opp votes will not removed
as part of suspend.
- Krishna Chaitanya.
> [...]
>
>> @@ -2132,25 +2138,46 @@ static int qcom_pcie_resume_noirq(struct device *dev)
> Similarly, I'd expect that .resume restores it (and you didn't do it manually)
>
> Konrad
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 5/5] PCI: qcom: Add D3cold support
2026-04-06 9:08 ` Krishna Chaitanya Chundru
@ 2026-04-07 11:36 ` Konrad Dybcio
2026-04-07 11:37 ` Krishna Chaitanya Chundru
0 siblings, 1 reply; 12+ messages in thread
From: Konrad Dybcio @ 2026-04-07 11:36 UTC (permalink / raw)
To: Krishna Chaitanya Chundru, Jingoo Han, Manivannan Sadhasivam,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson
On 4/6/26 11:08 AM, Krishna Chaitanya Chundru wrote:
>
>
> On 3/17/2026 2:45 PM, Konrad Dybcio wrote:
>> On 3/11/26 11:26 AM, Krishna Chaitanya Chundru wrote:
>>> Add support for transitioning PCIe endpoints & bridges 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.
>>>
>>> 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>
>>> ---
>> [...]
>>
>>> 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);
>> Does calling .suspend not drop the vote by itself?
> No, unlike genpd framework for power domains, opp votes will not removed as part of suspend.
Hm, I would imagine the power vote goes down.. is that the ICC vote
that's still hanging if we don't do this?
Konrad
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 5/5] PCI: qcom: Add D3cold support
2026-04-07 11:36 ` Konrad Dybcio
@ 2026-04-07 11:37 ` Krishna Chaitanya Chundru
2026-04-07 12:27 ` Konrad Dybcio
0 siblings, 1 reply; 12+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-04-07 11:37 UTC (permalink / raw)
To: Konrad Dybcio, Jingoo Han, Manivannan Sadhasivam,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson
On 4/7/2026 5:06 PM, Konrad Dybcio wrote:
> On 4/6/26 11:08 AM, Krishna Chaitanya Chundru wrote:
>>
>> On 3/17/2026 2:45 PM, Konrad Dybcio wrote:
>>> On 3/11/26 11:26 AM, Krishna Chaitanya Chundru wrote:
>>>> Add support for transitioning PCIe endpoints & bridges 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.
>>>>
>>>> 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>
>>>> ---
>>> [...]
>>>
>>>> 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);
>>> Does calling .suspend not drop the vote by itself?
>> No, unlike genpd framework for power domains, opp votes will not removed as part of suspend.
> Hm, I would imagine the power vote goes down.. is that the ICC vote
> that's still hanging if we don't do this?
yes, ICC votes are still present
- Krishna Chaitanya.
> Konrad
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 5/5] PCI: qcom: Add D3cold support
2026-04-07 11:37 ` Krishna Chaitanya Chundru
@ 2026-04-07 12:27 ` Konrad Dybcio
0 siblings, 0 replies; 12+ messages in thread
From: Konrad Dybcio @ 2026-04-07 12:27 UTC (permalink / raw)
To: Krishna Chaitanya Chundru, Jingoo Han, Manivannan Sadhasivam,
Lorenzo Pieralisi, Krzysztof Wilczyński, Rob Herring,
Bjorn Helgaas, Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson
On 4/7/26 1:37 PM, Krishna Chaitanya Chundru wrote:
>
>
> On 4/7/2026 5:06 PM, Konrad Dybcio wrote:
>> On 4/6/26 11:08 AM, Krishna Chaitanya Chundru wrote:
>>>
>>> On 3/17/2026 2:45 PM, Konrad Dybcio wrote:
>>>> On 3/11/26 11:26 AM, Krishna Chaitanya Chundru wrote:
>>>>> Add support for transitioning PCIe endpoints & bridges 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.
>>>>>
>>>>> 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>
>>>>> ---
>>>> [...]
>>>>
>>>>> 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);
>>>> Does calling .suspend not drop the vote by itself?
>>> No, unlike genpd framework for power domains, opp votes will not removed as part of suspend.
>> Hm, I would imagine the power vote goes down.. is that the ICC vote
>> that's still hanging if we don't do this?
> yes, ICC votes are still present
OK, thanks for confirming
Konrad
^ permalink raw reply [flat|nested] 12+ messages in thread