Merge branch 'pci/pwrctl'

- Use of_platform_device_create() instead of of_platform_populate() to
  create pwrctl platform devices so we can control it based on the child
  nodes (Manivannan Sadhasivam)

- Create pwrctrl platform devices only if there's a relevant power supply
  property (Manivannan Sadhasivam)

- Add device link from the pwrctl supplier to the PCI dev to ensure pwrctl
  drivers are probed before the PCI dev driver; this avoids a race where
  pwrctl could change device power state while the PCI driver was active
  (Manivannan Sadhasivam)

- Find pwrctl device for removal with of_find_device_by_node() instead of
  searching all children of the parent (Manivannan Sadhasivam)

- Rename 'pwrctl' to 'pwrctrl' to use the same 'ctrl' suffix as 'bwctrl'
  and other PCI files to reduce confusion (Bjorn Helgaas)

* pci/pwrctl:
  PCI/pwrctrl: Rename pwrctrl functions and structures
  PCI/pwrctrl: Rename pwrctl files to pwrctrl
  PCI/pwrctl: Remove pwrctl device without iterating over all children of pwrctl parent
  PCI/pwrctl: Ensure that pwrctl drivers are probed before PCI client drivers
  PCI/pwrctl: Create pwrctl device only if at least one power supply is present
  PCI/pwrctl: Use of_platform_device_create() to create pwrctl devices

# Conflicts:
#	drivers/pci/bus.c
#	drivers/pci/remove.c
This commit is contained in:
Bjorn Helgaas 2024-11-25 13:40:54 -06:00
commit ce1deca962
14 changed files with 285 additions and 212 deletions

View File

@ -17916,8 +17916,8 @@ M: Bartosz Golaszewski <brgl@bgdev.pl>
L: linux-pci@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git
F: drivers/pci/pwrctl/*
F: include/linux/pci-pwrctl.h
F: drivers/pci/pwrctrl/*
F: include/linux/pci-pwrctrl.h
PCI SUBSYSTEM
M: Bjorn Helgaas <bhelgaas@google.com>

View File

@ -305,6 +305,6 @@ source "drivers/pci/hotplug/Kconfig"
source "drivers/pci/controller/Kconfig"
source "drivers/pci/endpoint/Kconfig"
source "drivers/pci/switch/Kconfig"
source "drivers/pci/pwrctl/Kconfig"
source "drivers/pci/pwrctrl/Kconfig"
endif

View File

@ -9,7 +9,7 @@ obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \
obj-$(CONFIG_PCI) += msi/
obj-$(CONFIG_PCI) += pcie/
obj-$(CONFIG_PCI) += pwrctl/
obj-$(CONFIG_PCI) += pwrctrl/
ifdef CONFIG_PCI
obj-$(CONFIG_PROC_FS) += proc.o

View File

@ -13,6 +13,7 @@
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
@ -330,6 +331,47 @@ 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
@ -339,6 +381,7 @@ void __weak pcibios_bus_add_device(struct pci_dev *pdev) { }
void pci_bus_add_device(struct pci_dev *dev)
{
struct device_node *dn = dev->dev.of_node;
struct platform_device *pdev;
int retval;
/*
@ -353,20 +396,28 @@ 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
* pwrctrl device. This ensures that pwrctrl drivers are probed
* before PCI client drivers.
*/
pdev = of_find_device_by_node(dn);
if (pdev && of_pci_supply_present(dn)) {
if (!device_link_add(&dev->dev, &pdev->dev,
DL_FLAG_AUTOREMOVE_CONSUMER))
pci_err(dev, "failed to add device link to power control device %s\n",
pdev->name);
}
dev->match_driver = !dn || of_device_is_available(dn);
retval = device_attach(&dev->dev);
if (retval < 0 && retval != -EPROBE_DEFER)
pci_warn(dev, "device attach failed (%d)\n", retval);
pci_dev_assign_added(dev);
if (dev_of_node(&dev->dev) && pci_is_bridge(dev)) {
retval = of_platform_populate(dev_of_node(&dev->dev), NULL, NULL,
&dev->dev);
if (retval)
pci_err(dev, "failed to populate child OF nodes (%d)\n",
retval);
}
}
EXPORT_SYMBOL_GPL(pci_bus_add_device);

View File

