diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2025-03-27 13:14:44 -0500 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2025-03-27 13:14:44 -0500 |
commit | 55d25a101d47be6cf909adffe1660fab538e7fec (patch) | |
tree | 63d8909b3dc88bd70b5e9f86416c32ca37ea0578 | |
parent | e91c25c6fca81949d215f4009a8faac8c0308999 (diff) | |
parent | 75996c92f4de309f855471927e6489f5a354cfd4 (diff) |
Merge branch 'pci/pwrctrl'
- Create pwrctrl devices in pci_scan_device() to make it more symmetric
with pci_pwrctrl_unregister() and make pwrctrl devices for PCI bridges
possible (Manivannan Sadhasivam)
- Unregister pwrctrl devices in pci_destroy_dev() so DOE, ASPM, etc. can
still access devices after pci_stop_dev() (Manivannan Sadhasivam)
- If there's a pwrctrl device for a PCI device, skip scanning it because
the pwrctrl core will rescan the bus after the device is powered on
(Manivannan Sadhasivam)
- Add a pwrctrl driver for PCI slots based on voltage regulators described
via devicetree (Manivannan Sadhasivam)
* pci/pwrctrl:
PCI/pwrctrl: Add pwrctrl driver for PCI slots
dt-bindings: vendor-prefixes: Document the 'pciclass' prefix
PCI/pwrctrl: Skip scanning for the device further if pwrctrl device is created
PCI/pwrctrl: Move pci_pwrctrl_unregister() to pci_destroy_dev()
PCI/pwrctrl: Move creation of pwrctrl devices to pci_scan_device()
-rw-r--r-- | Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 | ||||
-rw-r--r-- | drivers/pci/bus.c | 43 | ||||
-rw-r--r-- | drivers/pci/probe.c | 41 | ||||
-rw-r--r-- | drivers/pci/pwrctrl/Kconfig | 11 | ||||
-rw-r--r-- | drivers/pci/pwrctrl/Makefile | 3 | ||||
-rw-r--r-- | drivers/pci/pwrctrl/core.c | 2 | ||||
-rw-r--r-- | drivers/pci/pwrctrl/slot.c | 93 | ||||
-rw-r--r-- | drivers/pci/remove.c | 2 |
8 files changed, 151 insertions, 46 deletions
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 5079ca6ce1d1..8e8bc6a7f0bd 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -18,7 +18,7 @@ patternProperties: # DO NOT ADD NEW PROPERTIES TO THIS LIST "^(at25|bm|devbus|dmacap|dsa|exynos|fsi[ab]|gpio-fan|gpio-key|gpio|gpmc|hdmi|i2c-gpio),.*": true "^(keypad|m25p|max8952|max8997|max8998|mpmc),.*": true - "^(pinctrl-single|#pinctrl-single|PowerPC),.*": true + "^(pciclass|pinctrl-single|#pinctrl-single|PowerPC),.*": true "^(pl022|pxa-mmc|rcar_sound|rotary-encoder|s5m8767|sdhci),.*": true "^(simple-audio-card|st-plgpio|st-spics|ts),.*": true diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index 98910bc0fcc4..b6851101ac36 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -331,47 +331,6 @@ void __weak pcibios_resource_survey_bus(struct pci_bus *bus) { } void __weak pcibios_bus_add_device(struct pci_dev *pdev) { } -/* - * Create pwrctrl devices (if required) for the PCI devices to handle the power - * state. - */ -static void pci_pwrctrl_create_devices(struct pci_dev *dev) -{ - struct device_node *np = dev_of_node(&dev->dev); - struct device *parent = &dev->dev; - struct platform_device *pdev; - - /* - * First ensure that we are starting from a PCI bridge and it has a - * corresponding devicetree node. - */ - if (np && pci_is_bridge(dev)) { - /* - * Now look for the child PCI device nodes and create pwrctrl - * devices for them. The pwrctrl device drivers will manage the - * power state of the devices. - */ - for_each_available_child_of_node_scoped(np, child) { - /* - * First check whether the pwrctrl device really - * needs to be created or not. This is decided - * based on at least one of the power supplies - * being defined in the devicetree node of the - * device. - */ - if (!of_pci_supply_present(child)) { - pci_dbg(dev, "skipping OF node: %s\n", child->name); - return; - } - - /* Now create the pwrctrl device */ - pdev = of_platform_device_create(child, NULL, parent); - if (!pdev) - pci_err(dev, "failed to create OF node: %s\n", child->name); - } - } -} - /** * pci_bus_add_device - start driver for a single device * @dev: device to add @@ -396,8 +355,6 @@ void pci_bus_add_device(struct pci_dev *dev) pci_proc_attach_device(dev); pci_bridge_d3_update(dev); - pci_pwrctrl_create_devices(dev); - /* * If the PCI device is associated with a pwrctrl device with a * power supply, create a device link between the PCI device and diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c8480e7edd11..0214f98aea46 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -9,6 +9,8 @@ #include <linux/pci.h> #include <linux/msi.h> #include <linux/of_pci.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> #include <linux/pci_hotplug.h> #include <linux/slab.h> #include <linux/module.h> @@ -2503,6 +2505,36 @@ bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, } EXPORT_SYMBOL(pci_bus_read_dev_vendor_id); +static struct platform_device *pci_pwrctrl_create_device(struct pci_bus *bus, int devfn) +{ + struct pci_host_bridge *host = pci_find_host_bridge(bus); + struct platform_device *pdev; + struct device_node *np; + + np = of_pci_find_child_device(dev_of_node(&bus->dev), devfn); + if (!np || of_find_device_by_node(np)) + return NULL; + + /* + * First check whether the pwrctrl device really needs to be created or + * not. This is decided based on at least one of the power supplies + * being defined in the devicetree node of the device. + */ + if (!of_pci_supply_present(np)) { + pr_debug("PCI/pwrctrl: Skipping OF node: %s\n", np->name); + return NULL; + } + + /* Now create the pwrctrl device */ + pdev = of_platform_device_create(np, NULL, &host->dev); + if (!pdev) { + pr_err("PCI/pwrctrl: Failed to create pwrctrl device for node: %s\n", np->name); + return NULL; + } + + return pdev; +} + /* * Read the config data for a PCI device, sanity-check it, * and fill in the dev structure. @@ -2512,6 +2544,15 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) struct pci_dev *dev; u32 l; + /* + * Create pwrctrl device (if required) for the PCI device to handle the + * power state. If the pwrctrl device is created, then skip scanning + * further as the pwrctrl core will rescan the bus after powering on + * the device. + */ + if (pci_pwrctrl_create_device(bus, devfn)) + return NULL; + if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000)) return NULL; diff --git a/drivers/pci/pwrctrl/Kconfig b/drivers/pci/pwrctrl/Kconfig index 54589bb2403b..990cab67d413 100644 --- a/drivers/pci/pwrctrl/Kconfig +++ b/drivers/pci/pwrctrl/Kconfig @@ -10,3 +10,14 @@ config PCI_PWRCTL_PWRSEQ tristate select POWER_SEQUENCING select PCI_PWRCTL + +config PCI_PWRCTL_SLOT + tristate "PCI Power Control driver for PCI slots" + select PCI_PWRCTL + help + Say Y here to enable the PCI Power Control driver to control the power + state of PCI slots. + + This is a generic driver that controls the power state of different + PCI slots. The voltage regulators powering the rails of the PCI slots + are expected to be defined in the devicetree node of the PCI bridge. diff --git a/drivers/pci/pwrctrl/Makefile b/drivers/pci/pwrctrl/Makefile index 75c7ce531c7e..ddfb12c5aadf 100644 --- a/drivers/pci/pwrctrl/Makefile +++ b/drivers/pci/pwrctrl/Makefile @@ -4,3 +4,6 @@ obj-$(CONFIG_PCI_PWRCTL) += pci-pwrctrl-core.o pci-pwrctrl-core-y := core.o obj-$(CONFIG_PCI_PWRCTL_PWRSEQ) += pci-pwrctrl-pwrseq.o + +obj-$(CONFIG_PCI_PWRCTL_SLOT) += pci-pwrctl-slot.o +pci-pwrctl-slot-y := slot.o diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index 2fb174db91e5..9cc7e2b7f2b5 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -44,7 +44,7 @@ static void rescan_work_func(struct work_struct *work) struct pci_pwrctrl, work); pci_lock_rescan_remove(); - pci_rescan_bus(to_pci_dev(pwrctrl->dev->parent)->bus); + pci_rescan_bus(to_pci_host_bridge(pwrctrl->dev->parent)->bus); pci_unlock_rescan_remove(); } diff --git a/drivers/pci/pwrctrl/slot.c b/drivers/pci/pwrctrl/slot.c new file mode 100644 index 000000000000..18becc144913 --- /dev/null +++ b/drivers/pci/pwrctrl/slot.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Linaro Ltd. + * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> + */ + +#include <linux/device.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pci-pwrctrl.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +struct pci_pwrctrl_slot_data { + struct pci_pwrctrl ctx; + struct regulator_bulk_data *supplies; + int num_supplies; +}; + +static void devm_pci_pwrctrl_slot_power_off(void *data) +{ + struct pci_pwrctrl_slot_data *slot = data; + + regulator_bulk_disable(slot->num_supplies, slot->supplies); + regulator_bulk_free(slot->num_supplies, slot->supplies); +} + +static int pci_pwrctrl_slot_probe(struct platform_device *pdev) +{ + struct pci_pwrctrl_slot_data *slot; + struct device *dev = &pdev->dev; + int ret; + + slot = devm_kzalloc(dev, sizeof(*slot), GFP_KERNEL); + if (!slot) + return -ENOMEM; + + ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), + &slot->supplies); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to get slot regulators\n"); + return ret; + } + + slot->num_supplies = ret; + ret = regulator_bulk_enable(slot->num_supplies, slot->supplies); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to enable slot regulators\n"); + goto err_regulator_free; + } + + ret = devm_add_action_or_reset(dev, devm_pci_pwrctrl_slot_power_off, + slot); + if (ret) + goto err_regulator_disable; + + pci_pwrctrl_init(&slot->ctx, dev); + + ret = devm_pci_pwrctrl_device_set_ready(dev, &slot->ctx); + if (ret) + return dev_err_probe(dev, ret, "Failed to register pwrctrl driver\n"); + + return 0; + +err_regulator_disable: + regulator_bulk_disable(slot->num_supplies, slot->supplies); +err_regulator_free: + regulator_bulk_free(slot->num_supplies, slot->supplies); + + return ret; +} + +static const struct of_device_id pci_pwrctrl_slot_of_match[] = { + { + .compatible = "pciclass,0604", + }, + { } +}; +MODULE_DEVICE_TABLE(of, pci_pwrctrl_slot_of_match); + +static struct platform_driver pci_pwrctrl_slot_driver = { + .driver = { + .name = "pci-pwrctrl-slot", + .of_match_table = pci_pwrctrl_slot_of_match, + }, + .probe = pci_pwrctrl_slot_probe, +}; +module_platform_driver(pci_pwrctrl_slot_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); +MODULE_DESCRIPTION("Generic PCI Power Control driver for PCI Slots"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 5813726214e6..368659a97d57 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -41,7 +41,6 @@ static void pci_stop_dev(struct pci_dev *dev) if (!pci_dev_test_and_clear_added(dev)) return; - pci_pwrctrl_unregister(&dev->dev); device_release_driver(&dev->dev); pci_proc_detach_device(dev); pci_remove_sysfs_dev_files(dev); @@ -65,6 +64,7 @@ static void pci_destroy_dev(struct pci_dev *dev) pci_doe_destroy(dev); pcie_aspm_exit_link_state(dev); pci_bridge_d3_update(dev); + pci_pwrctrl_unregister(&dev->dev); pci_free_resources(dev); put_device(&dev->dev); } |