power sequencing updates for v7.4-rc1

Power sequencing core:
 - add a suite of kunit test cases for the power sequencing core
   functionalities
 - provide a new consumer API: pwrseq_is_controllable()
 
 New Drivers:
 - add the Renesas RZ/G3L Power Ready driver which provides information
   on power readiness for various IPs on RZ/G3L
 
 Driver changes:
 - use pwrseq_is_controllable() to report controllability to consumers in
   pwrseq-qcom-wcn and pwrseq-pcie-m2
 - add support for PCIe switch topologies to pwrseq-pcie-m2
 - add the PCI ID for Foxconn WCN7850 to pwrseq-pcie-m2
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEkeUTLeW1Rh17omX8BZ0uy/82hMMFAmrV2hkACgkQBZ0uy/82
 hMMBnQ//ciYweRR/AgASdvZsybFnDirMDKi07WFc5xnes5aAQs7IO2qbd2sk+tes
 tFTpzNHA/s1w62kpfabRb6oyXAP5LgqfAGR6LBTMCPApUYh3a2FfyK515ATfG6Ql
 a2oxjD5D/WO8utqt9daebYZX7fKWSMifhEI7ystHtJDtoWNTsEXPjkzDZNdLmiir
 g7dv0fhCssdvnlT1OUYUgFjQ0GAqPCgouC9jSwRzneHjSmAf8pv6d8r7d+SSUEI8
 XcRgpfcn3HEAMnfwLGUXFFf9kRwHOvL6wKgvzBck4y0KnsKJ04Q77RQYWPepf8ZL
 WLys63KuOvWSol+L7Bb8NwHT3+ZTyIYu62nlXRs9xNrJSpEDsXxQWjgEA24L4FdI
 4ZJSic0UUGYZkAAIrcxyrx3+Kv/bqCma5SamVUs0fmcos3SLddV4ymPYkFye0HLu
 OhpSCheA4oHMxoqR0Z284i9dWkktPLSdAeBXWCT49qU8YRdgt7PvEVhhHj/yaEJY
 g0M0J/xAxghBsM7SpfkeEtNBINXq293N3+U5iwnXt+80pA/XQFgVc1qpjbCItnSy
 nZzxZwWITFTAKmGcASktSmWyzC3mNRJzBpulcBiFcHWqAifs4rPJ4rZQSpH5frKX
 KTua2o6SnONiK7QGC5Q9Rhp5OIoS/uOgWjgakKMRwkuEP73SvCA=
 =EM35
 -----END PGP SIGNATURE-----

Merge tag 'pwrseq-updates-for-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux

Pull power sequencing updates from Bartosz Golaszewski:
 "The bulk of added lines are kunit tests that comprehensively verify
  the core functionality of the power sequencing framework.

  Other than that, we have another pwrseq driver: Renesas RZ/G3L Power
  Ready and support for more models in pwrseq-pcie-m2. We also added a
  new consumer-facing API in the form of pwrseq_is_controllable()
  function which allows consumers to check if a given target resource is
  actually host-controllable.

  Summary:

  Power sequencing core:
   - add a suite of kunit test cases for the power sequencing core
     functionalities
   - provide a new consumer API: pwrseq_is_controllable()

  New Drivers:
   - add the Renesas RZ/G3L Power Ready driver which provides
     information on power readiness for various IPs on RZ/G3L

  Driver changes:
   - use pwrseq_is_controllable() to report controllability to consumers
     in pwrseq-qcom-wcn and pwrseq-pcie-m2
   - add support for PCIe switch topologies to pwrseq-pcie-m2
   - add the PCI ID for Foxconn WCN7850 to pwrseq-pcie-m2"

* tag 'pwrseq-updates-for-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux:
  power: sequencing: pcie-m2: Add Foxconn WCN7850 subsystem PCI ID
  power: sequencing: qcom-wcn: Report power controllability
  power: sequencing: pcie-m2: Report power controllability
  power: sequencing: Add pwrseq_is_controllable() API
  power: sequencing: add kunit test cases
  power: sequencing: pwrseq-pcie-m2: Support PCIe switch topologies
  power: sequencing: Add Renesas RZ/G3L Power Ready driver
