diff options
| author | Mark Brown <broonie@kernel.org> | 2026-05-29 18:23:30 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-05-29 18:23:30 +0100 |
| commit | 9773daab060ecbd902dc2c90736d7d77ec946370 (patch) | |
| tree | bbc0c76a92b6d3e8bd7b7047940735b87d600c5b | |
| parent | 20368c7fc5984a77131bbaab1b7ef577b87fc018 (diff) | |
| parent | fba9c703c18459e936c365442667c08ca40dadcb (diff) | |
| download | linux-next-history-9773daab060ecbd902dc2c90736d7d77ec946370.tar.gz | |
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm.git
| -rw-r--r-- | Documentation/devicetree/bindings/power/power-domain.yaml | 34 | ||||
| -rw-r--r-- | drivers/pmdomain/arm/scmi_pm_domain.c | 15 | ||||
| -rw-r--r-- | drivers/pmdomain/core.c | 167 | ||||
| -rw-r--r-- | include/linux/pm_domain.h | 16 |
4 files changed, 231 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/power/power-domain.yaml b/Documentation/devicetree/bindings/power/power-domain.yaml index b1147dbf2e738..163b0af158fd2 100644 --- a/Documentation/devicetree/bindings/power/power-domain.yaml +++ b/Documentation/devicetree/bindings/power/power-domain.yaml @@ -68,6 +68,21 @@ properties: by the given provider should be subdomains of the domain specified by this binding. + power-domains-child-ids: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + An array of child domain IDs that correspond to the power-domains + property. This property is only applicable to power domain providers + with "#power-domain-cells" > 0 (i.e., providers that supply multiple + power domains). It specifies which of the provider's child domains + should be associated with each parent domain listed in the power-domains + property. The number of elements in this array must match the number of + phandles in the power-domains property. Each element specifies the child + domain ID (index) that should be made a child domain of the corresponding + parent domain. This enables hierarchical power domain structures where + different child domains from the same provider can have different + parent domains. + required: - "#power-domain-cells" @@ -133,3 +148,22 @@ examples: min-residency-us = <7000>; }; }; + + - | + // Example: SCMI domain 15 -> MAIN_PD, SCMI domain 19 -> WKUP_PD + MAIN_PD: power-controller-main { + compatible = "foo,power-controller"; + #power-domain-cells = <0>; + }; + + WKUP_PD: power-controller-wkup { + compatible = "foo,power-controller"; + #power-domain-cells = <0>; + }; + + scmi_pds: power-controller-scmi { + compatible = "foo,power-controller"; + #power-domain-cells = <1>; + power-domains = <&MAIN_PD>, <&WKUP_PD>; + power-domains-child-ids = <15>, <19>; + }; diff --git a/drivers/pmdomain/arm/scmi_pm_domain.c b/drivers/pmdomain/arm/scmi_pm_domain.c index 5454faed7d5d4..3d73aef21d2f9 100644 --- a/drivers/pmdomain/arm/scmi_pm_domain.c +++ b/drivers/pmdomain/arm/scmi_pm_domain.c @@ -113,6 +113,15 @@ static int scmi_pm_domain_probe(struct scmi_device *sdev) goto err_rm_genpds; dev_set_drvdata(dev, scmi_pd_data); + + /* + * Parse (optional) power-domains-child-ids property to establish + * parent-child relationships. + */ + ret = of_genpd_add_child_ids(np, scmi_pd_data); + if (ret < 0) + dev_err(dev, "Failed to add child domain hierarchy: %d\n", ret); + dev_info(dev, "Initialized %d power domains", num_domains); return 0; @@ -130,9 +139,13 @@ static void scmi_pm_domain_remove(struct scmi_device *sdev) struct device *dev = &sdev->dev; struct device_node *np = dev->of_node; + scmi_pd_data = dev_get_drvdata(dev); + + /* Remove any parent-child relationships established at probe time */ + of_genpd_remove_child_ids(np, scmi_pd_data); + of_genpd_del_provider(np); - scmi_pd_data = dev_get_drvdata(dev); for (i = 0; i < scmi_pd_data->num_domains; i++) { if (!scmi_pd_data->domains[i]) continue; 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 @@ -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. 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 |