@ -728,6 +728,33 @@ void of_pci_make_dev_node(struct pci_dev *pdev)
}
#endif
/**
* of_pci_supply_present() - Check if the power supply is present for the PCI
* device
* @np: Device tree node
*
* Check if the power supply for the PCI device is present in the device tree
* node or not.
*
* Return: true if at least one power supply exists; false otherwise.
*/
bool of_pci_supply_present(struct device_node *np)
{
struct property *prop;
char *supply;
if (!np)
return false;
for_each_property_of_node(np, prop) {
supply = strrchr(prop->name, '-');
if (supply && !strcmp(supply, "-supply"))
return true;
}
return false;
}
#endif /* CONFIG_PCI */
/**

View File

@ -797,6 +797,7 @@ void pci_set_bus_of_node(struct pci_bus *bus);
void pci_release_bus_of_node(struct pci_bus *bus);
int devm_of_pci_bridge_init(struct device *dev, struct pci_host_bridge *bridge);
bool of_pci_supply_present(struct device_node *np);
#else
static inline int
@ -844,6 +845,10 @@ static inline int devm_of_pci_bridge_init(struct device *dev, struct pci_host_br
return 0;
}
static inline bool of_pci_supply_present(struct device_node *np)
{
return false;
}
#endif /* CONFIG_OF */
struct of_changeset;

View File

@ -1,6 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_PCI_PWRCTL) += pci-pwrctl-core.o
pci-pwrctl-core-y := core.o
obj-$(CONFIG_PCI_PWRCTL_PWRSEQ) += pci-pwrctl-pwrseq.o

View File