This commit is contained in:
Linus Torvalds 2026-10-22 06:56:33 -10:00
commit 74b1dbf6f2
9 changed files with 1799 additions and 2 deletions

View File

@ -27,6 +27,15 @@ config POWER_SEQUENCING_QCOM_WCN
this driver is needed for correct power control or else we'd risk not
respecting the required delays between enabling Bluetooth and WLAN.
config POWER_SEQUENCING_RENESAS_PWRRDY
tristate "Renesas Power Ready sequencing driver"
depends on SYSC_RZ || COMPILE_TEST
select AUXILIARY_BUS
help
Say Y here to enable the power sequencing driver for the Renesas
Power Ready signals. This driver handles the power ready signals
required to power on the various IP's on RZ/G3L platform.
config POWER_SEQUENCING_TH1520_GPU
tristate "T-HEAD TH1520 GPU power sequencing driver"
depends on ARCH_THEAD || COMPILE_TEST
@ -47,4 +56,14 @@ config POWER_SEQUENCING_PCIE_M2
connectors. This driver handles the power sequencing for the M.2
connectors exposing multiple interfaces like PCIe, SATA, UART, etc...
config POWER_SEQUENCING_KUNIT
tristate "Build power sequencing KUnit test cases"
depends on KUNIT
default KUNIT_ALL_TESTS
help
Say Y here to build the module containing KUnit test cases for the
power sequencing subsystem.
If unsure, say N.
endif

View File

@ -4,5 +4,7 @@ obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o
pwrseq-core-y := core.o
obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o
obj-$(CONFIG_POWER_SEQUENCING_RENESAS_PWRRDY) += pwrseq-renesas-pwrrdy.o
obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o
obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) += pwrseq-pcie-m2.o
obj-$(CONFIG_POWER_SEQUENCING_KUNIT) += pwrseq-kunit.o

View File

