summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorBjorn Helgaas <bhelgaas@google.com>2026-06-23 17:32:14 -0500
committerBjorn Helgaas <bhelgaas@google.com>2026-06-23 17:32:14 -0500
commit9cd6b3cae021dd8899cd4de7ba26daaad307db03 (patch)
treeaafad3d9f21c4e36f998d1666938fc3cd35255dd /drivers
parentc08c02e749b548afe5b772249d1ddab487c20815 (diff)
parente0779713a1e2f891aeec53e629dbbd33f423c629 (diff)
downloadlwn-9cd6b3cae021dd8899cd4de7ba26daaad307db03.tar.gz
lwn-9cd6b3cae021dd8899cd4de7ba26daaad307db03.zip
Merge branch 'pci/controller/dwc-qcom'
- Set max OPP during resume so DBI register accesses don't fail with NoC errors (Qiang Yu) - Add pci_host_common_d3cold_possible() to determine whether downstream devices are already in D3hot and wakeup-enabled devices are capable of generating PME from D3cold (Krishna Chaitanya Chundru) - Add a .get_ltssm() callback to get the LTSSM status without DBI, since DBI may be inaccessible after PME_Turn_Off (Krishna Chaitanya Chundru) - Power down PHY via PARF_PHY_CTRL before disabling rails/clocks to avoid power leakage (Krishna Chaitanya Chundru) - Decide whether suspend should put the link in L2 and power down using pci_host_common_d3cold_possible() instead of checking whether ASPM L1 is enabled (Krishna Chaitanya Chundru) - Add qcom D3cold support to tear down interconnect bandwidth and OPP votes (Krishna Chaitanya Chundru) - Handle unsupported mixed PERST#/PHY DT configurations, e.g., PHY in RP node while PERST# is in the RC node, but warn about the DT issue (Qiang Yu) - Add pcie_encode_t_power_on() to encode L1SS T_POWER_ON fields (Krishna Chaitanya Chundru) - Add dw_pcie_program_t_power_on() to program T_POWER_ON (Krishna Chaitanya Chundru) - Program qcom T_POWER_ON based on DT 't-power-on-us' property in case hardware advertises incorrect values (Krishna Chaitanya Chundru) - Disable ASPM L0s for SA8775P (Shawn Guo) - Initialize DWC MSI lock for firmware-managed ECAM hosts, which don't use the dw_pcie_host_init() path that initializes the lock (Yadu M G) * pci/controller/dwc-qcom: PCI: qcom: Initialize DWC MSI lock for firmware-managed ECAM hosts PCI: qcom: Disable ASPM L0s for SA8775P PCI: qcom: Program T_POWER_ON PCI: dwc: Add dw_pcie_program_t_power_on() to program T_POWER_ON PCI/ASPM: Add pcie_encode_t_power_on() helper to encode L1SS T_POWER_ON fields PCI: qcom: Handle mixed PERST#/PHY DT configuration PCI: qcom: Add D3cold support PCI: dwc: Use common D3cold eligibility helper in suspend path PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks PCI: qcom: Add .get_ltssm() callback to query LTSSM status PCI: host-common: Add pci_host_common_d3cold_possible() helper PCI: qcom: Set max OPP before DBI access during resume # Conflicts: # drivers/pci/controller/pci-host-common.c
Diffstat (limited to 'drivers')
-rw-r--r--drivers/pci/controller/dwc/pcie-designware-host.c23
-rw-r--r--drivers/pci/controller/dwc/pcie-designware.c28
-rw-r--r--drivers/pci/controller/dwc/pcie-designware.h2
-rw-r--r--drivers/pci/controller/dwc/pcie-qcom.c318
-rw-r--r--drivers/pci/controller/pci-host-common.c63
-rw-r--r--drivers/pci/controller/pci-host-common.h3
-rw-r--r--drivers/pci/pci.h6
-rw-r--r--drivers/pci/pcie/aspm.c40
8 files changed, 390 insertions, 93 deletions
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index c9517a348836..cffb34f6f3a9 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,14 @@ 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);
+ bool pme_capable = false;
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, &pme_capable))
return 0;
if (pci->pp.ops->pme_turn_off) {
@@ -1273,6 +1271,15 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci)
udelay(1);
stop_link:
+ /*
+ * TODO: "pme_capable" means some downstream device is wakeup-
+ * enabled and is capable of generating PME from D3cold, which
+ * requires auxiliary power. Instead of always skipping power off
+ * if PME is supported from D3cold, query the pwrctrl core and skip
+ * power off only if device supports PME from D3cold and Vaux is
+ * not supported.
+ */
+ pci->pp.skip_pwrctrl_off = pme_capable;
dw_pcie_stop_link(pci);
if (pci->pp.ops->deinit)
pci->pp.ops->deinit(&pci->pp);
@@ -1290,8 +1297,6 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci)
if (!pci->suspended)
return 0;
- pci->suspended = false;
-
if (pci->pp.ops->init) {
ret = pci->pp.ops->init(&pci->pp);
if (ret) {
@@ -1313,6 +1318,8 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci)
if (pci->pp.ops->post_init)
pci->pp.ops->post_init(&pci->pp);
+ pci->suspended = false;
+
return 0;
err_stop_link:
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index 89f2934c7cce..ea014c3da814 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -1253,6 +1253,34 @@ void dw_pcie_hide_unsupported_l1ss(struct dw_pcie *pci)
dw_pcie_writel_dbi(pci, l1ss + PCI_L1SS_CAP, l1ss_cap);
}
+/* TODO: Need to handle multi Root Ports */
+void dw_pcie_program_t_power_on(struct dw_pcie *pci, u32 t_power_on)
+{
+ u8 scale, value;
+ u16 offset;
+ u32 val;
+
+ if (!t_power_on)
+ return;
+
+ offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_L1SS);
+ if (!offset)
+ return;
+
+ pcie_encode_t_power_on(t_power_on, &scale, &value);
+
+ dw_pcie_dbi_ro_wr_en(pci);
+
+ val = dw_pcie_readl_dbi(pci, offset + PCI_L1SS_CAP);
+ val &= ~(PCI_L1SS_CAP_P_PWR_ON_SCALE | PCI_L1SS_CAP_P_PWR_ON_VALUE);
+ FIELD_MODIFY(PCI_L1SS_CAP_P_PWR_ON_SCALE, &val, scale);
+ FIELD_MODIFY(PCI_L1SS_CAP_P_PWR_ON_VALUE, &val, value);
+
+ dw_pcie_writel_dbi(pci, offset + PCI_L1SS_CAP, val);
+
+ dw_pcie_dbi_ro_wr_dis(pci);
+}
+
void dw_pcie_setup(struct dw_pcie *pci)
{
u32 val;
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 7f4bb88c7b22..b3ea6bb55499 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -451,6 +451,7 @@ struct dw_pcie_rp {
bool ecam_enabled;
bool native_ecam;
bool skip_l23_ready;
+ bool skip_pwrctrl_off;
};
struct dw_pcie_ep_ops {
@@ -608,6 +609,7 @@ int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
u8 bar, size_t size);
void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index);
void dw_pcie_hide_unsupported_l1ss(struct dw_pcie *pci);
+void dw_pcie_program_t_power_on(struct dw_pcie *pci, u32 t_power_on);
void dw_pcie_setup(struct dw_pcie *pci);
void dw_pcie_iatu_detect(struct dw_pcie *pci);
int dw_pcie_edma_detect(struct dw_pcie *pci);
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index a0b59b4ef1d2..d8eb52857f69 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -71,6 +71,7 @@
/* ELBI registers */
#define ELBI_SYS_CTRL 0x04
+#define ELBI_SYS_STTS 0x08
/* DBI registers */
#define AXI_MSTR_RESP_COMP_CTRL0 0x818
@@ -131,6 +132,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)
@@ -144,6 +146,10 @@
/* 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)
/* AXI_MSTR_RESP_COMP_CTRL0 register fields */
#define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_2K 0x4
@@ -245,6 +251,7 @@ struct qcom_pcie_ops {
void (*deinit)(struct qcom_pcie *pcie);
void (*ltssm_enable)(struct qcom_pcie *pcie);
int (*config_sid)(struct qcom_pcie *pcie);
+ enum dw_pcie_ltssm (*get_ltssm)(struct qcom_pcie *pcie);
};
/**
@@ -269,6 +276,7 @@ struct qcom_pcie_perst {
struct qcom_pcie_port {
struct list_head list;
struct phy *phy;
+ u32 l1ss_t_power_on;
struct list_head perst;
};
@@ -282,7 +290,7 @@ struct qcom_pcie {
const struct qcom_pcie_cfg *cfg;
struct dentry *debugfs;
struct list_head ports;
- bool suspended;
+ struct gpio_desc *reset;
bool use_pm_opp;
};
@@ -428,6 +436,15 @@ static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie)
writel(val, pci->elbi_base + ELBI_SYS_CTRL);
}
+static enum dw_pcie_ltssm qcom_pcie_2_1_0_get_ltssm(struct qcom_pcie *pcie)
+{
+ struct dw_pcie *pci = pcie->pci;
+ u32 val;
+
+ val = readl(pci->elbi_base + ELBI_SYS_STTS);
+ return (enum dw_pcie_ltssm)FIELD_GET(ELBI_SYS_STTS_LTSSM_STATE_MASK, val);
+}
+
static int qcom_pcie_get_resources_2_1_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_1_0 *res = &pcie->res.v2_1_0;
@@ -517,7 +534,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);
@@ -684,6 +701,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);
@@ -716,7 +739,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);
@@ -780,6 +803,12 @@ static int qcom_pcie_get_resources_2_4_0(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_4_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_4_0 *res = &pcie->res.v2_4_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);
reset_control_bulk_assert(res->num_resets, res->resets);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
@@ -848,6 +877,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);
}
@@ -903,6 +938,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);
@@ -998,7 +1034,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);
@@ -1069,6 +1105,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);
@@ -1173,6 +1215,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);
}
@@ -1213,6 +1261,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);
@@ -1260,6 +1309,19 @@ 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;
+
+ if (pcie->cfg->ops->get_ltssm)
+ return pcie->cfg->ops->get_ltssm(pcie);
+
+ 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;
@@ -1288,6 +1350,14 @@ static int qcom_pcie_phy_power_on(struct qcom_pcie *pcie)
return 0;
}
+static void qcom_pcie_configure_ports(struct qcom_pcie *pcie)
+{
+ struct qcom_pcie_port *port;
+
+ list_for_each_entry(port, &pcie->ports, list)
+ dw_pcie_program_t_power_on(pcie->pci, port->l1ss_t_power_on);
+}
+
static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
{
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
@@ -1304,13 +1374,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);
@@ -1322,6 +1396,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
dw_pcie_remove_capability(pcie->pci, PCI_CAP_ID_MSIX);
dw_pcie_remove_ext_capability(pcie->pci, PCI_EXT_CAP_ID_DPC);
+ qcom_pcie_configure_ports(pcie);
+
qcom_pcie_perst_deassert(pcie);
if (pcie->cfg->ops->config_sid) {
@@ -1335,9 +1411,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);
@@ -1354,11 +1431,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);
}
@@ -1385,10 +1465,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 */
@@ -1398,6 +1486,7 @@ static const struct qcom_pcie_ops ops_2_1_0 = {
.post_init = qcom_pcie_post_init_2_1_0,
.deinit = qcom_pcie_deinit_2_1_0,
.ltssm_enable = qcom_pcie_2_1_0_ltssm_enable,
+ .get_ltssm = qcom_pcie_2_1_0_get_ltssm,
};
/* Qcom IP rev.: 1.0.0 Synopsys IP rev.: 4.11a */
@@ -1407,6 +1496,7 @@ static const struct qcom_pcie_ops ops_1_0_0 = {
.post_init = qcom_pcie_post_init_1_0_0,
.deinit = qcom_pcie_deinit_1_0_0,
.ltssm_enable = qcom_pcie_2_1_0_ltssm_enable,
+ .get_ltssm = qcom_pcie_2_1_0_get_ltssm,
};
/* Qcom IP rev.: 2.3.2 Synopsys IP rev.: 4.21a */
@@ -1486,6 +1576,7 @@ static const struct qcom_pcie_cfg cfg_1_9_0 = {
static const struct qcom_pcie_cfg cfg_1_34_0 = {
.ops = &ops_1_9_0,
.override_no_snoop = true,
+ .no_l0s = true,
};
static const struct qcom_pcie_cfg cfg_2_1_0 = {
@@ -1525,6 +1616,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)
@@ -1626,6 +1718,22 @@ static void qcom_pcie_icc_opp_update(struct qcom_pcie *pcie)
}
}
+static int qcom_pcie_set_max_opp(struct device *dev)
+{
+ unsigned long max_freq = ULONG_MAX;
+ struct dev_pm_opp *opp;
+ int ret;
+
+ opp = dev_pm_opp_find_freq_floor(dev, &max_freq);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ ret = dev_pm_opp_set_opp(dev, opp);
+ dev_pm_opp_put(opp);
+
+ return ret;
+}
+
static int qcom_pcie_link_transition_count(struct seq_file *s, void *data)
{
struct qcom_pcie *pcie = (struct qcom_pcie *)dev_get_drvdata(s->private);
@@ -1687,6 +1795,12 @@ static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg)
pci->dbi_base = cfg->win;
pp->num_vectors = MSI_DEF_NUM_VECTORS;
+ /*
+ * dw_pcie_msi_host_init() is called directly here, bypassing
+ * dw_pcie_host_init() where pp->lock is normally initialized.
+ */
+ raw_spin_lock_init(&pp->lock);
+
ret = dw_pcie_msi_host_init(pp);
if (ret)
return ret;
@@ -1716,6 +1830,13 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
struct gpio_desc *reset;
int ret;
+ if (pcie->reset) {
+ dev_warn_once(dev,
+ "Reusing PERST# from Root Complex node. DT needs to be fixed!\n");
+ reset = pcie->reset;
+ goto skip_perst_parsing;
+ }
+
if (!of_find_property(np, "reset-gpios", NULL))
goto parse_child_node;
@@ -1734,6 +1855,7 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
return PTR_ERR(reset);
}
+skip_perst_parsing:
perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL);
if (!perst)
return -ENOMEM;
@@ -1777,6 +1899,9 @@ static int qcom_pcie_parse_port(struct qcom_pcie *pcie, struct device_node *node
if (ret)
return ret;
+ /* TODO: Move to DWC core after multi Root Port support is added */
+ of_property_read_u32(node, "t-power-on-us", &port->l1ss_t_power_on);
+
port->phy = phy;
INIT_LIST_HEAD(&port->list);
list_add_tail(&port->list, &pcie->ports);
@@ -1791,6 +1916,13 @@ static int qcom_pcie_parse_ports(struct qcom_pcie *pcie)
struct device *dev = pcie->pci->dev;
int ret = -ENODEV;
+ if (of_find_property(dev->of_node, "perst-gpios", NULL)) {
+ pcie->reset = devm_gpiod_get_optional(dev, "perst",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(pcie->reset))
+ return PTR_ERR(pcie->reset);
+ }
+
for_each_available_child_of_node_scoped(dev->of_node, of_port) {
if (!of_node_is_type(of_port, "pci"))
continue;
@@ -1817,7 +1949,6 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie)
struct device *dev = pcie->pci->dev;
struct qcom_pcie_perst *perst;
struct qcom_pcie_port *port;
- struct gpio_desc *reset;
struct phy *phy;
int ret;
@@ -1825,10 +1956,6 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie)
if (IS_ERR(phy))
return PTR_ERR(phy);
- reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_HIGH);
- if (IS_ERR(reset))
- return PTR_ERR(reset);
-
ret = phy_init(phy);
if (ret)
return ret;
@@ -1845,7 +1972,7 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie)
INIT_LIST_HEAD(&port->list);
list_add_tail(&port->list, &pcie->ports);
- perst->desc = reset;
+ perst->desc = pcie->reset;
INIT_LIST_HEAD(&port->perst);
INIT_LIST_HEAD(&perst->list);
list_add_tail(&perst->list, &port->perst);
@@ -1858,9 +1985,7 @@ static int qcom_pcie_probe(struct platform_device *pdev)
struct qcom_pcie_perst *perst, *tmp_perst;
struct qcom_pcie_port *port, *tmp_port;
const struct qcom_pcie_cfg *pcie_cfg;
- unsigned long max_freq = ULONG_MAX;
struct device *dev = &pdev->dev;
- struct dev_pm_opp *opp;
struct qcom_pcie *pcie;
struct dw_pcie_rp *pp;
struct resource *res;
@@ -1964,21 +2089,9 @@ static int qcom_pcie_probe(struct platform_device *pdev)
* probe(), OPP will be updated using qcom_pcie_icc_opp_update().
*/
if (!ret) {
- opp = dev_pm_opp_find_freq_floor(dev, &max_freq);
- if (IS_ERR(opp)) {
- ret = PTR_ERR(opp);
- dev_err_probe(pci->dev, ret,
- "Unable to find max freq OPP\n");
- goto err_pm_runtime_put;
- } else {
- ret = dev_pm_opp_set_opp(dev, opp);
- }
-
- dev_pm_opp_put(opp);
+ ret = qcom_pcie_set_max_opp(dev);
if (ret) {
- dev_err_probe(pci->dev, ret,
- "Failed to set OPP for freq %lu\n",
- max_freq);
+ dev_err_probe(dev, ret, "Failed to set max OPP\n");
goto err_pm_runtime_put;
}
@@ -2052,53 +2165,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;
}
@@ -2112,25 +2223,62 @@ 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) {
+ dev_err(dev, "Failed to set max OPP: %d\n", ret);
+ return ret;
+ }
+ }
+
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) {
+ 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;
+ }
+ }
+
+ 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[] = {
diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c
index 4ada2e536ba9..2ce6f4b66133 100644
--- a/drivers/pci/controller/pci-host-common.c
+++ b/drivers/pci/controller/pci-host-common.c
@@ -186,6 +186,10 @@ err_cleanup:
}
EXPORT_SYMBOL_GPL(pci_host_common_parse_ports);
+#define PCI_HOST_D3COLD_ALLOWED BIT(0)
+#define PCI_HOST_PME_D3COLD_CAPABLE BIT(1)
+
+
static void gen_pci_unmap_cfg(void *ptr)
{
pci_ecam_free((struct pci_config_window *)ptr);
@@ -279,5 +283,64 @@ 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)
+{
+ u32 *flags = userdata;
+
+ if (!pdev->dev.driver && !pci_is_enabled(pdev))
+ return 0;
+
+ if (pdev->current_state != PCI_D3hot)
+ goto exit;
+
+ if (device_may_wakeup(&pdev->dev)) {
+ if (!pci_pme_capable(pdev, PCI_D3cold))
+ goto exit;
+ else
+ *flags |= PCI_HOST_PME_D3COLD_CAPABLE;
+ }
+
+ return 0;
+
+exit:
+ *flags &= ~PCI_HOST_D3COLD_ALLOWED;
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * pci_host_common_d3cold_possible - Determine whether the host bridge can
+ * transition the devices into D3cold.
+ *
+ * @bridge: PCI host bridge to check
+ * @pme_capable: Pointer to update if there is any device capable of generating
+ * PME from D3cold.
+ *
+ * Walk downstream PCIe endpoint devices and determine whether the host bridge
+ * is permitted to transition the devices into D3cold.
+ *
+ * Devices under 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.
+ *
+ * The @pme_capable output allows PCIe controller drivers to apply
+ * platform-specific handling to preserve wakeup functionality.
+ *
+ * Return: %true if the host bridge may enter D3cold, otherwise %false.
+ */
+bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge,
+ bool *pme_capable)
+{
+ u32 flags = PCI_HOST_D3COLD_ALLOWED;
+
+ pci_walk_bus(bridge->bus, __pci_host_common_d3cold_possible, &flags);
+
+ *pme_capable = !!(flags & PCI_HOST_PME_D3COLD_CAPABLE);
+
+ return !!(flags & PCI_HOST_D3COLD_ALLOWED);
+}
+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 0f5fcc02e778..458678051d27 100644
--- a/drivers/pci/controller/pci-host-common.h
+++ b/drivers/pci/controller/pci-host-common.h
@@ -48,4 +48,7 @@ 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,
+ bool *pme_capable);
#endif
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 19660d068fb7..b36667969ad5 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1094,6 +1094,7 @@ void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked);
void pcie_aspm_powersave_config_link(struct pci_dev *pdev);
void pci_configure_ltr(struct pci_dev *pdev);
void pci_bridge_reconfigure_ltr(struct pci_dev *pdev);
+void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value);
#else
static inline void pcie_aspm_remove_cap(struct pci_dev *pdev, u32 lnkcap) { }
static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { }
@@ -1102,6 +1103,11 @@ static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked)
static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { }
static inline void pci_configure_ltr(struct pci_dev *pdev) { }
static inline void pci_bridge_reconfigure_ltr(struct pci_dev *pdev) { }
+static inline void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value)
+{
+ *scale = 0;
+ *value = 0;
+}
#endif
#ifdef CONFIG_PCIE_ECRC
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index 925373b98dff..172783e7f519 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -525,6 +525,46 @@ static u32 calc_l12_pwron(struct pci_dev *pdev, u32 scale, u32 val)
return 0;
}
+/**
+ * pcie_encode_t_power_on - Encode T_POWER_ON into scale and value fields
+ * @t_power_on_us: T_POWER_ON time in microseconds
+ * @scale: Encoded T_POWER_ON Scale (0..2)
+ * @value: Encoded T_POWER_ON Value
+ *
+ * T_POWER_ON is encoded as:
+ * T_POWER_ON(us) = scale_unit(us) * value
+ *
+ * where scale_unit is selected by @scale:
+ * 0: 2us
+ * 1: 10us
+ * 2: 100us
+ *
+ * If @t_power_on_us exceeds the maximum representable value, the result
+ * is clamped to the largest encodable T_POWER_ON.
+ *
+ * See PCIe r7.0, sec 7.8.3.2.
+ */
+void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value)
+{
+ u8 maxv = FIELD_MAX(PCI_L1SS_CAP_P_PWR_ON_VALUE);
+
+ /* T_POWER_ON_Value ("value") is a 5-bit field with max value of 31. */
+ if (t_power_on_us <= 2 * maxv) {
+ *scale = 0; /* Value times 2us */
+ *value = DIV_ROUND_UP(t_power_on_us, 2);
+ } else if (t_power_on_us <= 10 * maxv) {
+ *scale = 1; /* Value times 10us */
+ *value = DIV_ROUND_UP(t_power_on_us, 10);
+ } else if (t_power_on_us <= 100 * maxv) {
+ *scale = 2; /* value times 100us */
+ *value = DIV_ROUND_UP(t_power_on_us, 100);
+ } else {
+ *scale = 2;
+ *value = maxv;
+ }
+}
+EXPORT_SYMBOL(pcie_encode_t_power_on);
+
/*
* Encode an LTR_L1.2_THRESHOLD value for the L1 PM Substates Control 1
* register. Ports enter L1.2 when the most recent LTR value is greater