Skip to content

Commit e763c9e

Browse files
committed
Merge tag 'pwrseq-updates-for-v6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux
Pull power sequencing updates from Bartosz Golaszewski: "This has been in development since last year's Linux Plumbers Conference and was inspired by the need to enable support upstream for Bluetooth/WLAN chips on Qualcomm platforms. The main problem we're fixing is powering up devices which are represented as separate objects in the kernel (binding to different drivers) but which share parts of the power-up sequence and thus need some kind of a mediator who knows the possible interactions and can assure they don't interfere with neither device's bring up. An example of such an inter-driver interaction is the WCN family of BT/WLAN chips from Qualcomm of which some models require the user to observe a certain delay between driving the bt-enable and wlan-enable GPIOs. This is not a new problem but up to this point all attempts at addressing it ended up hitting one wall or another and being dropped. The main obstacle was the fact that most these attempts tried to introduce the concept of a "power sequence" into the device-tree bindings which breaks the main DT rule: describe the hardware, not its behavior. The solution I proposed focuses on making the power sequencer drivers interpret the actual HW description flexibly. More details on that are in the linked cover letter. The second problem fixed here is powering up PCI devices before they are detected on the bus. This is achieved by creating special platform devices for device-tree nodes describing hard-wired PCI devices which bind to the so-called PCI power control drivers which enable required resources and trigger a bus rescan once the controlled device is up then setup the correct devlink hierarchy for power-management. By combining the two new frameworks we implemented the power sequencing PCI power control driver which is capable of powering up the WLAN modules of the QCom WCN family of chipsets. All this has spent a significant amount of time in linux-next and enabled WLAN/BT support on several Qualcomm platforms. To further prove that this is useful and needed: right after this was picked up into next, I was sent a series using the subsystem for a similar use-case on Amlogic platforms. This contains the core power sequencing framework, the first driver, PCI changes using the pwrseq library (blessed by Bjorn Helgaas) and some fixes that came later" * tag 'pwrseq-updates-for-v6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: PCI/pwrctl: only call of_platform_populate() if CONFIG_OF is enabled power: sequencing: simplify returning pointer without cleanup PCI/pwrctl: Add a PCI power control driver for power sequenced devices PCI/pwrctl: Add PCI power control core code PCI/pwrctl: Create platform devices for child OF nodes of the port node PCI/pwrctl: Reuse the OF node for power controlled devices PCI: Hold the rescan mutex when scanning for the first time power: pwrseq: add a driver for the PMU module on the QCom WCN chipsets power: sequencing: implement the pwrseq core
2 parents cdf471c + 50b040e commit e763c9e

File tree

20 files changed

+1950
-5
lines changed

20 files changed

+1950
-5
lines changed

MAINTAINERS

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17361,6 +17361,14 @@ F: Documentation/driver-api/pci/p2pdma.rst
1736117361
F: drivers/pci/p2pdma.c
1736217362
F: include/linux/pci-p2pdma.h
1736317363

