diff options
| author | Kevin Hilman (TI) <khilman@baylibre.com> | 2026-04-20 16:51:18 -0700 |
|---|---|---|
| committer | Ulf Hansson <ulf.hansson@linaro.org> | 2026-05-11 12:46:42 +0200 |
| commit | 29e34a882b09e9f01d0fff5e312ea0cf9499137d (patch) | |
| tree | b718bfb665febc1610834a1ccc633c4a96c7ff8c /drivers/pmdomain | |
| parent | 53df57b3ae880914b5b4ea51a5e8e52c2729258a (diff) | |
| download | linux-next-29e34a882b09e9f01d0fff5e312ea0cf9499137d.tar.gz linux-next-29e34a882b09e9f01d0fff5e312ea0cf9499137d.zip | |
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>
Diffstat (limited to 'drivers/pmdomain')
| -rw-r--r-- | drivers/pmdomain/core.c | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 71e930e80178..f7731270015d 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -2925,6 +2925,173 @@ static struct generic_pm_domain *genpd_get_from_provider( } /** + * 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 * @dev: Device to be added. |
