Re: [PATCH 2/3] pmdomain: core: add support for power-domains-child-ids
From: Dhruva Gole <hidden>
Date: 2026-03-13 11:55:32
Also in:
arm-scmi, linux-devicetree, linux-pm, lkml
Hi Kevin, On Mar 10, 2026 at 17:19:24 -0700, Kevin Hilman (TI) wrote:
Currently, PM domains can only support hierarchy for simple providers (e.g. ones with #power-domain-cells = 0). Add support for oncell providers as well by adding a new property `power-domains-child-ids` to describe the parent/child relationship. For example, an SCMI PM domain provider has multiple domains, each of which might be a child of diffeent parent domains. In this example,
s/diffeent/different
the parent domains are MAIN_PD and WKUP_PD:
scmi_pds: protocol@11 {
reg = <0x11>;
#power-domain-cells = <1>;
power-domains = <&MAIN_PD>, <&WKUP_PD>;
power-domains-child-ids = <15>, <19>;
};
With this example using the new property, SCMI PM domain 15 becomes a
child domain of MAIN_PD, and SCMI domain 19 becomes a child domain of
WKUP_PD.
To support this feature, add two new core functions
- of_genpd_add_child_ids()
- of_genpd_remove_child_ids()
which can be called by pmdomain providers to add/remove child domains
if they support the new property power-domains-child-ids.
Signed-off-by: Kevin Hilman (TI) <khilman@baylibre.com>
---
I've tested multiple possibilities with this series on the TI AM62L,
Tested-by: Dhruva Gole <redacted>
I tried having more parents than child nodes. That failed.
Tried less parents, more child nodes, failed as well.
< All as expected >
Tried the proper thing , worked :) (attached a short log of working case)
8<-----------------------
root@am62lxx-evm:~# cat /sys/kernel/debug/pm_genpd/power-controller-cluster/sub_domains
GPMC0
ELM0
root@am62lxx-evm:~# cat /sys/kernel/debug/pm_genpd/power-controller-main/sub_domains
power-controller-cluster
WKUP_GTC0
MSRAM_96K0
MCSPI0
root@am62lxx-evm:~# uname -a
Linux am62lxx-evm 7.0.0-rc3-next-20260312-00003-g9556feac2532-dirty #2 SMP PREEMPT Fri Mar 13 16:13:51 IST 2026 aarch64 GNU/Linux
---------------------->8
changes in DT:
8<------------------------------------------
+&scmi_pds {
+ power-domains = <&MAIN_PD>, <&MAIN_PD>, <&CLUSTER_PD>,<&CLUSTER_PD>, <&CLUSTER_PD>;
+ power-domains-child-ids = <58>, <63>, <72>, <37>, <25>;
+};
+
+&psci {
+ CLUSTER_PD: power-controller-cluster {
+ #power-domain-cells = <0>;
+ power-domains = <&MAIN_PD>;
+ };
+
+ MAIN_PD: power-controller-main {
+ #power-domain-cells = <0>;
+ };
};
--------------------------------------------->8
quoted hunk ↗ jump to hunk
drivers/pmdomain/core.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_domain.h | 16 ++++++++++++++++ 2 files changed, 185 insertions(+)diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 61c2277c9ce3..acb45dd540b7 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c@@ -2909,6 +2909,175 @@ static struct generic_pm_domain *genpd_get_from_provider( return genpd; } +/** + * of_genpd_add_child_ids() - Parse power-domains-child-ids property + * @np: Device node pointer associated with the PM domain provider. + * @data: Pointer to the onecell data associated with the PM domain provider. + * + * Parse the power-domains and power-domains-child-ids properties to establish + * parent-child relationships for PM domains. The power-domains property lists + * parent domains, and power-domains-child-ids lists which child domain IDs + * should be associated with each parent. + * + * Returns 0 on success, -ENOENT if properties don't exist, or negative error code. + */ +int of_genpd_add_child_ids(struct device_node *np, + struct genpd_onecell_data *data) +{ + struct of_phandle_args parent_args; + struct generic_pm_domain *parent_genpd, *child_genpd; + struct of_phandle_iterator it; + const struct property *prop; + const __be32 *item; + u32 child_id; + int ret; + + /* Check if both properties exist */ + if (of_count_phandle_with_args(np, "power-domains", "#power-domain-cells") <= 0) + return -ENOENT; + + prop = of_find_property(np, "power-domains-child-ids", NULL); + if (!prop) + return -ENOENT; + + item = of_prop_next_u32(prop, NULL, &child_id); + + /* Iterate over power-domains phandles and power-domains-child-ids in lockstep */ + of_for_each_phandle(&it, ret, np, "power-domains", "#power-domain-cells", 0) { + if (!item) { + pr_err("power-domains-child-ids shorter than power-domains for %pOF\n", np); + ret = -EINVAL; + goto err_put_node; + } + + /* + * Fill parent_args from the iterator. it.node is released by + * the next of_phandle_iterator_next() call at the top of the + * loop, or by the of_node_put() on the error path below. + */ + parent_args.np = it.node; + parent_args.args_count = of_phandle_iterator_args(&it, parent_args.args, + MAX_PHANDLE_ARGS); + + /* Get the parent domain */ + parent_genpd = genpd_get_from_provider(&parent_args); + if (IS_ERR(parent_genpd)) { + pr_err("Failed to get parent domain for %pOF: %ld\n", + np, PTR_ERR(parent_genpd)); + ret = PTR_ERR(parent_genpd); + goto err_put_node; + } + + /* Validate child ID is within bounds */ + if (child_id >= data->num_domains) { + pr_err("Child ID %u out of bounds (max %u) for %pOF\n", + child_id, data->num_domains - 1, np); + ret = -EINVAL; + goto err_put_node; + } + + /* Get the child domain */ + child_genpd = data->domains[child_id]; + if (!child_genpd) { + pr_err("Child domain %u is NULL for %pOF\n", child_id, np); + ret = -EINVAL; + goto err_put_node; + } + + /* Establish parent-child relationship */ + ret = genpd_add_subdomain(parent_genpd, child_genpd); + if (ret) { + pr_err("Failed to add child domain %u to parent in %pOF: %d\n", + child_id, np, ret); + goto err_put_node; + } + + pr_debug("Added child domain %u (%s) to parent %s for %pOF\n", + child_id, child_genpd->name, parent_genpd->name, np); + + item = of_prop_next_u32(prop, item, &child_id); + } + + /* of_for_each_phandle returns -ENOENT at natural end-of-list */ + if (ret && ret != -ENOENT) + return ret; + + /* All power-domains phandles were consumed; check for trailing child IDs */ + if (item) { + pr_err("power-domains-child-ids longer than power-domains for %pOF\n", np); + return -EINVAL; + } + + return 0; + +err_put_node: + of_node_put(it.node); + return ret; +} +EXPORT_SYMBOL_GPL(of_genpd_add_child_ids); + +/** + * of_genpd_remove_child_ids() - Remove parent-child PM domain relationships + * @np: Device node pointer associated with the PM domain provider. + * @data: Pointer to the onecell data associated with the PM domain provider. + * + * Reverses the effect of of_genpd_add_child_ids() by parsing the same + * power-domains and power-domains-child-ids properties and calling + * pm_genpd_remove_subdomain() for each established relationship. + * + * Returns 0 on success, -ENOENT if properties don't exist, or negative error + * code on failure. + */ +int of_genpd_remove_child_ids(struct device_node *np, + struct genpd_onecell_data *data) +{ + struct of_phandle_args parent_args; + struct generic_pm_domain *parent_genpd, *child_genpd; + struct of_phandle_iterator it; + const struct property *prop; + const __be32 *item; + u32 child_id; + int ret; + + /* Check if both properties exist */ + if (of_count_phandle_with_args(np, "power-domains", "#power-domain-cells") <= 0) + return -ENOENT; + + prop = of_find_property(np, "power-domains-child-ids", NULL); + if (!prop) + return -ENOENT; + + item = of_prop_next_u32(prop, NULL, &child_id); + + of_for_each_phandle(&it, ret, np, "power-domains", "#power-domain-cells", 0) { + if (!item) + break; + + parent_args.np = it.node; + parent_args.args_count = of_phandle_iterator_args(&it, parent_args.args, + MAX_PHANDLE_ARGS); + + if (child_id >= data->num_domains || !data->domains[child_id]) { + item = of_prop_next_u32(prop, item, &child_id); + continue; + } + + parent_genpd = genpd_get_from_provider(&parent_args); + if (IS_ERR(parent_genpd)) { + item = of_prop_next_u32(prop, item, &child_id); + continue; + } + + child_genpd = data->domains[child_id]; + pm_genpd_remove_subdomain(parent_genpd, child_genpd); + + item = of_prop_next_u32(prop, item, &child_id); + } + + return (ret == -ENOENT) ? 0 : ret; +} +EXPORT_SYMBOL_GPL(of_genpd_remove_child_ids); + /** * of_genpd_add_device() - Add a device to an I/O PM domain * @genpdspec: OF phandle args to use for look-up PM domaindiff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index f67a2cb7d781..b44615d79af6 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h@@ -465,6 +465,10 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np); int of_genpd_parse_idle_states(struct device_node *dn, struct genpd_power_state **states, int *n); void of_genpd_sync_state(struct device_node *np); +int of_genpd_add_child_ids(struct device_node *np, + struct genpd_onecell_data *data); +int of_genpd_remove_child_ids(struct device_node *np, + struct genpd_onecell_data *data); int genpd_dev_pm_attach(struct device *dev); struct device *genpd_dev_pm_attach_by_id(struct device *dev,@@ -534,6 +538,18 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np) { return ERR_PTR(-EOPNOTSUPP); } + +static inline int of_genpd_add_child_ids(struct device_node *np, + struct genpd_onecell_data *data) +{ + return -EOPNOTSUPP; +} + +static inline int of_genpd_remove_child_ids(struct device_node *np, + struct genpd_onecell_data *data) +{ + return -EOPNOTSUPP; +} #endif /* CONFIG_PM_GENERIC_DOMAINS_OF */ #ifdef CONFIG_PM-- 2.51.0
-- Best regards, Dhruva Gole Texas Instruments Incorporated