17364+
PCI POWER CONTROL
17365+
M: Bartosz Golaszewski <[email protected]>
17366+
17367+
S: Maintained
17368+
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git
17369+
F: drivers/pci/pwrctl/*
17370+
F: include/linux/pci-pwrctl.h
17371+
1736417372
PCI SUBSYSTEM
1736517373
M: Bjorn Helgaas <[email protected]>
1736617374
@@ -17901,6 +17909,14 @@ F: include/linux/pm_*
1790117909
F: include/linux/powercap.h
1790217910
F: kernel/configs/nopm.config
1790317911

17912+
POWER SEQUENCING
17913+
M: Bartosz Golaszewski <[email protected]>
17914+
17915+
S: Maintained
17916+
T: git git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git
17917+
F: drivers/power/sequencing/
17918+
F: include/linux/pwrseq/
17919+
1790417920
POWER STATE COORDINATION INTERFACE (PSCI)
1790517921
M: Mark Rutland <[email protected]>
1790617922
M: Lorenzo Pieralisi <[email protected]>

drivers/pci/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,5 +296,6 @@ source "drivers/pci/hotplug/Kconfig"
296296
source "drivers/pci/controller/Kconfig"
297297
source "drivers/pci/endpoint/Kconfig"
298298
source "drivers/pci/switch/Kconfig"
299+
source "drivers/pci/pwrctl/Kconfig"
299300

300301
endif

drivers/pci/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \
99

1010
obj-$(CONFIG_PCI) += msi/
1111
obj-$(CONFIG_PCI) += pcie/
12+
obj-$(CONFIG_PCI) += pwrctl/
1213

1314
ifdef CONFIG_PCI
1415
obj-$(CONFIG_PROC_FS) += proc.o

drivers/pci/bus.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <linux/errno.h>
1313
#include <linux/ioport.h>
1414
#include <linux/of.h>
15+
#include <linux/of_platform.h>
1516
#include <linux/proc_fs.h>
1617
#include <linux/slab.h>
1718

@@ -354,6 +355,14 @@ void pci_bus_add_device(struct pci_dev *dev)
354355
pci_warn(dev, "device attach failed (%d)\n", retval);
355356

356357
pci_dev_assign_added(dev, true);
358+
359+
if (IS_ENABLED(CONFIG_OF) && pci_is_bridge(dev)) {
360+
retval = of_platform_populate(dev->dev.of_node, NULL, NULL,
361+
&dev->dev);
362+
if (retval)
363+
pci_err(dev, "failed to populate child OF nodes (%d)\n",
364+
retval);
365+
}
357366
}
358367
EXPORT_SYMBOL_GPL(pci_bus_add_device);
359368

drivers/pci/of.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
*/
77
#define pr_fmt(fmt) "PCI: OF: " fmt
88

9+
#include <linux/cleanup.h>
910
#include <linux/irqdomain.h>
1011
#include <linux/kernel.h>
1112
#include <linux/pci.h>
1213
#include <linux/of.h>
1314
#include <linux/of_irq.h>
1415
#include <linux/of_address.h>
1516
#include <linux/of_pci.h>
17+
#include <linux/platform_device.h>
1618
#include "pci.h"
1719

1820
#ifdef CONFIG_PCI
@@ -25,16 +27,20 @@
2527
*/
2628
int pci_set_of_node(struct pci_dev *dev)
2729
{
28-
struct device_node *node;
29-
3030
if (!dev->bus->dev.of_node)
3131
return 0;
3232

33-
node = of_pci_find_child_device(dev->bus->dev.of_node, dev->devfn);
33+
struct device_node *node __free(device_node) =
34+
of_pci_find_child_device(dev->bus->dev.of_node, dev->devfn);
3435
if (!node)
3536
return 0;
3637

37-
device_set_node(&dev->dev, of_fwnode_handle(node));
38+
struct device *pdev __free(put_device) =
39+
bus_find_device_by_of_node(&platform_bus_type, node);
40+
if (pdev)
41+
dev->bus->dev.of_node_reused = true;
42+
43+
device_set_node(&dev->dev, of_fwnode_handle(no_free_ptr(node)));
3844
return 0;
3945
}
4046

drivers/pci/probe.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3069,7 +3069,9 @@ int pci_host_probe(struct pci_host_bridge *bridge)
30693069
struct pci_bus *bus, *child;
30703070
int ret;
30713071

