mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 06:01:57 +00:00
pwrseq updates for v6.11-rc1
- add the pwrseq core framework - add the first power sequencing driver: pwrseq-qcom-wcn - add power control (pwrctl) changes to PCI core - add the first PCI pwrctl power sequencing driver -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEFp3rbAvDxGAT0sefEacuoBRx13IFAmaQ72wACgkQEacuoBRx 13K4fg//Qd8vxOH06/VSJRCwEvilUfYgDe/WiqTcBXL+8cK/3B0fUhfD83wgMnJn /yw2GgS5OjAvYe47nvM4T5M/XMpQ967XTED9cXxJWpBwcee+LZ6hsBGlPuSRunpo DQ4EuDh8wQ4j8Gw5pXj7dgtCN7zx4Idj+amh9i2ep+/7ZqT9UFqe294Tq9pX0X+a rhOrTooQo4uiwpSAh6k1db3o1VpVDhlx+m2CCQV6V5F3r7eiPv2tQE2B8GvlJlY/ KjGECsbxKMch6dtKQumRnn3R07v1b3SlQOQOSP99blNv8dfDBOKS0dFPI4se2n2b FvKCtmfC9QD9MPvTFKGSIZTTLMInfGpniLyxMD3Ubu+l26AA7eLpv4BICExFo71c 5gM/Xm8ypsrW7WadhAsl0mNS1CpbFZ2jNTBtuBrP5AFTMOvPqmYbN0VjRgYbX0Yw qXyu540iP8t8c0eTqRyrabhXFEehTso5cJGqtKvIARuoK1rF+wOPxTBU+RHjJWg/ xp7ckCGP+VymcD1xNGpHPweHxwu0z2RMI4zYg2i6WOjWz6FY/V2x1Pf3PGlXF7pq yaTMqK5tiGizCF2/RNFGDFQ8re0qtZfh6/mNtT45GGbUBaYltPGQFdFhsaI6rwEi LMO7M387u0fPJlS0/ib8I+S5ZfKo65xjC/jQSuUL8WfQf85Ck9k= =rI5g -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
e763c9ec71
16
MAINTAINERS
16
MAINTAINERS
@ -17361,6 +17361,14 @@ F: Documentation/driver-api/pci/p2pdma.rst
|
||||
F: drivers/pci/p2pdma.c
|
||||
F: include/linux/pci-p2pdma.h
|
||||
|
||||
PCI POWER CONTROL
|
||||
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
|
||||
|
||||
PCI SUBSYSTEM
|
||||
M: Bjorn Helgaas <bhelgaas@google.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
@ -17901,6 +17909,14 @@ F: include/linux/pm_*
|
||||
F: include/linux/powercap.h
|
||||
F: kernel/configs/nopm.config
|
||||
|
||||
POWER SEQUENCING
|
||||
M: Bartosz Golaszewski <brgl@bgdev.pl>
|
||||
L: linux-pm@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git
|
||||
F: drivers/power/sequencing/
|
||||
F: include/linux/pwrseq/
|
||||
|
||||
POWER STATE COORDINATION INTERFACE (PSCI)
|
||||
M: Mark Rutland <mark.rutland@arm.com>
|
||||
M: Lorenzo Pieralisi <lpieralisi@kernel.org>
|
||||
|
@ -296,5 +296,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"
|
||||
|
||||
endif
|
||||
|
@ -9,6 +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/
|
||||
|
||||
ifdef CONFIG_PCI
|
||||
obj-$(CONFIG_PROC_FS) += proc.o
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <linux/errno.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
@ -354,6 +355,14 @@ void pci_bus_add_device(struct pci_dev *dev)
|
||||
pci_warn(dev, "device attach failed (%d)\n", retval);
|
||||
|
||||
pci_dev_assign_added(dev, true);
|
||||
|
||||
if (IS_ENABLED(CONFIG_OF) && pci_is_bridge(dev)) {
|
||||
retval = of_platform_populate(dev->dev.of_node, 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);
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
*/
|
||||
#define pr_fmt(fmt) "PCI: OF: " fmt
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
@ -13,6 +14,7 @@
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include "pci.h"
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
@ -25,16 +27,20 @@
|
||||
*/
|
||||
int pci_set_of_node(struct pci_dev *dev)
|
||||
{
|
||||
struct device_node *node;
|
||||
|
||||
if (!dev->bus->dev.of_node)
|
||||
return 0;
|
||||
|
||||
node = of_pci_find_child_device(dev->bus->dev.of_node, dev->devfn);
|
||||
struct device_node *node __free(device_node) =
|
||||
of_pci_find_child_device(dev->bus->dev.of_node, dev->devfn);
|
||||
if (!node)
|
||||
return 0;
|
||||
|
||||
device_set_node(&dev->dev, of_fwnode_handle(node));
|
||||
struct device *pdev __free(put_device) =
|
||||
bus_find_device_by_of_node(&platform_bus_type, node);
|
||||
if (pdev)
|
||||
dev->bus->dev.of_node_reused = true;
|
||||
|
||||
device_set_node(&dev->dev, of_fwnode_handle(no_free_ptr(node)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -3069,7 +3069,9 @@ int pci_host_probe(struct pci_host_bridge *bridge)
|
||||
struct pci_bus *bus, *child;
|
||||
int ret;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
ret = pci_scan_root_bus_bridge(bridge);
|
||||
pci_unlock_rescan_remove();
|
||||
if (ret < 0) {
|
||||
dev_err(bridge->dev.parent, "Scanning root bridge failed");
|
||||
return ret;
|
||||
|
17
drivers/pci/pwrctl/Kconfig
Normal file
17
drivers/pci/pwrctl/Kconfig
Normal file
@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
menu "PCI Power control drivers"
|
||||
|
||||
config PCI_PWRCTL
|
||||
tristate
|
||||
|
||||
config PCI_PWRCTL_PWRSEQ
|
||||
tristate "PCI Power Control driver using the Power Sequencing subsystem"
|
||||
select POWER_SEQUENCING
|
||||
select PCI_PWRCTL
|
||||
default m if ((ATH11K_PCI || ATH12K) && ARCH_QCOM)
|
||||
help
|
||||
Enable support for the PCI power control driver for device
|
||||
drivers using the Power Sequencing subsystem.
|
||||
|
||||
endmenu
|
6
drivers/pci/pwrctl/Makefile
Normal file
6
drivers/pci/pwrctl/Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
# 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
|
137
drivers/pci/pwrctl/core.c
Normal file
137
drivers/pci/pwrctl/core.c
Normal file
@ -0,0 +1,137 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_rescan_bus(to_pci_dev(pwrctl->dev->parent)->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
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");
|
89
drivers/pci/pwrctl/pci-pwrctl-pwrseq.c
Normal file
89
drivers/pci/pwrctl/pci-pwrctl-pwrseq.c
Normal file
@ -0,0 +1,89 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2024 Linaro Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pci-pwrctl.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 pwrseq_desc *pwrseq;
|
||||
};
|
||||
|
||||
static void devm_pci_pwrctl_pwrseq_power_off(void *data)
|
||||
{
|
||||
struct pwrseq_desc *pwrseq = data;
|
||||
|
||||
pwrseq_power_off(pwrseq);
|
||||
}
|
||||
|
||||
static int pci_pwrctl_pwrseq_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pci_pwrctl_pwrseq_data *data;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->pwrseq = devm_pwrseq_get(dev, of_device_get_match_data(dev));
|
||||
if (IS_ERR(data->pwrseq))
|
||||
return dev_err_probe(dev, PTR_ERR(data->pwrseq),
|
||||
"Failed to get the power sequencer\n");
|
||||
|
||||
ret = pwrseq_power_on(data->pwrseq);
|
||||
if (ret)
|
||||
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,
|
||||
data->pwrseq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->ctx.dev = dev;
|
||||
|
||||
ret = devm_pci_pwrctl_device_set_ready(dev, &data->ctx);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to register the pwrctl wrapper\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id pci_pwrctl_pwrseq_of_match[] = {
|
||||
{
|
||||
/* ATH11K in QCA6390 package. */
|
||||
.compatible = "pci17cb,1101",
|
||||
.data = "wlan",
|
||||
},
|
||||
{
|
||||
/* ATH12K in WCN7850 package. */
|
||||
.compatible = "pci17cb,1107",
|
||||
.data = "wlan",
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pci_pwrctl_pwrseq_of_match);
|
||||
|
||||
static struct platform_driver pci_pwrctl_pwrseq_driver = {
|
||||
.driver = {
|
||||
.name = "pci-pwrctl-pwrseq",
|
||||
.of_match_table = pci_pwrctl_pwrseq_of_match,
|
||||
},
|
||||
.probe = pci_pwrctl_pwrseq_probe,
|
||||
};
|
||||
module_platform_driver(pci_pwrctl_pwrseq_driver);
|
||||
|
||||
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
|
||||
MODULE_DESCRIPTION("Generic PCI Power Control module for power sequenced devices");
|
||||
MODULE_LICENSE("GPL");
|
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/pci.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include "pci.h"
|
||||
|
||||
static void pci_free_resources(struct pci_dev *dev)
|
||||
@ -18,7 +19,7 @@ static void pci_stop_dev(struct pci_dev *dev)
|
||||
pci_pme_active(dev, false);
|
||||
|
||||
if (pci_dev_is_added(dev)) {
|
||||
|
||||
of_platform_depopulate(&dev->dev);
|
||||
device_release_driver(&dev->dev);
|
||||
pci_proc_detach_device(dev);
|
||||
pci_remove_sysfs_dev_files(dev);
|
||||
|
@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
source "drivers/power/reset/Kconfig"
|
||||
source "drivers/power/sequencing/Kconfig"
|
||||
source "drivers/power/supply/Kconfig"
|
||||
|
@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_POWER_RESET) += reset/
|
||||
obj-$(CONFIG_POWER_SEQUENCING) += sequencing/
|
||||
obj-$(CONFIG_POWER_SUPPLY) += supply/
|
||||
|
29
drivers/power/sequencing/Kconfig
Normal file
29
drivers/power/sequencing/Kconfig
Normal file
@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
menuconfig POWER_SEQUENCING
|
||||
tristate "Power Sequencing support"
|
||||
help
|
||||
Say Y here to enable the Power Sequencing subsystem.
|
||||
|
||||
This subsystem is designed to control power to devices that share
|
||||
complex resources and/or require specific power sequences to be run
|
||||
during power-up.
|
||||
|
||||
If unsure, say no.
|
||||
|
||||
if POWER_SEQUENCING
|
||||
|
||||
config POWER_SEQUENCING_QCOM_WCN
|
||||
tristate "Qualcomm WCN family PMU driver"
|
||||
default m if ARCH_QCOM
|
||||
help
|
||||
Say Y here to enable the power sequencing driver for Qualcomm
|
||||
WCN Bluetooth/WLAN chipsets.
|
||||
|
||||
Typically, a package from the Qualcomm WCN family contains the BT
|
||||
and WLAN modules whose power is controlled by the PMU module. As the
|
||||
former two share the power-up sequence which is executed by the PMU,
|
||||
this driver is needed for correct power control or else we'd risk not
|
||||
respecting the required delays between enabling Bluetooth and WLAN.
|
||||
|
||||
endif
|
6
drivers/power/sequencing/Makefile
Normal file
6
drivers/power/sequencing/Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o
|
||||
pwrseq-core-y := core.o
|
||||
|
||||
obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o
|
1105
drivers/power/sequencing/core.c
Normal file
1105
drivers/power/sequencing/core.c
Normal file
File diff suppressed because it is too large
Load Diff
336
drivers/power/sequencing/pwrseq-qcom-wcn.c
Normal file
336
drivers/power/sequencing/pwrseq-qcom-wcn.c
Normal file
@ -0,0 +1,336 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2024 Linaro Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/pwrseq/provider.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct pwrseq_qcom_wcn_pdata {
|
||||
const char *const *vregs;
|
||||
size_t num_vregs;
|
||||
unsigned int pwup_delay_ms;
|
||||
unsigned int gpio_enable_delay_ms;
|
||||
};
|
||||
|
||||
struct pwrseq_qcom_wcn_ctx {
|
||||
struct pwrseq_device *pwrseq;
|
||||
struct device_node *of_node;
|
||||
const struct pwrseq_qcom_wcn_pdata *pdata;
|
||||
struct regulator_bulk_data *regs;
|
||||
struct gpio_desc *bt_gpio;
|
||||
struct gpio_desc *wlan_gpio;
|
||||
struct clk *clk;
|
||||
unsigned long last_gpio_enable_jf;
|
||||
};
|
||||
|
||||
static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx)
|
||||
{
|
||||
unsigned long diff_jiffies;
|
||||
unsigned int diff_msecs;
|
||||
|
||||
if (!ctx->pdata->gpio_enable_delay_ms)
|
||||
return;
|
||||
|
||||
diff_jiffies = jiffies - ctx->last_gpio_enable_jf;
|
||||
diff_msecs = jiffies_to_msecs(diff_jiffies);
|
||||
|
||||
if (diff_msecs < ctx->pdata->gpio_enable_delay_ms)
|
||||
msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs);
|
||||
}
|
||||
|
||||
static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
return regulator_bulk_enable(ctx->pdata->num_vregs, ctx->regs);
|
||||
}
|
||||
|
||||
static int pwrseq_qcom_wcn_vregs_disable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
return regulator_bulk_disable(ctx->pdata->num_vregs, ctx->regs);
|
||||
}
|
||||
|
||||
static const struct pwrseq_unit_data pwrseq_qcom_wcn_vregs_unit_data = {
|
||||
.name = "regulators-enable",
|
||||
.enable = pwrseq_qcom_wcn_vregs_enable,
|
||||
.disable = pwrseq_qcom_wcn_vregs_disable,
|
||||
};
|
||||
|
||||
static int pwrseq_qcom_wcn_clk_enable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
return clk_prepare_enable(ctx->clk);
|
||||
}
|
||||
|
||||
static int pwrseq_qcom_wcn_clk_disable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
clk_disable_unprepare(ctx->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = {
|
||||
.name = "clock-enable",
|
||||
.enable = pwrseq_qcom_wcn_clk_enable,
|
||||
.disable = pwrseq_qcom_wcn_clk_disable,
|
||||
};
|
||||
|
||||
static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = {
|
||||
&pwrseq_qcom_wcn_vregs_unit_data,
|
||||
&pwrseq_qcom_wcn_clk_unit_data,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int pwrseq_qcom_wcn_bt_enable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
pwrseq_qcom_wcn_ensure_gpio_delay(ctx);
|
||||
gpiod_set_value_cansleep(ctx->bt_gpio, 1);
|
||||
ctx->last_gpio_enable_jf = jiffies;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
gpiod_set_value_cansleep(ctx->bt_gpio, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = {
|
||||
.name = "bluetooth-enable",
|
||||
.deps = pwrseq_qcom_wcn_unit_deps,
|
||||
.enable = pwrseq_qcom_wcn_bt_enable,
|
||||
.disable = pwrseq_qcom_wcn_bt_disable,
|
||||
};
|
||||
|
||||
static int pwrseq_qcom_wcn_wlan_enable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
pwrseq_qcom_wcn_ensure_gpio_delay(ctx);
|
||||
gpiod_set_value_cansleep(ctx->wlan_gpio, 1);
|
||||
ctx->last_gpio_enable_jf = jiffies;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
gpiod_set_value_cansleep(ctx->wlan_gpio, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = {
|
||||
.name = "wlan-enable",
|
||||
.deps = pwrseq_qcom_wcn_unit_deps,
|
||||
.enable = pwrseq_qcom_wcn_wlan_enable,
|
||||
.disable = pwrseq_qcom_wcn_wlan_disable,
|
||||
};
|
||||
|
||||
static int pwrseq_qcom_wcn_pwup_delay(struct pwrseq_device *pwrseq)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
|
||||
if (ctx->pdata->pwup_delay_ms)
|
||||
msleep(ctx->pdata->pwup_delay_ms);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwrseq_target_data pwrseq_qcom_wcn_bt_target_data = {
|
||||
.name = "bluetooth",
|
||||
.unit = &pwrseq_qcom_wcn_bt_unit_data,
|
||||
.post_enable = pwrseq_qcom_wcn_pwup_delay,
|
||||
};
|
||||
|
||||
static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = {
|
||||
.name = "wlan",
|
||||
.unit = &pwrseq_qcom_wcn_wlan_unit_data,
|
||||
.post_enable = pwrseq_qcom_wcn_pwup_delay,
|
||||
};
|
||||
|
||||
static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = {
|
||||
&pwrseq_qcom_wcn_bt_target_data,
|
||||
&pwrseq_qcom_wcn_wlan_target_data,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const pwrseq_qca6390_vregs[] = {
|
||||
"vddio",
|
||||
"vddaon",
|
||||
"vddpmu",
|
||||
"vddrfa0p95",
|
||||
"vddrfa1p3",
|
||||
"vddrfa1p9",
|
||||
"vddpcie1p3",
|
||||
"vddpcie1p9",
|
||||
};
|
||||
|
||||
static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = {
|
||||
.vregs = pwrseq_qca6390_vregs,
|
||||
.num_vregs = ARRAY_SIZE(pwrseq_qca6390_vregs),
|
||||
.pwup_delay_ms = 60,
|
||||
.gpio_enable_delay_ms = 100,
|
||||
};
|
||||
|
||||
static const char *const pwrseq_wcn7850_vregs[] = {
|
||||
"vdd",
|
||||
"vddio",
|
||||
"vddio1p2",
|
||||
"vddaon",
|
||||
"vdddig",
|
||||
"vddrfa1p2",
|
||||
"vddrfa1p8",
|
||||
};
|
||||
|
||||
static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = {
|
||||
.vregs = pwrseq_wcn7850_vregs,
|
||||
.num_vregs = ARRAY_SIZE(pwrseq_wcn7850_vregs),
|
||||
.pwup_delay_ms = 50,
|
||||
};
|
||||
|
||||
static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
|
||||
struct device *dev)
|
||||
{
|
||||
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
|
||||
struct device_node *dev_node = dev->of_node;
|
||||
|
||||
/*
|
||||
* The PMU supplies power to the Bluetooth and WLAN modules. both
|
||||
* consume the PMU AON output so check the presence of the
|
||||
* 'vddaon-supply' property and whether it leads us to the right
|
||||
* device.
|
||||
*/
|
||||
if (!of_property_present(dev_node, "vddaon-supply"))
|
||||
return 0;
|
||||
|
||||
struct device_node *reg_node __free(device_node) =
|
||||
of_parse_phandle(dev_node, "vddaon-supply", 0);
|
||||
if (!reg_node)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* `reg_node` is the PMU AON regulator, its parent is the `regulators`
|
||||
* node and finally its grandparent is the PMU device node that we're
|
||||
* looking for.
|
||||
*/
|
||||
if (!reg_node->parent || !reg_node->parent->parent ||
|
||||
reg_node->parent->parent != ctx->of_node)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pwrseq_qcom_wcn_ctx *ctx;
|
||||
struct pwrseq_config config;
|
||||
int i, ret;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx->of_node = dev->of_node;
|
||||
|
||||
ctx->pdata = of_device_get_match_data(dev);
|
||||
if (!ctx->pdata)
|
||||
return dev_err_probe(dev, -ENODEV,
|
||||
"Failed to obtain platform data\n");
|
||||
|
||||
ctx->regs = devm_kcalloc(dev, ctx->pdata->num_vregs,
|
||||
sizeof(*ctx->regs), GFP_KERNEL);
|
||||
if (!ctx->regs)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < ctx->pdata->num_vregs; i++)
|
||||
ctx->regs[i].supply = ctx->pdata->vregs[i];
|
||||
|
||||
ret = devm_regulator_bulk_get(dev, ctx->pdata->num_vregs, ctx->regs);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to get all regulators\n");
|
||||
|
||||
ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(ctx->bt_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio),
|
||||
"Failed to get the Bluetooth enable GPIO\n");
|
||||
|
||||
ctx->wlan_gpio = devm_gpiod_get_optional(dev, "wlan-enable",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(ctx->wlan_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->wlan_gpio),
|
||||
"Failed to get the WLAN enable GPIO\n");
|
||||
|
||||
ctx->clk = devm_clk_get_optional(dev, NULL);
|
||||
if (IS_ERR(ctx->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->clk),
|
||||
"Failed to get the reference clock\n");
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
config.parent = dev;
|
||||
config.owner = THIS_MODULE;
|
||||
config.drvdata = ctx;
|
||||
config.match = pwrseq_qcom_wcn_match;
|
||||
config.targets = pwrseq_qcom_wcn_targets;
|
||||
|
||||
ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
|
||||
if (IS_ERR(ctx->pwrseq))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
|
||||
"Failed to register the power sequencer\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id pwrseq_qcom_wcn_of_match[] = {
|
||||
{
|
||||
.compatible = "qcom,qca6390-pmu",
|
||||
.data = &pwrseq_qca6390_of_data,
|
||||
},
|
||||
{
|
||||
.compatible = "qcom,wcn7850-pmu",
|
||||
.data = &pwrseq_wcn7850_of_data,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pwrseq_qcom_wcn_of_match);
|
||||
|
||||
static struct platform_driver pwrseq_qcom_wcn_driver = {
|
||||
.driver = {
|
||||
.name = "pwrseq-qcom_wcn",
|
||||
.of_match_table = pwrseq_qcom_wcn_of_match,
|
||||
},
|
||||
.probe = pwrseq_qcom_wcn_probe,
|
||||
};
|
||||
module_platform_driver(pwrseq_qcom_wcn_driver);
|
||||
|
||||
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
|
||||
MODULE_DESCRIPTION("Qualcomm WCN PMU power sequencing driver");
|
||||
MODULE_LICENSE("GPL");
|
51
include/linux/pci-pwrctl.h
Normal file
51
include/linux/pci-pwrctl.h
Normal file
@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2024 Linaro Ltd.
|
||||
*/
|
||||
|
||||
#ifndef __PCI_PWRCTL_H__
|
||||
#define __PCI_PWRCTL_H__
|
||||
|
||||
#include <linux/notifier.h>
|
||||
|
||||
struct device;
|
||||
struct device_link;
|
||||
|
||||
/*
|
||||
* This is a simple framework for solving the issue of PCI devices that require
|
||||
* certain resources (regulators, GPIOs, clocks) to be enabled before the
|
||||
* device can actually be detected on the PCI bus.
|
||||
*
|
||||
* The idea is to reuse the platform bus to populate OF nodes describing the
|
||||
* PCI device and its resources, let these platform devices probe and enable
|
||||
* relevant resources and then trigger a rescan of the PCI bus allowing for the
|
||||
* same device (with a second associated struct device) to be registered with
|
||||
* the PCI subsystem.
|
||||
*
|
||||
* To preserve a correct hierarchy for PCI power management and device reset,
|
||||
* we create a device link between the power control platform device (parent)
|
||||
* and the supplied PCI device (child).
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct pci_pwrctl - 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
|
||||
* link with the device once it's up.
|
||||
*/
|
||||
struct pci_pwrctl {
|
||||
struct device *dev;
|
||||
|
||||
/* Private: don't use. */
|
||||
struct notifier_block nb;
|
||||
struct device_link *link;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
#endif /* __PCI_PWRCTL_H__ */
|
56
include/linux/pwrseq/consumer.h
Normal file
56
include/linux/pwrseq/consumer.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2024 Linaro Ltd.
|
||||
*/
|
||||
|
||||
#ifndef __POWER_SEQUENCING_CONSUMER_H__
|
||||
#define __POWER_SEQUENCING_CONSUMER_H__
|
||||
|
||||
#include <linux/err.h>
|
||||
|
||||
struct device;
|
||||
struct pwrseq_desc;
|
||||
|
||||
#if IS_ENABLED(CONFIG_POWER_SEQUENCING)
|
||||
|
||||
struct pwrseq_desc * __must_check
|
||||
pwrseq_get(struct device *dev, const char *target);
|
||||
void pwrseq_put(struct pwrseq_desc *desc);
|
||||
|
||||
struct pwrseq_desc * __must_check
|
||||
devm_pwrseq_get(struct device *dev, const char *target);
|
||||
|
||||
int pwrseq_power_on(struct pwrseq_desc *desc);
|
||||
int pwrseq_power_off(struct pwrseq_desc *desc);
|
||||
|
||||
#else /* CONFIG_POWER_SEQUENCING */
|
||||
|
||||
static inline struct pwrseq_desc * __must_check
|
||||
pwrseq_get(struct device *dev, const char *target)
|
||||
{
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
|
||||
static inline void pwrseq_put(struct pwrseq_desc *desc)
|
||||
{
|
||||
}
|
||||
|
||||
static inline struct pwrseq_desc * __must_check
|
||||
devm_pwrseq_get(struct device *dev, const char *target)
|
||||
{
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
|
||||
static inline int pwrseq_power_on(struct pwrseq_desc *desc)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static inline int pwrseq_power_off(struct pwrseq_desc *desc)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_POWER_SEQUENCING */
|
||||
|
||||
#endif /* __POWER_SEQUENCING_CONSUMER_H__ */
|
75
include/linux/pwrseq/provider.h
Normal file
75
include/linux/pwrseq/provider.h
Normal file
@ -0,0 +1,75 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2024 Linaro Ltd.
|
||||
*/
|
||||
|
||||
#ifndef __POWER_SEQUENCING_PROVIDER_H__
|
||||
#define __POWER_SEQUENCING_PROVIDER_H__
|
||||
|
||||
struct device;
|
||||
struct module;
|
||||
struct pwrseq_device;
|
||||
|
||||
typedef int (*pwrseq_power_state_func)(struct pwrseq_device *);
|
||||
typedef int (*pwrseq_match_func)(struct pwrseq_device *, struct device *);
|
||||
|
||||
/**
|
||||
* struct pwrseq_unit_data - Configuration of a single power sequencing
|
||||
* unit.
|
||||
* @name: Name of the unit.
|
||||
* @deps: Units that must be enabled before this one and disabled after it
|
||||
* in the order they come in this array. Must be NULL-terminated.
|
||||
* @enable: Callback running the part of the power-on sequence provided by
|
||||
* this unit.
|
||||
* @disable: Callback running the part of the power-off sequence provided
|
||||
* by this unit.
|
||||
*/
|
||||
struct pwrseq_unit_data {
|
||||
const char *name;
|
||||
const struct pwrseq_unit_data **deps;
|
||||
pwrseq_power_state_func enable;
|
||||
pwrseq_power_state_func disable;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pwrseq_target_data - Configuration of a power sequencing target.
|
||||
* @name: Name of the target.
|
||||
* @unit: Final unit that this target must reach in order to be considered
|
||||
* enabled.
|
||||
* @post_enable: Callback run after the target unit has been enabled, *after*
|
||||
* the state lock has been released. It's useful for implementing
|
||||
* boot-up delays without blocking other users from powering up
|
||||
* using the same power sequencer.
|
||||
*/
|
||||
struct pwrseq_target_data {
|
||||
const char *name;
|
||||
const struct pwrseq_unit_data *unit;
|
||||
pwrseq_power_state_func post_enable;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pwrseq_config - Configuration used for registering a new provider.
|
||||
* @parent: Parent device for the sequencer. Must be set.
|
||||
* @owner: Module providing this device.
|
||||
* @drvdata: Private driver data.
|
||||
* @match: Provider callback used to match the consumer device to the sequencer.
|
||||
* @targets: Array of targets for this power sequencer. Must be NULL-terminated.
|
||||
*/
|
||||
struct pwrseq_config {
|
||||
struct device *parent;
|
||||
struct module *owner;
|
||||
void *drvdata;
|
||||
pwrseq_match_func match;
|
||||
const struct pwrseq_target_data **targets;
|
||||
};
|
||||
|
||||
struct pwrseq_device *
|
||||
pwrseq_device_register(const struct pwrseq_config *config);
|
||||
void pwrseq_device_unregister(struct pwrseq_device *pwrseq);
|
||||
struct pwrseq_device *
|
||||
devm_pwrseq_device_register(struct device *dev,
|
||||
const struct pwrseq_config *config);
|
||||
|
||||
void *pwrseq_device_get_drvdata(struct pwrseq_device *pwrseq);
|
||||
|
||||
#endif /* __POWER_SEQUENCING_PROVIDER_H__ */
|
Loading…
Reference in New Issue
Block a user