[PATCH v5 10/12] powercap: arm_scmi: Create synthetic parent node for multi-instance

Philip Radford
2026-04-28T09:10:03Z

SCMI powercap domains are exposed as a flat list and may include
multiple top-level domains without a parent. When registered with
the powercap framework, these appear as independent root zones
with no common hierarchy. The driver probes domains per SCMI
instance, but when registering with the powercap framework, these
would be combined into a single tree. This is particularly evident
in multi-instance setups where zone IDs and parent zones can only
coexist with other zones and parents from the same instance.

Create a synthetic root zone to act as a common parent for all
top-level domains. Creates a single hierarchy and unified entry
point per-instance for userspace.

Signed-off-by: Philip Radford <philip.radford@arm.com>
---
---
drivers/powercap/arm_scmi_powercap.c | 67 ++++++++++++++++++++++++++--
1 file changed, 64 insertions(+), 3 deletions(-)

diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index 7f2bb162f96c..d74869af1633 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -36,6 +36,7 @@ struct scmi_powercap_root {
struct scmi_powercap_zone *spzones;
struct list_head *registered_zones;
struct list_head scmi_zones;
+ struct scmi_powercap_zone instance_root;
};

static struct powercap_control_type *scmi_top_pcntrl;
@@ -263,12 +264,47 @@ static const struct powercap_zone_constraint_ops constraint_ops = {
.get_name = scmi_powercap_get_name,
};

+/* Multi-instance constraints to meet driver requrements */
+static int instance_root_release(struct powercap_zone *pz)
+{
+ return 0;
+}
+
+static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *v)
+{
+ *v = 0;
+ return 0;
+}
+
+static int instance_root_set_constraint(struct powercap_zone *pz, int cid, u64 v)
+{
+ return -EOPNOTSUPP;
+}
+
+static int instance_root_get_constraint(struct powercap_zone *pz, int cid, u64 *v)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct powercap_zone_ops instance_root_ops = {
+ .get_max_power_range_uw = scmi_powercap_get_max_power_range_uw,
+ .get_power_uw = instance_root_get_power_uw,
+ .release = instance_root_release,
+};
+
+static const struct powercap_zone_constraint_ops instance_root_const_ops = {
+ .set_power_limit_uw = instance_root_set_constraint,
+ .get_power_limit_uw = instance_root_get_constraint,
+ .set_time_window_us = instance_root_set_constraint,
+ .get_time_window_us = instance_root_get_constraint,
+};
+
static void scmi_powercap_unregister_all_zones(struct scmi_powercap_root *pr)
{
int i;

/* Un-register children zones first starting from the leaves */
- for (i = pr->num_zones - 1; i >= 0; i--) {
+ for (i = pr->num_zones; i >= 0; i--) {
if (!list_empty(&pr->registered_zones[i])) {
struct scmi_powercap_zone *spz;

@@ -313,7 +349,6 @@ static int scmi_powercap_register_zone(struct scmi_powercap_root *pr,
parent ? &parent->zone : NULL,
&zone_ops, spz->info->num_cpli, &constraint_ops);
if (!IS_ERR(z)) {
- spz->height = scmi_powercap_get_zone_height(spz);
spz->registered = true;
list_move(&spz->node, &pr->registered_zones[spz->height]);
dev_dbg(spz->dev, "Registered node %s - parent %s - height:%d\n",
@@ -384,6 +419,8 @@ static int scmi_zones_register(struct device *dev,
struct scmi_powercap_zone *parent;

parent = scmi_powercap_get_parent_zone(spz);
+ if (!parent)
+ parent = &pr->instance_root;
if (parent && !parent->registered) {
zones_stack[sp++] = spz;
spz = parent;
@@ -424,8 +461,11 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
int ret, i;
struct scmi_powercap_root *pr;
struct scmi_powercap_zone *spz;
+ struct scmi_powercap_zone *ir;
struct scmi_protocol_handle *ph;
struct device *dev = &sdev->dev;
+ char *instance_name;
+ struct powercap_zone *z;

if (!sdev->handle)
return -ENODEV;
@@ -453,7 +493,7 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
return -ENOMEM;

/* Allocate for worst possible scenario of maximum tree height. */
- pr->registered_zones = devm_kcalloc(dev, pr->num_zones,
+ pr->registered_zones = devm_kcalloc(dev, pr->num_zones + 1,
sizeof(*pr->registered_zones),
GFP_KERNEL);
if (!pr->registered_zones)
@@ -491,6 +531,27 @@ static int scmi_powercap_probe(struct scmi_device *sdev)
}
}

+ ir = &pr->instance_root;
+ ir->dev = dev;
+ INIT_LIST_HEAD(&ir->node);
+ instance_name = devm_kasprintf(dev, GFP_KERNEL, "instance_%s", dev_name(dev));
+ if (!instance_name)
+ return -ENOMEM;
+
+ z = powercap_register_zone(&ir->zone, scmi_top_pcntrl,
+ instance_name, NULL, &instance_root_ops, 0,
+ &instance_root_const_ops);
+
+ if (IS_ERR(z)) {
+ ret = PTR_ERR(z);
+ dev_err(dev, "Failed to register sysnthetic instance root: %d\n", ret);
+ return ret;
+ }
+
+ ir->registered = true;
+ ir->height = 0;
+ list_add_tail(&ir->node, &pr->registered_zones[0]);
+
/*
* Scan array of retrieved SCMI powercap domains and register them
* recursively starting from the root domains.
--
2.47.3