3072+
pci_lock_rescan_remove();
30723073
ret = pci_scan_root_bus_bridge(bridge);
3074+
pci_unlock_rescan_remove();
30733075
if (ret < 0) {
30743076
dev_err(bridge->dev.parent, "Scanning root bridge failed");
30753077
return ret;

drivers/pci/pwrctl/Kconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
3+
menu "PCI Power control drivers"
4+
5+
config PCI_PWRCTL
6+
tristate
7+
8+
config PCI_PWRCTL_PWRSEQ
9+
tristate "PCI Power Control driver using the Power Sequencing subsystem"
10+
select POWER_SEQUENCING
11+
select PCI_PWRCTL
12+
default m if ((ATH11K_PCI || ATH12K) && ARCH_QCOM)
13+
help
14+
Enable support for the PCI power control driver for device
15+
drivers using the Power Sequencing subsystem.
16+
17+
endmenu

drivers/pci/pwrctl/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
3+
obj-$(CONFIG_PCI_PWRCTL) += pci-pwrctl-core.o
4+
pci-pwrctl-core-y := core.o
5+
6+
obj-$(CONFIG_PCI_PWRCTL_PWRSEQ) += pci-pwrctl-pwrseq.o

drivers/pci/pwrctl/core.c

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright (C) 2024 Linaro Ltd.
4+
*/
5+
6+
#include <linux/device.h>
7+
#include <linux/export.h>
8+
#include <linux/kernel.h>
9+
#include <linux/pci.h>
10+
#include <linux/pci-pwrctl.h>
11+
#include <linux/property.h>
12+
#include <linux/slab.h>
13+
14+
static int pci_pwrctl_notify(struct notifier_block *nb, unsigned long action,
15+
void *data)
16+
{
17+
struct pci_pwrctl *pwrctl = container_of(nb, struct pci_pwrctl, nb);
18+
struct device *dev = data;
19+
20+
if (dev_fwnode(dev) != dev_fwnode(pwrctl->dev))
21+
return NOTIFY_DONE;
22+
23+
switch (action) {
24+
case BUS_NOTIFY_ADD_DEVICE:
25+
/*
26+
* We will have two struct device objects bound to two different
27+
* drivers on different buses but consuming the same DT node. We
28+
* must not bind the pins twice in this case but only once for
29+
* the first device to be added.
30+
*
31+
* If we got here then the PCI device is the second after the
32+
* power control platform device. Mark its OF node as reused.
33+
*/
34+
dev->of_node_reused = true;
35+
break;
36+
case BUS_NOTIFY_BOUND_DRIVER:
37+
pwrctl->link = device_link_add(dev, pwrctl->dev,
38+
DL_FLAG_AUTOREMOVE_CONSUMER);
39+
if (!pwrctl->link)
40+
dev_err(pwrctl->dev, "Failed to add device link\n");
41+
break;
42+
case BUS_NOTIFY_UNBOUND_DRIVER:
43+
if (pwrctl->link)
44+
device_link_remove(dev, pwrctl->dev);
45+
break;
46+
}
47+
48+
return NOTIFY_DONE;
49+
}
50+
51+
/**
52+
* pci_pwrctl_device_set_ready() - Notify the pwrctl subsystem that the PCI
53+
* device is powered-up and ready to be detected.
54+
*
55+
* @pwrctl: PCI power control data.
56+
*
57+
* Returns:
58+
* 0 on success, negative error number on error.
59+
*
60+
* Note:
61+
* This function returning 0 doesn't mean the device was detected. It means,
62+
* that the bus rescan was successfully started. The device will get bound to
63+
* its PCI driver asynchronously.
64+
*/
65+
int pci_pwrctl_device_set_ready(struct pci_pwrctl *pwrctl)
66+
{
67+
int ret;
68+
69+
if (!pwrctl->dev)
70+
return -ENODEV;
71+
72+
pwrctl->nb.notifier_call = pci_pwrctl_notify;
73+
ret = bus_register_notifier(&pci_bus_type, &pwrctl->nb);
74+
if (ret)
75+
return ret;
76+
77+
pci_lock_rescan_remove();
78+
pci_rescan_bus(to_pci_dev(pwrctl->dev->parent)->bus);
79+
pci_unlock_rescan_remove();
80+
81+
return 0;
82+
}
83+
EXPORT_SYMBOL_GPL(pci_pwrctl_device_set_ready);
84+
85+
/**
86+
* pci_pwrctl_device_unset_ready() - Notify the pwrctl subsystem that the PCI
87+
* device is about to be powered-down.
88+
*
89+
* @pwrctl: PCI power control data.
90+
*/
91+
void pci_pwrctl_device_unset_ready(struct pci_pwrctl *pwrctl)
92+
{
93+
/*
94+
* We don't have to delete the link here. Typically, this function
95+
* is only called when the power control device is being detached. If
96+
* it is being detached then the child PCI device must have already
97+
* been unbound too or the device core wouldn't let us unbind.
98+
*/
99+
bus_unregister_notifier(&pci_bus_type, &pwrctl->nb);
100+
}
101+
EXPORT_SYMBOL_GPL(pci_pwrctl_device_unset_ready);
102+
103+
static void devm_pci_pwrctl_device_unset_ready(void *data)
104+
{
105+
struct pci_pwrctl *pwrctl = data;
106+
107+
pci_pwrctl_device_unset_ready(pwrctl);
108+
}
109+
110+
/**
111+
* devm_pci_pwrctl_device_set_ready - Managed variant of
112+
* pci_pwrctl_device_set_ready().
113+
*
114+
* @dev: Device managing this pwrctl provider.
115+
* @pwrctl: PCI power control data.
116+
*
117+
* Returns:
118+
* 0 on success, negative error number on error.
119+
*/
120+
int devm_pci_pwrctl_device_set_ready(struct device *dev,
121+
struct pci_pwrctl *pwrctl)
122+
{
123+
int ret;
124+
125+
ret = pci_pwrctl_device_set_ready(pwrctl);
126+
if (ret)
127+
return ret;
128+
129+
return devm_add_action_or_reset(dev,
130+
devm_pci_pwrctl_device_unset_ready,
131+
pwrctl);
132+
}
133+
EXPORT_SYMBOL_GPL(devm_pci_pwrctl_device_set_ready);
134+
135+
MODULE_AUTHOR("Bartosz Golaszewski <[email protected]>");
136+
MODULE_DESCRIPTION("PCI Device Power Control core driver");
137+
MODULE_LICENSE("GPL");
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright (C) 2024 Linaro Ltd.
4+
*/
5+
6+
#include <linux/device.h>
7+
#include <linux/mod_devicetable.h>
8+
#include <linux/module.h>
9+
#include <linux/of.h>
10+
#include <linux/pci-pwrctl.h>
11+
#include <linux/platform_device.h>
12+
#include <linux/pwrseq/consumer.h>
13+
#include <linux/slab.h>
14+
#include <linux/types.h>
15+
16+
struct pci_pwrctl_pwrseq_data {
17+
struct pci_pwrctl ctx;
18+
struct pwrseq_desc *pwrseq;
19+
};
20+
21+
static void devm_pci_pwrctl_pwrseq_power_off(void *data)
22+
{
23+
struct pwrseq_desc *pwrseq = data;
24+
25+
pwrseq_power_off(pwrseq);
26+
}
27+
28+
static int pci_pwrctl_pwrseq_probe(struct platform_device *pdev)
29+
{
30+
struct pci_pwrctl_pwrseq_data *data;
31+
struct device *dev = &pdev->dev;
32+
int ret;
33+
34+
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
35+
if (!data)
36+
return -ENOMEM;
37+
38+
data->pwrseq = devm_pwrseq_get(dev, of_device_get_match_data(dev));
39+
if (IS_ERR(data->pwrseq))
40+
return dev_err_probe(dev, PTR_ERR(data->pwrseq),
41+
"Failed to get the power sequencer\n");
42+
43+
ret = pwrseq_power_on(data->pwrseq);
44+
if (ret)
45+
return dev_err_probe(dev, ret,
46+
"Failed to power-on the device\n");
47+
48+
ret = devm_add_action_or_reset(dev, devm_pci_pwrctl_pwrseq_power_off,
49+
data->pwrseq);
50+
if (ret)
51+
return ret;
52+
53+
data->ctx.dev = dev;
54+
55+
ret = devm_pci_pwrctl_device_set_ready(dev, &data->ctx);
56+
if (ret)
57+
return dev_err_probe(dev, ret,
58+
"Failed to register the pwrctl wrapper\n");
59+
60+
return 0;
61+
}
62+
63+
static const struct of_device_id pci_pwrctl_pwrseq_of_match[] = {
64+
{
65+
/* ATH11K in QCA6390 package. */
66+
.compatible = "pci17cb,1101",
67+
.data = "wlan",
68+
},
69+
{
70+
/* ATH12K in WCN7850 package. */
71+
.compatible = "pci17cb,1107",
72+
.data = "wlan",
73+
},
74+
{ }
75+
};
76+
MODULE_DEVICE_TABLE(of, pci_pwrctl_pwrseq_of_match);
77+
78+
static struct platform_driver pci_pwrctl_pwrseq_driver = {
79+
.driver = {
80+
.name = "pci-pwrctl-pwrseq",
81+
.of_match_table = pci_pwrctl_pwrseq_of_match,
82+
},
83+
.probe = pci_pwrctl_pwrseq_probe,
84+
};
85+
module_platform_driver(pci_pwrctl_pwrseq_driver);
86+
87+
MODULE_AUTHOR("Bartosz Golaszewski <[email protected]>");
88+
MODULE_DESCRIPTION("Generic PCI Power Control module for power sequenced devices");
89+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)