@ -1,157 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Linaro Ltd.
*/
#include <linux/device.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/pci-pwrctl.h>
#include <linux/property.h>
#include <linux/slab.h>
static int pci_pwrctl_notify(struct notifier_block *nb, unsigned long action,
void *data)
{
struct pci_pwrctl *pwrctl = container_of(nb, struct pci_pwrctl, nb);
struct device *dev = data;
if (dev_fwnode(dev) != dev_fwnode(pwrctl->dev))
return NOTIFY_DONE;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
/*
* We will have two struct device objects bound to two different
* drivers on different buses but consuming the same DT node. We
* must not bind the pins twice in this case but only once for
* the first device to be added.
*
* If we got here then the PCI device is the second after the
* power control platform device. Mark its OF node as reused.
*/
dev->of_node_reused = true;
break;
case BUS_NOTIFY_BOUND_DRIVER:
pwrctl->link = device_link_add(dev, pwrctl->dev,
DL_FLAG_AUTOREMOVE_CONSUMER);
if (!pwrctl->link)
dev_err(pwrctl->dev, "Failed to add device link\n");
break;
case BUS_NOTIFY_UNBOUND_DRIVER:
if (pwrctl->link)
device_link_remove(dev, pwrctl->dev);
break;
}
return NOTIFY_DONE;
}
static void rescan_work_func(struct work_struct *work)
{
struct pci_pwrctl *pwrctl = container_of(work, struct pci_pwrctl, work);
pci_lock_rescan_remove();
pci_rescan_bus(to_pci_dev(pwrctl->dev->parent)->bus);
pci_unlock_rescan_remove();
}
/**
* pci_pwrctl_init() - Initialize the PCI power control context struct
*
* @pwrctl: PCI power control data
* @dev: Parent device
*/
void pci_pwrctl_init(struct pci_pwrctl *pwrctl, struct device *dev)
{
pwrctl->dev = dev;
INIT_WORK(&pwrctl->work, rescan_work_func);
}
EXPORT_SYMBOL_GPL(pci_pwrctl_init);
/**
* pci_pwrctl_device_set_ready() - Notify the pwrctl subsystem that the PCI
* device is powered-up and ready to be detected.
*
* @pwrctl: PCI power control data.
*
* Returns:
* 0 on success, negative error number on error.
*
* Note:
* This function returning 0 doesn't mean the device was detected. It means,
* that the bus rescan was successfully started. The device will get bound to
* its PCI driver asynchronously.
*/
int pci_pwrctl_device_set_ready(struct pci_pwrctl *pwrctl)
{
int ret;
if (!pwrctl->dev)
return -ENODEV;
pwrctl->nb.notifier_call = pci_pwrctl_notify;
ret = bus_register_notifier(&pci_bus_type, &pwrctl->nb);
if (ret)
return ret;
schedule_work(&pwrctl->work);
return 0;
}
EXPORT_SYMBOL_GPL(pci_pwrctl_device_set_ready);
/**
* pci_pwrctl_device_unset_ready() - Notify the pwrctl subsystem that the PCI
* device is about to be powered-down.
*
* @pwrctl: PCI power control data.
*/
void pci_pwrctl_device_unset_ready(struct pci_pwrctl *pwrctl)
{
/*
* We don't have to delete the link here. Typically, this function
* is only called when the power control device is being detached. If
* it is being detached then the child PCI device must have already
* been unbound too or the device core wouldn't let us unbind.
*/
bus_unregister_notifier(&pci_bus_type, &pwrctl->nb);
}
EXPORT_SYMBOL_GPL(pci_pwrctl_device_unset_ready);
static void devm_pci_pwrctl_device_unset_ready(void *data)
{
struct pci_pwrctl *pwrctl = data;
pci_pwrctl_device_unset_ready(pwrctl);
}
/**
* devm_pci_pwrctl_device_set_ready - Managed variant of
* pci_pwrctl_device_set_ready().
*
* @dev: Device managing this pwrctl provider.
* @pwrctl: PCI power control data.
*
* Returns:
* 0 on success, negative error number on error.
*/
int devm_pci_pwrctl_device_set_ready(struct device *dev,
struct pci_pwrctl *pwrctl)
{
int ret;
ret = pci_pwrctl_device_set_ready(pwrctl);
if (ret)
return ret;
return devm_add_action_or_reset(dev,
devm_pci_pwrctl_device_unset_ready,
pwrctl);
}
EXPORT_SYMBOL_GPL(devm_pci_pwrctl_device_set_ready);
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
MODULE_DESCRIPTION("PCI Device Power Control core driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_PCI_PWRCTL) += pci-pwrctrl-core.o
pci-pwrctrl-core-y := core.o
obj-$(CONFIG_PCI_PWRCTL_PWRSEQ) += pci-pwrctrl-pwrseq.o

148
drivers/pci/pwrctrl/core.c Normal file
View File

@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Linaro Ltd.
*/
#include <linux/device.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/pci-pwrctrl.h>
#include <linux/property.h>
#include <linux/slab.h>
static int pci_pwrctrl_notify(struct notifier_block *nb, unsigned long action,
void *data)
{
struct pci_pwrctrl *pwrctrl = container_of(nb, struct pci_pwrctrl, nb);
struct device *dev = data;
if (dev_fwnode(dev) != dev_fwnode(pwrctrl->dev))
return NOTIFY_DONE;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
/*
* We will have two struct device objects bound to two different
* drivers on different buses but consuming the same DT node. We
* must not bind the pins twice in this case but only once for
* the first device to be added.
*
* If we got here then the PCI device is the second after the
* power control platform device. Mark its OF node as reused.
*/
dev->of_node_reused = true;
break;
}
return NOTIFY_DONE;
}
static void rescan_work_func(struct work_struct *work)
{
struct pci_pwrctrl *pwrctrl = container_of(work,
struct pci_pwrctrl, work);
pci_lock_rescan_remove();
pci_rescan_bus(to_pci_dev(pwrctrl->dev->parent)->bus);
pci_unlock_rescan_remove();
}
/**
* pci_pwrctrl_init() - Initialize the PCI power control context struct
*
* @pwrctrl: PCI power control data
* @dev: Parent device
*/
void pci_pwrctrl_init(struct pci_pwrctrl *pwrctrl, struct device *dev)
{
pwrctrl->dev = dev;
INIT_WORK(&pwrctrl->work, rescan_work_func);
}
EXPORT_SYMBOL_GPL(pci_pwrctrl_init);
/**
* pci_pwrctrl_device_set_ready() - Notify the pwrctrl subsystem that the PCI
* device is powered-up and ready to be detected.
*
* @pwrctrl: PCI power control data.
*
* Returns:
* 0 on success, negative error number on error.
*
* Note:
* This function returning 0 doesn't mean the device was detected. It means,
* that the bus rescan was successfully started. The device will get bound to
* its PCI driver asynchronously.
*/
int pci_pwrctrl_device_set_ready(struct pci_pwrctrl *pwrctrl)
{
int ret;
if (!pwrctrl->dev)
return -ENODEV;
pwrctrl->nb.notifier_call = pci_pwrctrl_notify;
ret = bus_register_notifier(&pci_bus_type, &pwrctrl->nb);
if (ret)
return ret;
schedule_work(&pwrctrl->work);
return 0;
}
EXPORT_SYMBOL_GPL(pci_pwrctrl_device_set_ready);
/**
* pci_pwrctrl_device_unset_ready() - Notify the pwrctrl subsystem that the PCI
* device is about to be powered-down.
*
* @pwrctrl: PCI power control data.
*/
void pci_pwrctrl_device_unset_ready(struct pci_pwrctrl *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
* it is being detached then the child PCI device must have already
* been unbound too or the device core wouldn't let us unbind.
*/
bus_unregister_notifier(&pci_bus_type, &pwrctrl->nb);
}
EXPORT_SYMBOL_GPL(pci_pwrctrl_device_unset_ready);
static void devm_pci_pwrctrl_device_unset_ready(void *data)
{
struct pci_pwrctrl *pwrctrl = data;
pci_pwrctrl_device_unset_ready(pwrctrl);
}
/**
* devm_pci_pwrctrl_device_set_ready - Managed variant of
* pci_pwrctrl_device_set_ready().
*
* @dev: Device managing this pwrctrl provider.
* @pwrctrl: PCI power control data.
*
* Returns:
* 0 on success, negative error number on error.
*/
int devm_pci_pwrctrl_device_set_ready(struct device *dev,
struct pci_pwrctrl *pwrctrl)
{
int ret;
ret = pci_pwrctrl_device_set_ready(pwrctrl);
if (ret)
return ret;
return devm_add_action_or_reset(dev,
devm_pci_pwrctrl_device_unset_ready,
pwrctrl);
}
EXPORT_SYMBOL_GPL(devm_pci_pwrctrl_device_set_ready);
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
MODULE_DESCRIPTION("PCI Device Power Control core driver");
MODULE_LICENSE("GPL");

