Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ allOf:
enum:
- qcom,glymur-qmp-gen4x2-pcie-phy
- qcom,glymur-qmp-gen5x4-pcie-phy
- qcom,kaanapali-qmp-gen3x2-pcie-phy
- qcom,qcs8300-qmp-gen4x2-pcie-phy
- qcom,sa8775p-qmp-gen4x2-pcie-phy
- qcom,sa8775p-qmp-gen4x4-pcie-phy
Expand Down
7 changes: 1 addition & 6 deletions drivers/pci/controller/dwc/pcie-designware-host.c
Original file line number Diff line number Diff line change
Expand Up @@ -1218,18 +1218,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_can_enter_d3cold(pci->pp.bridge))
return 0;

if (pci->pp.ops->pme_turn_off) {
Expand Down
1 change: 1 addition & 0 deletions drivers/pci/controller/dwc/pcie-designware.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <linux/pci-epc.h>
#include <linux/pci-epf.h>

#include "../pci-host-common.h"
#include "../../pci.h"

/* DWC PCIe IP-core versions (native support since v4.70a) */
Expand Down
133 changes: 86 additions & 47 deletions drivers/pci/controller/dwc/pcie-qcom.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <linux/pci-ecam.h>
#include <linux/pci-pwrctrl.h>
#include <linux/pm_opp.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/phy/pcie.h>
Expand Down Expand Up @@ -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)
Expand All @@ -144,6 +146,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
Expand Down Expand Up @@ -282,7 +285,6 @@ struct qcom_pcie {
const struct qcom_pcie_cfg *cfg;
struct dentry *debugfs;
struct list_head ports;
bool suspended;
bool use_pm_opp;
};

Expand Down Expand Up @@ -1064,6 +1066,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;

/* Disable PCIe clocks and resets */
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);

Expand Down Expand Up @@ -1255,6 +1263,15 @@ 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;
Expand Down Expand Up @@ -1367,10 +1384,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 */
Expand Down Expand Up @@ -1507,6 +1532,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)
Expand Down Expand Up @@ -2034,53 +2060,56 @@ 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)
dev_pm_genpd_rpm_always_on(dev, false);
else
dev_pm_genpd_rpm_always_on(dev, 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;
}
Expand All @@ -2094,20 +2123,30 @@ 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)
ret = icc_enable(pcie->icc_mem);
if (ret) {
dev_err(dev, "Failed to enable PCIe-MEM interconnect path: %d\n", ret);
return ret;

pcie->suspended = false;
}
ret = dw_pcie_resume_noirq(pcie->pci);
if (ret && (ret != -ETIMEDOUT))
return ret;
} 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);
Expand Down
29 changes: 29 additions & 0 deletions drivers/pci/controller/pci-host-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,34 @@ void pci_host_common_remove(struct platform_device *pdev)
}
EXPORT_SYMBOL_GPL(pci_host_common_remove);

static int pci_host_common_check_d3cold(struct pci_dev *pdev, void *userdata)
{
bool *d3cold_allow = userdata;

if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT)
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_allow = false;
return -EBUSY;
}

bool pci_host_common_can_enter_d3cold(struct pci_host_bridge *bridge)
{
bool d3cold_allow = true;

pci_walk_bus(bridge->bus, pci_host_common_check_d3cold, &d3cold_allow);

return d3cold_allow;
}
EXPORT_SYMBOL_GPL(pci_host_common_can_enter_d3cold);

MODULE_DESCRIPTION("Common library for PCI host controller drivers");
MODULE_LICENSE("GPL v2");
2 changes: 2 additions & 0 deletions drivers/pci/controller/pci-host-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_can_enter_d3cold(struct pci_host_bridge *bridge);
#endif
10 changes: 10 additions & 0 deletions drivers/pci/quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -6380,3 +6380,13 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev)
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout);
#endif

/*
* Renesas PCIe-to-USB bridge UPD720201 does not advertise D3cold
* capability by default until firmware is loaded post-enumeration.
*/
static void quirk_enable_d3cold(struct pci_dev *dev)
{
dev->pme_support = dev->pme_support | (1 << PCI_D3cold);
}
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_RENESAS, 0x0014, quirk_enable_d3cold);