PERST# is an (optional) auxiliary signal provided by the PCIe host to components for signalling 'Fundamental Reset' as per the PCIe spec r6.0, sec 6.6.1. If PERST# is available, it's state will be toggled during the component power-up and power-down scenarios as per the PCI Express Card Electromechanical Spec v4.0, sec 2.2. Historically, the PCIe controller drivers were directly controlling the PERST# signal together with the power supplies. But with the advent of the pwrctrl framework, the power supply control is now moved to the pwrctrl, but controller drivers still ended up toggling the PERST# signal. This only happens on Qcom platforms where pwrctrl framework is being used. But nevertheseless, it is wrong to toggle PERST# (especially deassert) without controlling the power supplies. So allow the pwrctrl core to control the PERST# GPIO is available. The controller drivers still need to parse them and populate the 'host_bridge->perst' GPIO descriptor array based on the available slots. Unfortunately, we cannot just move the PERST# handling from controller drivers as most of the controller drivers need to assert PERST# during the controller initialization. Signed-off-by: Manivannan Sadhasivam <mani@xxxxxxxxxx> --- drivers/pci/pwrctrl/core.c | 39 +++++++++++++++++++++++++++++++++++++++ include/linux/pci-pwrctrl.h | 2 ++ include/linux/pci.h | 2 ++ 3 files changed, 43 insertions(+) diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index 6bdbfed584d6d79ce28ba9e384a596b065ca69a4..abdb46399a96c8281916f971329d5460fcff3f6e 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -3,14 +3,19 @@ * Copyright (C) 2024 Linaro Ltd. */ +#include <linux/delay.h> #include <linux/device.h> #include <linux/export.h> +#include <linux/gpio/consumer.h> #include <linux/kernel.h> +#include <linux/of_pci.h> #include <linux/pci.h> #include <linux/pci-pwrctrl.h> #include <linux/property.h> #include <linux/slab.h> +#include "../pci.h" + static int pci_pwrctrl_notify(struct notifier_block *nb, unsigned long action, void *data) { @@ -56,11 +61,42 @@ static void rescan_work_func(struct work_struct *work) */ void pci_pwrctrl_init(struct pci_pwrctrl *pwrctrl, struct device *dev) { + struct pci_host_bridge *host_bridge = to_pci_host_bridge(dev->parent); + int devfn; + pwrctrl->dev = dev; INIT_WORK(&pwrctrl->work, rescan_work_func); + + if (!host_bridge->perst) + return; + + devfn = of_pci_get_devfn(dev_of_node(dev)); + if (devfn >= 0 && host_bridge->perst[PCI_SLOT(devfn)]) + pwrctrl->perst = host_bridge->perst[PCI_SLOT(devfn)]; } EXPORT_SYMBOL_GPL(pci_pwrctrl_init); +static void pci_pwrctrl_perst_deassert(struct pci_pwrctrl *pwrctrl) +{ + /* Bail out early to avoid the delay if PERST# is not available */ + if (!pwrctrl->perst) + return; + + msleep(PCIE_T_PVPERL_MS); + gpiod_set_value_cansleep(pwrctrl->perst, 0); + /* + * FIXME: The following delay is only required for downstream ports not + * supporting link speed greater than 5.0 GT/s. + */ + msleep(PCIE_RESET_CONFIG_DEVICE_WAIT_MS); +} + +static void pci_pwrctrl_perst_assert(struct pci_pwrctrl *pwrctrl) +{ + /* No need to validate desc here as gpiod APIs handle it for us */ + gpiod_set_value_cansleep(pwrctrl->perst, 1); +} + /** * pci_pwrctrl_device_set_ready() - Notify the pwrctrl subsystem that the PCI * device is powered-up and ready to be detected. @@ -82,6 +118,8 @@ int pci_pwrctrl_device_set_ready(struct pci_pwrctrl *pwrctrl) if (!pwrctrl->dev) return -ENODEV; + pci_pwrctrl_perst_deassert(pwrctrl); + pwrctrl->nb.notifier_call = pci_pwrctrl_notify; ret = bus_register_notifier(&pci_bus_type, &pwrctrl->nb); if (ret) @@ -103,6 +141,7 @@ void pci_pwrctrl_device_unset_ready(struct pci_pwrctrl *pwrctrl) { cancel_work_sync(&pwrctrl->work); + pci_pwrctrl_perst_assert(pwrctrl); /* * We don't have to delete the link here. Typically, this function * is only called when the power control device is being detached. If diff --git a/include/linux/pci-pwrctrl.h b/include/linux/pci-pwrctrl.h index 7d439b0675e91e920737287eaf1937f38e47f2ce..1ce6aec343fea1b77a146682f499ece791be70dc 100644 --- a/include/linux/pci-pwrctrl.h +++ b/include/linux/pci-pwrctrl.h @@ -31,6 +31,7 @@ struct device_link; /** * struct pci_pwrctrl - PCI device power control context. * @dev: Address of the power controlling device. + * @perst: PERST# GPIO connected to the PCI device. * * An object of this type must be allocated by the PCI power control device and * passed to the pwrctrl subsystem to trigger a bus rescan and setup a device @@ -38,6 +39,7 @@ struct device_link; */ struct pci_pwrctrl { struct device *dev; + struct gpio_desc *perst; /* Private: don't use. */ struct notifier_block nb; diff --git a/include/linux/pci.h b/include/linux/pci.h index 05e68f35f39238f8b9ce08df97b384d1c1e89bbe..fcce106c7e2985ee1dd79bfcd241f133e5599fe1 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -39,6 +39,7 @@ #include <linux/io.h> #include <linux/resource_ext.h> #include <linux/msi_api.h> +#include <linux/gpio/consumer.h> #include <uapi/linux/pci.h> #include <linux/pci_ids.h> @@ -600,6 +601,7 @@ struct pci_host_bridge { int (*enable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev); void (*disable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev); void *release_data; + struct gpio_desc **perst; unsigned int ignore_reset_delay:1; /* For entire hierarchy */ unsigned int no_ext_tags:1; /* No Extended Tags */ unsigned int no_inc_mrrs:1; /* No Increase MRRS */ -- 2.45.2