View File

@ -7,27 +7,27 @@
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pci-pwrctl.h>
#include <linux/pci-pwrctrl.h>
#include <linux/platform_device.h>
#include <linux/pwrseq/consumer.h>
#include <linux/slab.h>
#include <linux/types.h>
struct pci_pwrctl_pwrseq_data {
struct pci_pwrctl ctx;
struct pci_pwrctrl_pwrseq_data {
struct pci_pwrctrl ctx;
struct pwrseq_desc *pwrseq;
};
static void devm_pci_pwrctl_pwrseq_power_off(void *data)
static void devm_pci_pwrctrl_pwrseq_power_off(void *data)
{
struct pwrseq_desc *pwrseq = data;
pwrseq_power_off(pwrseq);
}
static int pci_pwrctl_pwrseq_probe(struct platform_device *pdev)
static int pci_pwrctrl_pwrseq_probe(struct platform_device *pdev)
{
struct pci_pwrctl_pwrseq_data *data;
struct pci_pwrctrl_pwrseq_data *data;
struct device *dev = &pdev->dev;
int ret;
@ -45,22 +45,22 @@ static int pci_pwrctl_pwrseq_probe(struct platform_device *pdev)
return dev_err_probe(dev, ret,
"Failed to power-on the device\n");
ret = devm_add_action_or_reset(dev, devm_pci_pwrctl_pwrseq_power_off,
ret = devm_add_action_or_reset(dev, devm_pci_pwrctrl_pwrseq_power_off,
data->pwrseq);
if (ret)
return ret;
pci_pwrctl_init(&data->ctx, dev);
pci_pwrctrl_init(&data->ctx, dev);
ret = devm_pci_pwrctl_device_set_ready(dev, &data->ctx);
ret = devm_pci_pwrctrl_device_set_ready(dev, &data->ctx);
if (ret)
return dev_err_probe(dev, ret,
"Failed to register the pwrctl wrapper\n");
"Failed to register the pwrctrl wrapper\n");
return 0;
}
static const struct of_device_id pci_pwrctl_pwrseq_of_match[] = {
static const struct of_device_id pci_pwrctrl_pwrseq_of_match[] = {
{
/* ATH11K in QCA6390 package. */
.compatible = "pci17cb,1101",
@ -78,16 +78,16 @@ static const struct of_device_id pci_pwrctl_pwrseq_of_match[] = {
},
{ }
};
MODULE_DEVICE_TABLE(of, pci_pwrctl_pwrseq_of_match);
MODULE_DEVICE_TABLE(of, pci_pwrctrl_pwrseq_of_match);
static struct platform_driver pci_pwrctl_pwrseq_driver = {
static struct platform_driver pci_pwrctrl_pwrseq_driver = {
.driver = {
.name = "pci-pwrctl-pwrseq",
.of_match_table = pci_pwrctl_pwrseq_of_match,
.name = "pci-pwrctrl-pwrseq",
.of_match_table = pci_pwrctrl_pwrseq_of_match,
},
.probe = pci_pwrctl_pwrseq_probe,
.probe = pci_pwrctrl_pwrseq_probe,
};
module_platform_driver(pci_pwrctl_pwrseq_driver);
module_platform_driver(pci_pwrctrl_pwrseq_driver);
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
MODULE_DESCRIPTION("Generic PCI Power Control module for power sequenced devices");

View File

@ -17,16 +17,16 @@ static void pci_free_resources(struct pci_dev *dev)
}
}
static int pci_pwrctl_unregister(struct device *dev, void *data)
static void pci_pwrctrl_unregister(struct device *dev)
{
struct device_node *pci_node = data, *plat_node = dev_of_node(dev);
struct platform_device *pdev;
if (dev_is_platform(dev) && plat_node && plat_node == pci_node) {
of_device_unregister(to_platform_device(dev));
of_node_clear_flag(plat_node, OF_POPULATED);
}
pdev = of_find_device_by_node(dev_of_node(dev));
if (!pdev)
return;
return 0;
of_device_unregister(pdev);
of_node_clear_flag(dev_of_node(dev), OF_POPULATED);
}
static void pci_stop_dev(struct pci_dev *dev)
@ -36,8 +36,7 @@ static void pci_stop_dev(struct pci_dev *dev)
if (!pci_dev_test_and_clear_added(dev))
return;
device_for_each_child(dev->dev.parent, dev_of_node(&dev->dev),
pci_pwrctl_unregister);
pci_pwrctrl_unregister(&dev->dev);
device_release_driver(&dev->dev);
pci_proc_detach_device(dev);
pci_remove_sysfs_dev_files(dev);

View File

@ -3,8 +3,8 @@
* Copyright (C) 2024 Linaro Ltd.
*/
#ifndef __PCI_PWRCTL_H__
#define __PCI_PWRCTL_H__
#ifndef __PCI_PWRCTRL_H__
#define __PCI_PWRCTRL_H__
#include <linux/notifier.h>
#include <linux/workqueue.h>
@ -29,14 +29,14 @@ struct device_link;
*/
/**
* struct pci_pwrctl - PCI device power control context.
* struct pci_pwrctrl - PCI device power control context.
* @dev: Address of the power controlling device.
*
* An object of this type must be allocated by the PCI power control device and
* passed to the pwrctl subsystem to trigger a bus rescan and setup a device
* passed to the pwrctrl subsystem to trigger a bus rescan and setup a device
* link with the device once it's up.
*/
struct pci_pwrctl {
struct pci_pwrctrl {
struct device *dev;
/* Private: don't use. */
@ -45,10 +45,10 @@ struct pci_pwrctl {
struct work_struct work;
};
void pci_pwrctl_init(struct pci_pwrctl *pwrctl, struct device *dev);
int pci_pwrctl_device_set_ready(struct pci_pwrctl *pwrctl);
void pci_pwrctl_device_unset_ready(struct pci_pwrctl *pwrctl);
int devm_pci_pwrctl_device_set_ready(struct device *dev,
struct pci_pwrctl *pwrctl);
void pci_pwrctrl_init(struct pci_pwrctrl *pwrctrl, struct device *dev);
int pci_pwrctrl_device_set_ready(struct pci_pwrctrl *pwrctrl);
void pci_pwrctrl_device_unset_ready(struct pci_pwrctrl *pwrctrl);
int devm_pci_pwrctrl_device_set_ready(struct device *dev,
struct pci_pwrctrl *pwrctrl);
#endif /* __PCI_PWRCTL_H__ */
#endif /* __PCI_PWRCTRL_H__ */