[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-Id: <20250912-pci-pwrctrl-perst-v3-4-3c0ac62b032c@oss.qualcomm.com>
Date: Fri, 12 Sep 2025 14:05:04 +0530
From: Manivannan Sadhasivam via B4 Relay <devnull+manivannan.sadhasivam.oss.qualcomm.com@...nel.org>
To: Manivannan Sadhasivam <mani@...nel.org>,
Lorenzo Pieralisi <lpieralisi@...nel.org>,
Krzysztof WilczyĆski <kwilczynski@...nel.org>,
Rob Herring <robh@...nel.org>, Bjorn Helgaas <bhelgaas@...gle.com>,
Bartosz Golaszewski <brgl@...ev.pl>, Saravana Kannan <saravanak@...gle.com>
Cc: linux-pci@...r.kernel.org, linux-arm-msm@...r.kernel.org,
linux-kernel@...r.kernel.org, devicetree@...r.kernel.org,
Krishna Chaitanya Chundru <krishna.chundru@....qualcomm.com>,
Brian Norris <briannorris@...omium.org>,
Manivannan Sadhasivam <manivannan.sadhasivam@....qualcomm.com>
Subject: [PATCH v3 4/4] PCI: qcom: Allow pwrctrl core to control PERST# if
'reset-gpios' property is available
From: Manivannan Sadhasivam <manivannan.sadhasivam@....qualcomm.com>
For historic reasons, the pcie-qcom driver was controlling the power supply
and PERST# GPIO of the PCIe slot. This turned out to be an issue as the
power supply requirements differ between components. For instance, some of
the WLAN chipsets used in Qualcomm systems were connected to the Root Port
in a non-standard way using their own connectors. This requires specific
power sequencing mechanisms for controlling the WLAN chipsets. So the
pwrctrl framework (CONFIG_PWRCTRL) was introduced to handle these custom
and complex power supply requirements for components.
Sooner, we realized that it would be best to let the pwrctrl driver control
the supplies to the PCIe slots also. As it will allow us to consolidate all
the power supply handling in one place instead of doing it in two. So the
CONFIG_PWRCTRL_SLOT driver was introduced, that just parses the Root Port
nodes representing slots and controls the standard power supplies like
3.3v, 3.3VAux etc...
However, the control of the PERST# GPIOs was still within the controller
drivers like pcie-qcom. So the controller drivers continued to assert/
deassert PERST# GPIOs independent of the power supplies to the components.
This mostly went unnoticed as the components tolerated this non-standard
PERST# assertion/deassertion. But this behavior completely goes against the
PCIe Electromechanical specs like CEM, M.2, as these specs enforce strict
control of PERST# signal together with the power supplies.
So conform to these specs, allow the pwrctrl core to control PERST# for the
slots if the 'reset-gpios' property is specified in the DT bridge nodes.
This is achieved by populating the 'pci_host_bridge::perst_assert' callback
with qcom_pcie_perst_assert() function, so that the pwrctrl core can
control PERST# through this callback.
qcom_pcie_perst_assert() will find the PERST# GPIO descriptor associated
with the supplied 'device_node' of the component and asserts/deasserts
PERST# as requested by the 'assert' parameter. If PERST# is not found in
the supplied node of the component, the function will look for PERST# in
the parent node as a fallback. This is needed since PERST# won't be
available in the endpoint node as per the DT binding.
Note that the driver still asserts PERST# during the controller
initialization as it is needed as per the hardware documentation.
For preserving the backward compatibility with older DTs that still
specifies the Root Port resources in the host bridge DT node, the
controller driver still controls power supplies and PERST# for them. For
those cases, the 'qcom_pcie::legacy_binding' flag will be set and the
driver will continue to control PERST# exclusively. If this flag is not
set, then the pwrctrl driver will control PERST# through the callback.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@....qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 94 ++++++++++++++++++++++++++++++----
1 file changed, 83 insertions(+), 11 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index ccff01c31898cdbc5634221e7f8ef7e86469f5fd..f9a8908d6e5dc3e8dd9ab1c210bfbc5cca1e5be9 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -270,6 +270,7 @@ struct qcom_pcie_cfg {
struct qcom_pcie_perst {
struct list_head list;
struct gpio_desc *desc;
+ struct device_node *np;
};
struct qcom_pcie_port {
@@ -291,34 +292,90 @@ struct qcom_pcie {
struct list_head ports;
bool suspended;
bool use_pm_opp;
+ bool legacy_binding;
};
#define to_qcom_pcie(x) dev_get_drvdata((x)->dev)
-static void qcom_perst_assert(struct qcom_pcie *pcie, bool assert)
+static struct gpio_desc *qcom_find_perst(struct qcom_pcie *pcie, struct device_node *np)
+{
+ struct qcom_pcie_perst *perst;
+ struct qcom_pcie_port *port;
+
+ list_for_each_entry(port, &pcie->ports, list) {
+ list_for_each_entry(perst, &port->perst, list)
+ if (np == perst->np)
+ return perst->desc;
+ }
+
+ return NULL;
+}
+
+static void qcom_perst_reset_per_device(struct qcom_pcie *pcie,
+ struct device_node *np, int val)
+{
+ struct gpio_desc *perst;
+
+ perst = qcom_find_perst(pcie, np);
+ if (perst)
+ goto perst_assert;
+
+ /*
+ * If PERST# is not available in the current node, try the parent. This
+ * fallback is needed if the current node belongs to an endpoint or
+ * switch upstream port.
+ */
+ if (np->parent)
+ perst = qcom_find_perst(pcie, np->parent);
+
+perst_assert:
+ /* gpiod* APIs handle NULL gpio_desc gracefully. So no need to check. */
+ gpiod_set_value_cansleep(perst, val);
+}
+
+static void qcom_perst_reset(struct qcom_pcie *pcie, struct device_node *np,
+ bool assert)
{
struct qcom_pcie_perst *perst;
struct qcom_pcie_port *port;
int val = assert ? 1 : 0;
+ if (np) {
+ qcom_perst_reset_per_device(pcie, np, val);
+ goto perst_delay;
+ }
+
list_for_each_entry(port, &pcie->ports, list) {
list_for_each_entry(perst, &port->perst, list)
gpiod_set_value_cansleep(perst->desc, val);
}
+perst_delay:
usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500);
}
-static void qcom_ep_reset_assert(struct qcom_pcie *pcie)
+static void qcom_ep_reset_assert(struct qcom_pcie *pcie, struct device_node *np)
{
- qcom_perst_assert(pcie, true);
+ qcom_perst_reset(pcie, np, true);
}
-static void qcom_ep_reset_deassert(struct qcom_pcie *pcie)
+static void qcom_ep_reset_deassert(struct qcom_pcie *pcie,
+ struct device_node *np)
{
/* Ensure that PERST has been asserted for at least 100 ms */
msleep(PCIE_T_PVPERL_MS);
- qcom_perst_assert(pcie, false);
+ qcom_perst_reset(pcie, np, false);
+}
+
+static void qcom_pcie_perst_assert(struct pci_host_bridge *bridge,
+ struct device_node *np, bool assert)
+{
+ struct qcom_pcie *pcie = dev_get_drvdata(bridge->dev.parent);
+
+ if (assert)
+ qcom_ep_reset_assert(pcie, np);
+ else
+ qcom_ep_reset_deassert(pcie, np);
}
static int qcom_pcie_start_link(struct dw_pcie *pci)
@@ -1290,7 +1347,7 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
struct qcom_pcie *pcie = to_qcom_pcie(pci);
int ret;
- qcom_ep_reset_assert(pcie);
+ qcom_ep_reset_assert(pcie, NULL);
ret = pcie->cfg->ops->init(pcie);
if (ret)
@@ -1306,7 +1363,13 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
goto err_disable_phy;
}
- qcom_ep_reset_deassert(pcie);
+ /*
+ * Only deassert PERST# for all devices here if legacy binding is used.
+ * For the new binding, pwrctrl driver is expected to toggle PERST# for
+ * individual devices.
+ */
+ if (pcie->legacy_binding)
+ qcom_ep_reset_deassert(pcie, NULL);
if (pcie->cfg->ops->config_sid) {
ret = pcie->cfg->ops->config_sid(pcie);
@@ -1314,10 +1377,12 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
goto err_assert_reset;
}
+ pci->pp.bridge->perst_assert = qcom_pcie_perst_assert;
+
return 0;
err_assert_reset:
- qcom_ep_reset_assert(pcie);
+ qcom_ep_reset_assert(pcie, NULL);
err_disable_phy:
qcom_pcie_phy_power_off(pcie);
err_deinit:
@@ -1331,7 +1396,7 @@ static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp)
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
struct qcom_pcie *pcie = to_qcom_pcie(pci);
- qcom_ep_reset_assert(pcie);
+ qcom_ep_reset_assert(pcie, NULL);
qcom_pcie_phy_power_off(pcie);
pcie->cfg->ops->deinit(pcie);
}
@@ -1712,6 +1777,9 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
INIT_LIST_HEAD(&perst->list);
perst->desc = reset;
+ /* Increase the refcount to make sure 'np' is valid till it is stored */
+ of_node_get(np);
+ perst->np = np;
list_add_tail(&perst->list, &port->perst);
parse_child_node:
@@ -1773,8 +1841,10 @@ static int qcom_pcie_parse_ports(struct qcom_pcie *pcie)
err_port_del:
list_for_each_entry_safe(port, tmp_port, &pcie->ports, list) {
- list_for_each_entry_safe(perst, tmp_perst, &port->perst, list)
+ list_for_each_entry_safe(perst, tmp_perst, &port->perst, list) {
+ of_node_put(perst->np);
list_del(&perst->list);
+ }
phy_exit(port->phy);
list_del(&port->list);
}
@@ -2035,8 +2105,10 @@ static int qcom_pcie_probe(struct platform_device *pdev)
dw_pcie_host_deinit(pp);
err_phy_exit:
list_for_each_entry_safe(port, tmp_port, &pcie->ports, list) {
- list_for_each_entry_safe(perst, tmp_perst, &port->perst, list)
+ list_for_each_entry_safe(perst, tmp_perst, &port->perst, list) {
+ of_node_put(perst->np);
list_del(&perst->list);
+ }
phy_exit(port->phy);
list_del(&port->list);
}
--
2.45.2
Powered by blists - more mailing lists