]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
pmdomain: core: add support for power-domains-child-ids
authorKevin Hilman (TI) <khilman@baylibre.com>
Mon, 20 Apr 2026 23:51:18 +0000 (16:51 -0700)
committerUlf Hansson <ulf.hansson@linaro.org>
Mon, 11 May 2026 10:46:42 +0000 (12:46 +0200)
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>
drivers/pmdomain/core.c
include/linux/pm_domain.h

index 71e930e80178e99be48abe285d7376a10d7eea44..f7731270015d97bb29a24ebb66cd0c425cecc6d5 100644 (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
index b299dc0128d65ee52f31ee0b5633ebddf579a016..f925614aebdbc9146c05ac834894ef42a43376b7 100644 (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