diff options
| author | Bjorn Helgaas <bhelgaas@google.com> | 2026-06-23 17:32:14 -0500 |
|---|---|---|
| committer | Bjorn Helgaas <bhelgaas@google.com> | 2026-06-23 17:32:14 -0500 |
| commit | 9cd6b3cae021dd8899cd4de7ba26daaad307db03 (patch) | |
| tree | aafad3d9f21c4e36f998d1666938fc3cd35255dd /drivers | |
| parent | c08c02e749b548afe5b772249d1ddab487c20815 (diff) | |
| parent | e0779713a1e2f891aeec53e629dbbd33f423c629 (diff) | |
| download | lwn-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.c | 23 | ||||
| -rw-r--r-- | drivers/pci/controller/dwc/pcie-designware.c | 28 | ||||
| -rw-r--r-- | drivers/pci/controller/dwc/pcie-designware.h | 2 | ||||
| -rw-r--r-- | drivers/pci/controller/dwc/pcie-qcom.c | 318 | ||||
| -rw-r--r-- | drivers/pci/controller/pci-host-common.c | 63 | ||||
| -rw-r--r-- | drivers/pci/controller/pci-host-common.h | 3 | ||||
| -rw-r--r-- | drivers/pci/pci.h | 6 | ||||
| -rw-r--r-- | drivers/pci/pcie/aspm.c | 40 |
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 |
