pmdomain: core: add support for power-domains-child-ids

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,
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.

The add function is "all or nothing".  If it cannot add all of the
child domains in the list, it will unwind any additions already made
and report a failure.

Signed-off-by: Kevin Hilman (TI) <khilman@baylibre.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
Kevin Hilman (TI) 2026-04-20 16:51:18 -07:00 committed by Ulf Hansson
parent 53df57b3ae
commit 29e34a882b
2 changed files with 183 additions and 0 deletions

View File

@ -2924,6 +2924,173 @@ 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.
*
* Uses "all or nothing" semantics: either all relationships are established
* successfully, or none are (any partially-added relationships are unwound
* on error).
*
* Returns the number of parent-child relationships established on success,
* 0 if the properties don't exist, or a negative error code on failure.
*/
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 generic_pm_domain **pairs; /* pairs[2*i]=parent, pairs[2*i+1]=child */
u32 child_id;
int i, ret, count, child_count, added = 0;
/* Check if both properties exist */
count = of_count_phandle_with_args(np, "power-domains", "#power-domain-cells");
if (count <= 0)
return 0;
child_count = of_property_count_u32_elems(np, "power-domains-child-ids");
if (child_count < 0)
return 0;
if (child_count != count)
return -EINVAL;
/* Allocate tracking array for error unwind (parent/child pairs) */
pairs = kmalloc_array(count * 2, sizeof(*pairs), GFP_KERNEL);
if (!pairs)
return -ENOMEM;
for (i = 0; i < count; i++) {
ret = of_property_read_u32_index(np, "power-domains-child-ids",
i, &child_id);
if (ret)
goto err_unwind;
/* 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_unwind;
}
/* 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_unwind;
}
ret = of_parse_phandle_with_args(np, "power-domains",
"#power-domain-cells", i,
&parent_args);
if (ret)
goto err_unwind;
/* Get the parent domain */
parent_genpd = genpd_get_from_provider(&parent_args);
of_node_put(parent_args.np);
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_unwind;
}
/* Establish parent-child relationship */
ret = pm_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_unwind;
}
/* Track for potential unwind */
pairs[2 * added] = parent_genpd;
pairs[2 * added + 1] = child_genpd;
added++;
pr_debug("Added child domain %u (%s) to parent %s for %pOF\n",
child_id, child_genpd->name, parent_genpd->name, np);
}
kfree(pairs);
return count;
err_unwind:
/* Reverse all previously established relationships */
while (added-- > 0)
pm_genpd_remove_subdomain(pairs[2 * added], pairs[2 * added + 1]);
kfree(pairs);
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;
u32 child_id;
int i, ret, count, child_count;
/* Check if both properties exist */
count = of_count_phandle_with_args(np, "power-domains", "#power-domain-cells");
if (count <= 0)
return -ENOENT;
child_count = of_property_count_u32_elems(np, "power-domains-child-ids");
if (child_count < 0)
return -ENOENT;
if (child_count != count)
return -EINVAL;
for (i = 0; i < count; i++) {
if (of_property_read_u32_index(np, "power-domains-child-ids",
i, &child_id))
continue;
if (child_id >= data->num_domains || !data->domains[child_id])
continue;
ret = of_parse_phandle_with_args(np, "power-domains",
"#power-domain-cells", i,
&parent_args);
if (ret)
continue;
parent_genpd = genpd_get_from_provider(&parent_args);
of_node_put(parent_args.np);
if (IS_ERR(parent_genpd))
continue;
child_genpd = data->domains[child_id];
pm_genpd_remove_subdomain(parent_genpd, child_genpd);
}
return 0;
}
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 domain

View File

@ -467,6 +467,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,
@ -536,6 +540,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