@ -183,12 +183,15 @@ static void pwrseq_unit_release(struct kref *ref)
* 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.
* @is_controllable: Optional callback reporting whether enabling/disabling
* this target actually controls power.
*/
struct pwrseq_target {
struct list_head list;
const char *name;
struct pwrseq_unit *unit;
pwrseq_power_state_func post_enable;
pwrseq_is_controllable_func is_controllable;
};
static struct pwrseq_target *
@ -207,6 +210,7 @@ pwrseq_target_new(const struct pwrseq_target_data *data)
}
target->post_enable = data->post_enable;
target->is_controllable = data->is_controllable;
return target;
}
@ -994,6 +998,51 @@ struct device *pwrseq_to_device(struct pwrseq_desc *desc)
}
EXPORT_SYMBOL_GPL(pwrseq_to_device);
/**
* pwrseq_is_controllable() - Check whether the target provides a
* host-controllable power actuator.
* @desc: Descriptor referencing the power sequencer.
*
* Some power sequencing targets provide no host-controllable enable for their
* function on a given board, for instance when the enable line is not wired up
* and is instead hardwired to an always-on level. For such targets a call to
* pwrseq_power_off() is still allowed, so that the consumer can drop its vote
* on the (possibly shared) resources, but the host cannot gate the function
* on its own.
*
* Returns:
* True if the target provides a host-controllable power actuator, false
* otherwise. Also returns false if @desc is NULL.
*/
bool pwrseq_is_controllable(struct pwrseq_desc *desc)
{
struct pwrseq_device *pwrseq;
struct pwrseq_target *target;
struct pwrseq_unit *unit;
might_sleep();
if (!desc)
return false;
pwrseq = desc->pwrseq;
target = desc->target;
unit = target->unit;
guard(rwsem_read)(&pwrseq->rw_lock);
if (!device_is_registered(&pwrseq->dev))
return false;
if (!unit->enable && !unit->disable)
return false;
if (!target->is_controllable)
return true;
return target->is_controllable(pwrseq);
}
EXPORT_SYMBOL_GPL(pwrseq_is_controllable);
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct pwrseq_debugfs_count_ctx {

File diff suppressed because it is too large Load Diff

View File

@ -83,6 +83,18 @@ static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq)
return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1);
}
static bool pwrseq_pci_m2_e_uart_is_controllable(struct pwrseq_device *pwrseq)
{
struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
/*
* The UART enable is driven through the W_DISABLE2# line. When it is not
* wired up on this connector the enable/disable callbacks are no-ops, so
* the host cannot gate the Bluetooth function on its own.
*/
return !!ctx->w_disable2_gpio;
}
static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = {
.name = "uart-enable",
.deps = pwrseq_pcie_m2_unit_deps,
@ -104,6 +116,18 @@ static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq)
return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1);
}
static bool pwrseq_pci_m2_e_pcie_is_controllable(struct pwrseq_device *pwrseq)
{
struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
/*
* The PCIe/WiFi enable is driven through the W_DISABLE1# line. When it
* is not wired up on this connector the enable/disable callbacks are
* no-ops, so the host cannot gate the PCIe/WiFi function on its own.
*/
return !!ctx->w_disable1_gpio;
}
static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = {
.name = "pcie-enable",
.deps = pwrseq_pcie_m2_unit_deps,
@ -132,12 +156,14 @@ static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = {
.name = "uart",
.unit = &pwrseq_pcie_m2_e_uart_unit_data,
.post_enable = pwrseq_pcie_m2_e_pwup_delay,
.is_controllable = pwrseq_pci_m2_e_uart_is_controllable,
};
static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = {
.name = "pcie",
.unit = &pwrseq_pcie_m2_e_pcie_unit_data,
.post_enable = pwrseq_pcie_m2_e_pwup_delay,
.is_controllable = pwrseq_pci_m2_e_pcie_is_controllable,
};
static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = {
@ -199,6 +225,8 @@ static const struct pci_device_id pwrseq_m2_pci_ids[] = {
.driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" },
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_LENOVO, 0xe0e9),
.driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" },
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_FOXCONN, 0xe0ee),
.driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" },
{ } /* Sentinel */
};
@ -378,6 +406,21 @@ static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx,
mutex_unlock(&ctx->list_lock);
}
static bool pwrseq_pcie_m2_pci_parent_matches(struct pci_dev *pdev,
struct device_node *pci_parent)
{
struct device *dev = pdev->dev.parent;
while (dev) {
if (dev->of_node == pci_parent)
return true;
if (!dev_is_pci(dev))
break;
dev = dev->parent;
}
return false;
}
static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action,
void *data)
{
@ -392,7 +435,7 @@ static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action
*/
struct device_node *pci_parent __free(device_node) =
of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0);
if (!pci_parent || (pci_parent != pdev->dev.parent->of_node))
if (!pci_parent || !pwrseq_pcie_m2_pci_parent_matches(pdev, pci_parent))
return NOTIFY_DONE;
switch (action) {
@ -466,7 +509,7 @@ static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx)
/* Create serdev for existing PCI devices if required */
for_each_pci_dev(pdev) {
if (!pdev->dev.parent || pci_parent != pdev->dev.parent->of_node)
if (!pwrseq_pcie_m2_pci_parent_matches(pdev, pci_parent))
continue;
if (!pci_match_id(pwrseq_m2_pci_ids, pdev))

View File

@ -183,6 +183,19 @@ static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq)
return 0;
}
static bool pwrseq_qcom_wcn_bt_is_controllable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
/*
* The Bluetooth enable is driven through the BT_EN GPIO. When it is not
* wired up (e.g. hardwired to an always-on pull-up), the enable/disable
* callbacks are no-ops, so the host cannot gate the Bluetooth function
* on its own.
*/
return !!ctx->bt_gpio;
}
static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = {
.name = "bluetooth-enable",
.deps = pwrseq_qcom_wcn_unit_deps,
@ -217,6 +230,19 @@ static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq)
return 0;
}
static bool pwrseq_qcom_wcn_wlan_is_controllable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
/*
* The WLAN enable is driven through the WLAN_EN GPIO. When it is not
* wired up (e.g. hardwired to an always-on pull-up), the enable/disable
* callbacks are no-ops, so the host cannot gate the WLAN function on
* its own.
*/
return !!ctx->wlan_gpio;
}
static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = {
.name = "wlan-enable",
.deps = pwrseq_qcom_wcn_unit_deps,
@ -257,12 +283,14 @@ 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,
.is_controllable = pwrseq_qcom_wcn_bt_is_controllable,
};
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,
.is_controllable = pwrseq_qcom_wcn_wlan_is_controllable,
};
/* There are no separate BT and WLAN enablement pins */
@ -280,12 +308,14 @@ static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = {
.name = "bluetooth",
.unit = &pwrseq_qcom_wcn6855_bt_unit_data,
.post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert,
.is_controllable = pwrseq_qcom_wcn_bt_is_controllable,
};
static const struct pwrseq_target_data pwrseq_qcom_wcn6855_wlan_target_data = {
.name = "wlan",
.unit = &pwrseq_qcom_wcn6855_wlan_unit_data,
.post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert,
.is_controllable = pwrseq_qcom_wcn_wlan_is_controllable,
};
static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = {

View File

@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Renesas RZ/G3L Power Ready driver
*
*/
#include <linux/auxiliary_bus.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pwrseq/provider.h>
#include <linux/regmap.h>
#define SYS_PWRRDY_N 0xd70
#define SYS_PWRRDY_N_USB_MASK BIT(0)
#define SYS_PWRRDY_N_DSI_MASK BIT(1)
#define SYS_PWRRDY_N_CSI_MASK BIT(2)
static int pwrseq_rzg3l_set_pwrrdy(struct pwrseq_device *pwrseq, u32 mask,
bool val)
{
struct regmap *regmap = pwrseq_device_get_drvdata(pwrseq);
return regmap_assign_bits(regmap, SYS_PWRRDY_N, mask, val);
}
static int pwrseq_rzg3l_usb_pwrrdy_enable(struct pwrseq_device *pwrseq)
{
return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_USB_MASK, 0);
}
static int pwrseq_rzg3l_usb_pwrrdy_disable(struct pwrseq_device *pwrseq)
{
return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_USB_MASK, 1);
}
static const struct pwrseq_unit_data pwrseq_rzg3l_usb_pwrrdy_unit = {
.name = "usb-pwrrdy-power-sequence",
.enable = pwrseq_rzg3l_usb_pwrrdy_enable,
.disable = pwrseq_rzg3l_usb_pwrrdy_disable,
};
static int pwrseq_rzg3l_dsi_pwrrdy_enable(struct pwrseq_device *pwrseq)
{
return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_DSI_MASK, 0);
}
static int pwrseq_rzg3l_dsi_pwrrdy_disable(struct pwrseq_device *pwrseq)
{
return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_DSI_MASK, 1);
}
static const struct pwrseq_unit_data pwrseq_rzg3l_dsi_pwrrdy_unit = {
.name = "dsi-pwrrdy-sequence",
.enable = pwrseq_rzg3l_dsi_pwrrdy_enable,
.disable = pwrseq_rzg3l_dsi_pwrrdy_disable,
};
static int pwrseq_rzg3l_csi_pwrrdy_enable(struct pwrseq_device *pwrseq)
{
return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_CSI_MASK, 0);
}
static int pwrseq_rzg3l_csi_pwrrdy_disable(struct pwrseq_device *pwrseq)
{
return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_CSI_MASK, 1);
}
static const struct pwrseq_unit_data pwrseq_rzg3l_csi_pwrrdy_unit = {
.name = "csi-pwrrdy-power-sequence",
.enable = pwrseq_rzg3l_csi_pwrrdy_enable,
.disable = pwrseq_rzg3l_csi_pwrrdy_disable,
};
static const struct pwrseq_target_data pwrseq_rzg3l_usb_pwrrdy_target = {
.name = "usb-pwrrdy",
.unit = &pwrseq_rzg3l_usb_pwrrdy_unit,
};
static const struct pwrseq_target_data pwrseq_rzg3l_dsi_pwrrdy_target = {
.name = "dsi-pwrrdy",
.unit = &pwrseq_rzg3l_dsi_pwrrdy_unit,
};
static const struct pwrseq_target_data pwrseq_rzg3l_csi_pwrrdy_target = {
.name = "csi-pwrrdy",
.unit = &pwrseq_rzg3l_csi_pwrrdy_unit,
};
static const struct pwrseq_target_data *pwrseq_rzg3l_pwrrdy_targets[] = {
&pwrseq_rzg3l_usb_pwrrdy_target,
&pwrseq_rzg3l_dsi_pwrrdy_target,
&pwrseq_rzg3l_csi_pwrrdy_target,
NULL
};
static const char * const pwrseq_rzg3l_pwrrdy_compats[] = {
"renesas,r9a08g046-usbphy-ctrl",
"renesas,r9a08g046-mipi-dsi",
NULL
};
static int pwrseq_rzg3l_pwrrdy_match(struct pwrseq_device *pwrseq,
struct device *dev)
{
if (of_device_compatible_match(dev->of_node, pwrseq_rzg3l_pwrrdy_compats))
return PWRSEQ_MATCH_OK;
return PWRSEQ_NO_MATCH;
}
static int pwrseq_rzg3l_pwrrdy_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct pwrseq_config config = {
.parent = &adev->dev,
.owner = THIS_MODULE,
.drvdata = adev->dev.platform_data,
.match = pwrseq_rzg3l_pwrrdy_match,
.targets = pwrseq_rzg3l_pwrrdy_targets,
};
return PTR_ERR_OR_ZERO(devm_pwrseq_device_register(&adev->dev, &config));
}
static const struct auxiliary_device_id pwrseq_rzg3l_pwrrdy_id_table[] = {
{ .name = "rz_sysc.pwrseq-pwrrdy" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(auxiliary, pwrseq_rzg3l_pwrrdy_id_table);
static struct auxiliary_driver pwrseq_rzg3l_pwrrdy_driver = {
.driver = {
.name = "pwrseq-rzg3l-pwrrdy",
},
.probe = pwrseq_rzg3l_pwrrdy_probe,
.id_table = pwrseq_rzg3l_pwrrdy_id_table,
};
module_auxiliary_driver(pwrseq_rzg3l_pwrrdy_driver);
MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
MODULE_DESCRIPTION("Renesas RZ/G3L Power Ready Driver");
MODULE_LICENSE("GPL");

View File

@ -25,6 +25,8 @@ int pwrseq_disable(struct pwrseq_desc *desc);
struct device *pwrseq_to_device(struct pwrseq_desc *desc);
bool pwrseq_is_controllable(struct pwrseq_desc *desc);
#else /* CONFIG_POWER_SEQUENCING */
static inline struct pwrseq_desc * __must_check
@ -58,6 +60,11 @@ static inline struct device *pwrseq_to_device(struct pwrseq_desc *desc)
return NULL;
}
static inline bool pwrseq_is_controllable(struct pwrseq_desc *desc)
{
return false;
}
#endif /* CONFIG_POWER_SEQUENCING */
#endif /* __POWER_SEQUENCING_CONSUMER_H__ */

View File

@ -6,12 +6,15 @@
#ifndef __POWER_SEQUENCING_PROVIDER_H__
#define __POWER_SEQUENCING_PROVIDER_H__
#include <linux/types.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 *);
typedef bool (*pwrseq_is_controllable_func)(struct pwrseq_device *);
#define PWRSEQ_NO_MATCH 0
#define PWRSEQ_MATCH_OK 1
@ -43,11 +46,16 @@ struct pwrseq_unit_data {
* 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.
* @is_controllable: Optional callback checking whether enabling/disabling this
* target actually controls power (for example when the
* controlling GPIO is wired up). If not provided, the
* target's power is assumed to be always controllable.
*/
struct pwrseq_target_data {
const char *name;
const struct pwrseq_unit_data *unit;
pwrseq_power_state_func post_enable;
pwrseq_is_controllable_func is_controllable;
};
/**