From: Kevin Hilman (TI) Date: Mon, 20 Apr 2026 23:51:18 +0000 (-0700) Subject: pmdomain: core: add support for power-domains-child-ids X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=29e34a882b09e9f01d0fff5e312ea0cf9499137d;p=thirdparty%2Fkernel%2Flinux.git 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) Signed-off-by: Ulf Hansson --- diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 71e930e80178e..f7731270015d9 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -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 diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index b299dc0128d65..f925614aebdbc 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -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