Changes in v3:
- Drop "Read back PARF_LTSSM register"
- Drop unnecessary inclusion of qcom,rpm-icc.h
- Fix a couple of commit msg typos
- Rebase, resolve some conflicts
- Link to v2: https://lore.kernel.org/r/[email protected]
Qualcomm PCIe RC shutdown & reinit
This series implements shutdown & reinitialization of the PCIe RC on
system suspend. Tested on 8280-crd.
Changes in v2:
* Rebase
* Get rid of "Cache last icc bandwidth", use icc_enable instead
* Don't permanently assert reset on clk enable fail in "Reshuffle reset.."
* Drop fixes tag in "Reshuffle reset.."
* Improve commit messages of "Reshuffle reset.." and "Implement RC shutdown.."
* Only set icc tag on RPMh SoCs
* Pick up rb
Link to v1: https://lore.kernel.org/linux-arm-msm/[email protected]/
Signed-off-by: Konrad Dybcio <[email protected]>
---
Konrad Dybcio (2):
PCI: qcom: reshuffle reset logic in 2_7_0 .init
PCI: qcom: properly implement RC shutdown/power up
drivers/pci/controller/dwc/Kconfig | 1 +
drivers/pci/controller/dwc/pcie-qcom.c | 176 ++++++++++++++++++++++++---------
2 files changed, 133 insertions(+), 44 deletions(-)
---
base-commit: 26074e1be23143b2388cacb36166766c235feb7c
change-id: 20240210-topic-8280_pcie-c94f58158029
Best regards,
--
Konrad Dybcio <[email protected]>
At least on SC8280XP, if the PCIe reset is asserted, the corresponding
AUX_CLK will be stuck at 'off'. This has not been an issue so far,
since the reset is both left de-asserted by the previous boot stages
and the driver only toggles it briefly in .init.
As part of the upcoming suspend procedure however, the reset will be
held asserted.
Assert the reset (which may end up being a NOP in some cases) and
de-assert it back *before* turning on the clocks in preparation for
introducing RC powerdown and reinitialization.
Signed-off-by: Konrad Dybcio <[email protected]>
---
drivers/pci/controller/dwc/pcie-qcom.c | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 14772edcf0d3..d875a9b2b7be 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -925,27 +925,27 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
return ret;
}
- ret = clk_bulk_prepare_enable(res->num_clks, res->clks);
- if (ret < 0)
- goto err_disable_regulators;
-
+ /* Assert the reset to hold the RC in a known state */
ret = reset_control_assert(res->rst);
if (ret) {
dev_err(dev, "reset assert failed (%d)\n", ret);
- goto err_disable_clocks;
+ goto err_disable_regulators;
}
-
usleep_range(1000, 1500);
+ /* GCC_PCIE_n_AUX_CLK won't come up if the reset is asserted */
ret = reset_control_deassert(res->rst);
if (ret) {
dev_err(dev, "reset deassert failed (%d)\n", ret);
- goto err_disable_clocks;
+ goto err_disable_regulators;
}
-
/* Wait for reset to complete, required on SM8450 */
usleep_range(1000, 1500);
+ ret = clk_bulk_prepare_enable(res->num_clks, res->clks);
+ if (ret < 0)
+ goto err_disable_regulators;
+
/* configure PCIe to RC mode */
writel(DEVICE_TYPE_RC, pcie->parf + PARF_DEVICE_TYPE);
@@ -976,8 +976,6 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
writel(val, pcie->parf + PARF_AXI_MSTR_WR_ADDR_HALT_V2);
return 0;
-err_disable_clocks:
- clk_bulk_disable_unprepare(res->num_clks, res->clks);
err_disable_regulators:
regulator_bulk_disable(ARRAY_SIZE(res->supplies), res->supplies);
--
2.44.0
Currently, we've only been minimizing the power draw while keeping the
RC up at all times. This is suboptimal, as it draws a whole lot of power
and prevents the SoC from power collapsing.
Implement full shutdown and re-initialization to allow for powering off
the controller.
This is mainly intended for SC8280XP with a broken power rail setup,
which requires a full RC shutdown/reinit in order to reach SoC-wide
power collapse, but sleeping is generally better than not sleeping and
less destructive suspend can be implemented later for platforms that
support it.
Co-developed-by: Bjorn Andersson <[email protected]>
Signed-off-by: Konrad Dybcio <[email protected]>
---
drivers/pci/controller/dwc/Kconfig | 1 +
drivers/pci/controller/dwc/pcie-qcom.c | 158 ++++++++++++++++++++++++++-------
2 files changed, 125 insertions(+), 34 deletions(-)
diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index 8afacc90c63b..4ce266951cb6 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -268,6 +268,7 @@ config PCIE_DW_PLAT_EP
config PCIE_QCOM
bool "Qualcomm PCIe controller (host mode)"
depends on OF && (ARCH_QCOM || COMPILE_TEST)
+ depends on QCOM_COMMAND_DB || QCOM_COMMAND_DB=n
depends on PCI_MSI
select PCIE_DW_HOST
select CRC8
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index d875a9b2b7be..8fb3532f4651 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -30,13 +30,17 @@
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/types.h>
+#include <soc/qcom/cmd-db.h>
#include "../../pci.h"
#include "pcie-designware.h"
+#include <dt-bindings/interconnect/qcom,icc.h>
+
/* PARF registers */
#define PARF_SYS_CTRL 0x00
#define PARF_PM_CTRL 0x20
+#define PARF_PM_STTS 0x24
#define PARF_PCS_DEEMPH 0x34
#define PARF_PCS_SWING 0x38
#define PARF_PHY_CTRL 0x40
@@ -81,7 +85,10 @@
#define L1_CLK_RMV_DIS BIT(1)
/* PARF_PM_CTRL register fields */
-#define REQ_NOT_ENTR_L1 BIT(5)
+#define REQ_NOT_ENTR_L1 BIT(5) /* "Prevent L0->L1" */
+
+/* PARF_PM_STTS register fields */
+#define PM_ENTER_L23 BIT(5)
/* PARF_PCS_DEEMPH register fields */
#define PCS_DEEMPH_TX_DEEMPH_GEN1(x) FIELD_PREP(GENMASK(21, 16), x)
@@ -126,6 +133,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
@@ -248,6 +256,7 @@ struct qcom_pcie {
const struct qcom_pcie_cfg *cfg;
struct dentry *debugfs;
bool suspended;
+ bool soc_is_rpmh;
};
#define to_qcom_pcie(x) dev_get_drvdata((x)->dev)
@@ -277,6 +286,24 @@ static int qcom_pcie_start_link(struct dw_pcie *pci)
return 0;
}
+static int qcom_pcie_stop_link(struct dw_pcie *pci)
+{
+ struct qcom_pcie *pcie = to_qcom_pcie(pci);
+ u32 ret_l23, val;
+
+ writel(ELBI_SYS_CTRL_PME_TURNOFF_MSG, pcie->elbi + ELBI_SYS_CTRL);
+ readl(pcie->elbi + ELBI_SYS_CTRL);
+
+ ret_l23 = readl_poll_timeout(pcie->parf + PARF_PM_STTS, val,
+ val & PM_ENTER_L23, 10000, 100000);
+ if (ret_l23) {
+ dev_err(pci->dev, "Failed to enter L2/L3\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
static void qcom_pcie_clear_aspm_l0s(struct dw_pcie *pci)
{
struct qcom_pcie *pcie = to_qcom_pcie(pci);
@@ -1012,9 +1039,19 @@ 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);
+ readl(pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
+ reset_control_assert(res->rst);
+ usleep_range(2000, 2500);
+
regulator_bulk_disable(ARRAY_SIZE(res->supplies), res->supplies);
}
@@ -1581,6 +1618,9 @@ static int qcom_pcie_probe(struct platform_device *pdev)
goto err_phy_exit;
}
+ /* If the soc features RPMh, cmd_db must have been prepared by now */
+ pcie->soc_is_rpmh = !cmd_db_ready();
+
qcom_pcie_icc_update(pcie);
if (pcie->mhi)
@@ -1597,58 +1637,108 @@ static int qcom_pcie_probe(struct platform_device *pdev)
return ret;
}
-static int qcom_pcie_suspend_noirq(struct device *dev)
+static int qcom_pcie_resume_noirq(struct device *dev)
{
struct qcom_pcie *pcie = dev_get_drvdata(dev);
int ret;
- /*
- * Set minimum bandwidth required to keep data path functional during
- * suspend.
- */
- ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
- if (ret) {
- dev_err(dev, "Failed to set interconnect bandwidth: %d\n", ret);
- return ret;
+ if (pcie->soc_is_rpmh) {
+ /*
+ * Undo the tag change from qcom_pcie_suspend_noirq first in
+ * case RPMh spontaneously decides to power collapse the
+ * platform based on other inputs.
+ */
+ icc_set_tag(pcie->icc_mem, QCOM_ICC_TAG_ALWAYS);
+
+ /* Flush the tag change */
+ ret = icc_enable(pcie->icc_mem);
+ if (ret) {
+ dev_err(pcie->pci->dev, "failed to icc_enable: %d", 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;
- }
+ /* Only check this now to make sure the icc tag has been set. */
+ if (!pcie->suspended)
+ return 0;
+
+ ret = qcom_pcie_host_init(&pcie->pci->pp);
+ if (ret)
+ goto revert_icc_tag;
+
+ dw_pcie_setup_rc(&pcie->pci->pp);
+
+ ret = qcom_pcie_start_link(pcie->pci);
+ if (ret)
+ goto deinit_host;
+
+ /* Ignore the retval, the devices may come up later. */
+ dw_pcie_wait_for_link(pcie->pci);
+
+ qcom_pcie_icc_update(pcie);
+
+ pcie->suspended = false;
return 0;
+
+deinit_host:
+ qcom_pcie_host_deinit(&pcie->pci->pp);
+revert_icc_tag:
+ if (pcie->soc_is_rpmh) {
+ icc_set_tag(pcie->icc_mem, QCOM_ICC_TAG_WAKE);
+
+ /* Ignore the retval, failing here would be tragic anyway.. */
+ icc_enable(pcie->icc_mem);
+ }
+
+ return ret;
}
-static int qcom_pcie_resume_noirq(struct device *dev)
+static int qcom_pcie_suspend_noirq(struct device *dev)
{
struct qcom_pcie *pcie = dev_get_drvdata(dev);
int ret;
- if (pcie->suspended) {
- ret = qcom_pcie_host_init(&pcie->pci->pp);
+ if (pcie->suspended)
+ return 0;
+
+ if (dw_pcie_link_up(pcie->pci)) {
+ ret = qcom_pcie_stop_link(pcie->pci);
if (ret)
return ret;
+ }
- pcie->suspended = false;
+ qcom_pcie_host_deinit(&pcie->pci->pp);
+
+ if (pcie->soc_is_rpmh) {
+ /*
+ * The PCIe RC may be covertly accessed by the secure firmware
+ * on sleep exit. Use the WAKE bucket to let RPMh pull the plug
+ * on PCIe in sleep, but guarantee it comes back up for resume.
+ */
+ icc_set_tag(pcie->icc_mem, QCOM_ICC_TAG_WAKE);
+
+ /* Flush the tag change */
+ ret = icc_enable(pcie->icc_mem);
+ if (ret) {
+ dev_err(pcie->pci->dev, "failed to icc_enable %d\n", ret);
+
+ /* Revert everything and pray icc calls succeed */
+ return qcom_pcie_resume_noirq(dev);
+ }
+ } else {
+ /*
+ * Set minimum bandwidth required to keep data path functional
+ * during suspend.
+ */
+ ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
+ if (ret) {
+ dev_err(dev, "Failed to set interconnect bandwidth: %d\n", ret);
+ return ret;
+ }
}
- qcom_pcie_icc_update(pcie);
+ pcie->suspended = true;
return 0;
}
--
2.44.0
On Wed, Mar 27, 2024 at 08:49:09PM +0100, Konrad Dybcio wrote:
> Currently, we've only been minimizing the power draw while keeping the
> RC up at all times. This is suboptimal, as it draws a whole lot of power
> and prevents the SoC from power collapsing.
>
> Implement full shutdown and re-initialization to allow for powering off
> the controller.
>
> This is mainly intended for SC8280XP with a broken power rail setup,
> which requires a full RC shutdown/reinit in order to reach SoC-wide
> power collapse, but sleeping is generally better than not sleeping and
> less destructive suspend can be implemented later for platforms that
> support it.
Second try (first at
https://lore.kernel.org/all/20240212213216.GA1145794@bhelgaas/):
- Capitalize subject lines to match history (sorry, I didn't mention
the first time)
- Drop or replace "properly" with something specific
- "... minimizing power draw while keeping RC up at all times ...
draws a whole lot of power" doesn't quite make sense to me
- Reword or explain "power collapse"
- No COMPILE_TEST provision (maybe it turned out to be impractical?)
- Magic delay numbers below with no citation or explanation. Even a
short comment could be a hint about how to verify and potentially
change in the future. A #define for readl_poll_timeout() would be
helpful as a place for a comment and because the name could
include "_US" to show the units.
- Add "()" after function names in comments
Bjorn
On 27.03.2024 10:20 PM, Bjorn Helgaas wrote:
> On Wed, Mar 27, 2024 at 08:49:09PM +0100, Konrad Dybcio wrote:
>> Currently, we've only been minimizing the power draw while keeping the
>> RC up at all times. This is suboptimal, as it draws a whole lot of power
>> and prevents the SoC from power collapsing.
>>
>> Implement full shutdown and re-initialization to allow for powering off
>> the controller.
>>
>> This is mainly intended for SC8280XP with a broken power rail setup,
>> which requires a full RC shutdown/reinit in order to reach SoC-wide
>> power collapse, but sleeping is generally better than not sleeping and
>> less destructive suspend can be implemented later for platforms that
>> support it.
>
> Second try (first at
> https://lore.kernel.org/all/20240212213216.GA1145794@bhelgaas/):
>
> - Capitalize subject lines to match history (sorry, I didn't mention
> the first time)
>
> - Drop or replace "properly" with something specific
>
> - "... minimizing power draw while keeping RC up at all times ...
> draws a whole lot of power" doesn't quite make sense to me
>
> - Reword or explain "power collapse"
>
> - No COMPILE_TEST provision (maybe it turned out to be impractical?)
>
> - Magic delay numbers below with no citation or explanation. Even a
> short comment could be a hint about how to verify and potentially
> change in the future. A #define for readl_poll_timeout() would be
> helpful as a place for a comment and because the name could
> include "_US" to show the units.
>
> - Add "()" after function names in comments
Sorry Bjorn, I came back to this series after some time and didn't revisit
your message. I'll be sure not to forget the next time around.
Konrad