* [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions
@ 2026-04-28 9:09 Philip Radford
2026-04-28 9:09 ` [PATCH v5 01/12] firmware: arm_scmi: Add an optional custom parameter to fastchannel helpers Philip Radford
` (11 more replies)
0 siblings, 12 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
Hi all,
I will be taking over this series from Cristian and in doing so I have
addressed a couple of issues raised by the first version and added five
additional patches since Cristian's original series:
[6/12] exposes the measurement averaging interval (MAI) value when the
agent has registered to receive power measurement change notifications
for the power capping domain.
[9/12] adds MAI get and set support for the powercap protocol.
[10/12] introduces a synthetic root zone to act as a common parent for all
top-level domains.
[11/12] adds get_power_uw to synthetic root zone, summing the per-zone
power of immediate child zones.
[12/12] adds enable/disable functionality to synthetic root zone to
enable/disable immediate children.
The original series was based on v6.17-rc1 whereas this version has been
based on v7.1-rc1.
The rest of Cristian's series is explained below;
SCMIv4.0 [1] introduces some new features and commands into the Powercap
protocol. In a nutshell, such protocol changes add support for:
- setting multiple powercap limit/interval constraints for each SCMI
powercap domain which supports multiple Concurrent Power Limit
- enabling more Powercap commands to use Fastchannels mechanism
- adding multiple constraints support to the existing notifications
After a bit of needed updates in the SCMI core this series adds:
- support for the idea of optional multiple Concurrent Power Limit (CPLs)
- support for the new FCs
- support for extended notifications
- enable usage of such multiple constraint in the ARM SCMI Powercap driver
Note that the public SCMIv4.0 spec at [1] is currently still BETA0, so
this series could anyway need some minor rework along the way and
definitely will need to wait for a final public release before being
possibly merged.
Tested on a single and multi-instance scenario on an emulated setup
implementing the new protocol extensions.
Based on v7.1-rc1.
Thanks,
Phil
---
V4->V5
- Added enable/disable functionality to synthetic node
- Rebased on v7.1-rc1
V3->V4
- Rebased on v7.0-rc4
- Added sythentic parent node and functionality
- Moved fastchannel inits outside of loop
- Renamed arguments for consistency
V2->V3
- Added powercap MAI get/set support
V1->V2
- Rebased on sudeep/for-next/scmi/updates
- Amended Copyright to include 2026
- Added patch to extend powercap report to include MAI
- Removed creation of pi powercap_info struct due to legacy code change
- Amended references to pi->version and similar, which were based on
legacy code
- Wrapped two variables in le32_to_cpu() to appease Sparse warnings
- Amended comparing operator value in response to feedback
Cristian Marussi (7):
firmware: arm_scmi: Add an optional custom parameter to fastchannel
helpers
firmware: arm_scmi: Refactor powercap domain layout
firmware: arm_scmi: Add SCMIv4.0 Powercap basic support
firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support
firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support
include: trace: Add new parameter to trace_scmi_fc_call
powercap: arm_scmi: Enable multiple constraints support
Philip Radford (5):
firmware: arm_scmi: Extend powercap report to include MAI
firmware: arm_scmi: add Powercap MAI get/set support
powercap: arm_scmi: Create synthetic parent node for multi-instance
powercap: arm_scmi: Add get_power_uw to synthetic node
powercap: arm_scmi: Synthetic zone enable/disable
drivers/firmware/arm_scmi/driver.c | 12 +-
drivers/firmware/arm_scmi/perf.c | 16 +-
drivers/firmware/arm_scmi/powercap.c | 984 ++++++++++++++++++++------
drivers/firmware/arm_scmi/protocols.h | 2 +-
drivers/powercap/arm_scmi_powercap.c | 227 +++++-
include/linux/scmi_protocol.h | 87 ++-
include/trace/events/scmi.h | 12 +-
7 files changed, 1067 insertions(+), 273 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v5 01/12] firmware: arm_scmi: Add an optional custom parameter to fastchannel helpers
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 02/12] firmware: arm_scmi: Refactor powercap domain layout Philip Radford
` (10 subsequent siblings)
11 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
From: Cristian Marussi <cristian.marussi@arm.com>
Starting from SCMIv4.0 the protocols DESCRIBE_FASTCHANNEL commands allow
to specify one additional per-protocol custom field in the outgoing message
request in order to, optionally, further narrow down the scope of the
fastchannel discovery request; the related message-reply format is instead
unchanged.
Add an optional custom protocol parameter to the common fastchannel helper
so as to enable the caller to choose the kind of message to send based on
the detected protocol version.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/driver.c | 12 ++++++++++--
drivers/firmware/arm_scmi/perf.c | 8 ++++----
drivers/firmware/arm_scmi/powercap.c | 8 ++++----
drivers/firmware/arm_scmi/protocols.h | 2 +-
4 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index f167194f7cf6..adff81a3d233 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1872,6 +1872,11 @@ static int scmi_iterator_run(void *iter)
struct scmi_msg_get_fc_info {
__le32 domain;
__le32 message_id;
+ __le32 custom;
+#define MSG_FC_INFO_SZ_EXTENDED \
+ (sizeof(struct scmi_msg_get_fc_info))
+#define MSG_FC_INFO_SZ \
+ (sizeof(struct scmi_msg_get_fc_info) - sizeof(__le32))
};
struct scmi_msg_resp_desc_fc {
@@ -1900,7 +1905,7 @@ struct scmi_msg_resp_desc_fc {
static void
scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph,
u8 describe_id, u32 message_id, u32 valid_size,
- u32 domain, void __iomem **p_addr,
+ u32 domain, u32 *custom, void __iomem **p_addr,
struct scmi_fc_db_info **p_db, u32 *rate_limit)
{
int ret;
@@ -1931,13 +1936,16 @@ scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph,
}
ret = ph->xops->xfer_get_init(ph, describe_id,
- sizeof(*info), sizeof(*resp), &t);
+ custom ? MSG_FC_INFO_SZ_EXTENDED :
+ MSG_FC_INFO_SZ, sizeof(*resp), &t);
if (ret)
goto err_out;
info = t->tx.buf;
info->domain = cpu_to_le32(domain);
info->message_id = cpu_to_le32(message_id);
+ if (custom)
+ info->custom = cpu_to_le32(*custom);
/*
* Bail out on error leaving fc_info addresses zeroed; this includes
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 4583d02bee1c..7f283f457e02 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -835,25 +835,25 @@ static void scmi_perf_domain_init_fc(const struct scmi_protocol_handle *ph,
return;
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LEVEL_GET, 4, dom->id,
+ PERF_LEVEL_GET, 4, dom->id, NULL,
&fc[PERF_FC_LEVEL].get_addr, NULL,
&fc[PERF_FC_LEVEL].rate_limit);
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LIMITS_GET, 8, dom->id,
+ PERF_LIMITS_GET, 8, dom->id, NULL,
&fc[PERF_FC_LIMIT].get_addr, NULL,
&fc[PERF_FC_LIMIT].rate_limit);
if (dom->info.set_perf)
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LEVEL_SET, 4, dom->id,
+ PERF_LEVEL_SET, 4, dom->id, NULL,
&fc[PERF_FC_LEVEL].set_addr,
&fc[PERF_FC_LEVEL].set_db,
&fc[PERF_FC_LEVEL].rate_limit);
if (dom->set_limits)
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LIMITS_SET, 8, dom->id,
+ PERF_LIMITS_SET, 8, dom->id, NULL,
&fc[PERF_FC_LIMIT].set_addr,
&fc[PERF_FC_LIMIT].set_db,
&fc[PERF_FC_LIMIT].rate_limit);
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index ab9733f4458b..22aff71c75e9 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -716,24 +716,24 @@ static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
return;
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_SET, 4, domain,
+ POWERCAP_CAP_SET, 4, domain, NULL,
&fc[POWERCAP_FC_CAP].set_addr,
&fc[POWERCAP_FC_CAP].set_db,
&fc[POWERCAP_FC_CAP].rate_limit);
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_GET, 4, domain,
+ POWERCAP_CAP_GET, 4, domain, NULL,
&fc[POWERCAP_FC_CAP].get_addr, NULL,
&fc[POWERCAP_FC_CAP].rate_limit);
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_SET, 4, domain,
+ POWERCAP_PAI_SET, 4, domain, NULL,
&fc[POWERCAP_FC_PAI].set_addr,
&fc[POWERCAP_FC_PAI].set_db,
&fc[POWERCAP_FC_PAI].rate_limit);
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_GET, 4, domain,
+ POWERCAP_PAI_GET, 4, domain, NULL,
&fc[POWERCAP_FC_PAI].get_addr, NULL,
&fc[POWERCAP_FC_PAI].rate_limit);
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index f51245aca259..e618d3141c95 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -280,7 +280,7 @@ struct scmi_proto_helpers_ops {
u32 message_id, u32 *attributes);
void (*fastchannel_init)(const struct scmi_protocol_handle *ph,
u8 describe_id, u32 message_id,
- u32 valid_size, u32 domain,
+ u32 valid_size, u32 domain, u32 *custom,
void __iomem **p_addr,
struct scmi_fc_db_info **p_db,
u32 *rate_limit);
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 02/12] firmware: arm_scmi: Refactor powercap domain layout
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
2026-04-28 9:09 ` [PATCH v5 01/12] firmware: arm_scmi: Add an optional custom parameter to fastchannel helpers Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 03/12] firmware: arm_scmi: Add SCMIv4.0 Powercap basic support Philip Radford
` (9 subsequent siblings)
11 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
From: Cristian Marussi <cristian.marussi@arm.com>
SCMIv4.0 introduces the idea of an optional Concurrent Power Limit (CPL)
for each powercap domain, where CPL0 coincides with the one and only
per-domain constraint limit that was available in pre-v4.0 SCMI Powercap.
Refactor the powercap domain descriptors and powercap operations to allow
future v4.0 extensions to cope with multiple CPLs.
While at that generalize the powercap protocol API to drop PAI references
in favour of a more generic avg_ivl naming, since from v4.0 the number and
types of averaging intervals will change in a non-backward compatible way,
so let's bury these changes within the protocol layer.
Last but not least, make the necessary changes to the ARM SCMI Powwercap
driver in order to support all of these new capabilities.
No functional change.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Adjusted domain_id comparitor in scmi_powercap_pai_get]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 182 +++++++++++++++++----------
drivers/powercap/arm_scmi_powercap.c | 50 ++++----
include/linux/scmi_protocol.h | 74 +++++++----
3 files changed, 188 insertions(+), 118 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 22aff71c75e9..47aa6dde4a52 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -2,7 +2,7 @@
/*
* System Control and Management Interface (SCMI) Powercap Protocol
*
- * Copyright (C) 2022 ARM Ltd.
+ * Copyright (C) 2022-2026 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications POWERCAP - " fmt
@@ -20,6 +20,8 @@
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000
+#define CPL0 0
+
enum scmi_powercap_protocol_cmd {
POWERCAP_DOMAIN_ATTRIBUTES = 0x3,
POWERCAP_CAP_GET = 0x4,
@@ -192,27 +194,26 @@ scmi_powercap_validate(unsigned int min_val, unsigned int max_val,
static int
scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo, u32 domain)
+ struct powercap_info *pinfo,
+ struct scmi_powercap_info *dom_info)
{
int ret;
u32 flags;
struct scmi_xfer *t;
- struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
struct scmi_msg_resp_powercap_domain_attributes *resp;
ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
- sizeof(domain), sizeof(*resp), &t);
+ sizeof(dom_info->id), sizeof(*resp), &t);
if (ret)
return ret;
- put_unaligned_le32(domain, t->tx.buf);
+ put_unaligned_le32(dom_info->id, t->tx.buf);
resp = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
flags = le32_to_cpu(resp->attributes);
- dom_info->id = domain;
if (pinfo->notify_cap_cmd)
dom_info->notify_powercap_cap_change =
SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags);
@@ -221,12 +222,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
dom_info->async_powercap_cap_set =
SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
- dom_info->powercap_cap_config =
- SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+
dom_info->powercap_monitoring =
SUPPORTS_POWERCAP_MONITORING(flags);
- dom_info->powercap_pai_config =
- SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
dom_info->powercap_scale_mw =
SUPPORTS_POWER_UNITS_MW(flags);
dom_info->powercap_scale_uw =
@@ -236,13 +234,29 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
- dom_info->min_pai = le32_to_cpu(resp->min_pai);
- dom_info->max_pai = le32_to_cpu(resp->max_pai);
- dom_info->pai_step = le32_to_cpu(resp->pai_step);
- ret = scmi_powercap_validate(dom_info->min_pai,
- dom_info->max_pai,
- dom_info->pai_step,
- dom_info->powercap_pai_config);
+ dom_info->sustainable_power =
+ le32_to_cpu(resp->sustainable_power);
+ dom_info->accuracy = le32_to_cpu(resp->accuracy);
+
+ dom_info->parent_id = le32_to_cpu(resp->parent_id);
+ if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
+ (dom_info->parent_id >= pinfo->num_domains ||
+ dom_info->parent_id == dom_info->id)) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent parent ID for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ ret = -ENODEV;
+ }
+
+ dom_info->cpli[0].avg_ivl_config =
+ SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
+ dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_pai);
+ dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_pai);
+ dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->pai_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
+ dom_info->cpli[0].max_avg_ivl,
+ dom_info->cpli[0].avg_ivl_step,
+ dom_info->cpli[0].avg_ivl_config);
if (ret) {
dev_err(ph->dev,
"Platform reported inconsistent PAI config for domain %d - %s\n",
@@ -250,13 +264,15 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
goto clean;
}
- dom_info->min_power_cap = le32_to_cpu(resp->min_power_cap);
- dom_info->max_power_cap = le32_to_cpu(resp->max_power_cap);
- dom_info->power_cap_step = le32_to_cpu(resp->power_cap_step);
- ret = scmi_powercap_validate(dom_info->min_power_cap,
- dom_info->max_power_cap,
- dom_info->power_cap_step,
- dom_info->powercap_cap_config);
+ dom_info->cpli[0].cap_config =
+ SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+ dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
+ dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
+ dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
+ dom_info->cpli[0].max_power_cap,
+ dom_info->cpli[0].power_cap_step,
+ dom_info->cpli[0].cap_config);
if (ret) {
dev_err(ph->dev,
"Platform reported inconsistent CAP config for domain %d - %s\n",
@@ -264,19 +280,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
goto clean;
}
- dom_info->sustainable_power =
- le32_to_cpu(resp->sustainable_power);
- dom_info->accuracy = le32_to_cpu(resp->accuracy);
-
- dom_info->parent_id = le32_to_cpu(resp->parent_id);
- if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
- (dom_info->parent_id >= pinfo->num_domains ||
- dom_info->parent_id == dom_info->id)) {
- dev_err(ph->dev,
- "Platform reported inconsistent parent ID for domain %d - %s\n",
- dom_info->id, dom_info->name);
- ret = -ENODEV;
- }
+ /* Just using same short name */
+ strscpy(dom_info->cpli[0].name, dom_info->name,
+ SCMI_SHORT_NAME_MAX_SIZE);
}
clean:
@@ -288,12 +294,30 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
*/
if (!ret && SUPPORTS_EXTENDED_NAMES(flags))
ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET,
- domain, NULL, dom_info->name,
+ dom_info->id, NULL, dom_info->name,
SCMI_MAX_STR_SIZE);
return ret;
}
+static int
+scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
+ struct powercap_info *pinfo, u32 domain)
+{
+ struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
+
+ dom_info->num_cpli = 1;
+ dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
+ sizeof(*dom_info->cpli), GFP_KERNEL);
+ if (!dom_info->cpli)
+ return -ENOMEM;
+
+ dom_info->id = domain;
+ dom_info->cpli[0].id = CPL0;
+
+ return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
+}
+
static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -335,10 +359,11 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *dom,
- u32 *power_cap)
+ u32 cpl_id, u32 *power_cap)
{
- if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) {
- *power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr);
+ if (dom->cpli[cpl_id].fc_info &&
+ dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
+ *power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
dom->id, *power_cap, 0);
return 0;
@@ -348,7 +373,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *power_cap)
+ u32 domain_id, u32 cpl_id, u32 *power_cap)
{
const struct scmi_powercap_info *dom;
@@ -359,12 +384,13 @@ static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
if (!dom)
return -EINVAL;
- return __scmi_powercap_cap_get(ph, dom, power_cap);
+ return __scmi_powercap_cap_get(ph, dom, cpl_id, power_cap);
}
static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *pc,
- u32 power_cap, bool ignore_dresp)
+ u32 cpl_id, u32 power_cap,
+ bool ignore_dresp)
{
int ret;
struct scmi_xfer *t;
@@ -406,21 +432,23 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
struct powercap_info *pi, u32 domain_id,
- u32 power_cap, bool ignore_dresp)
+ u32 cpl_id, u32 power_cap, bool ignore_dresp)
{
int ret = -EINVAL;
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_cap_config)
+ if (!pc || !pc->cpli[cpl_id].cap_config)
return ret;
if (power_cap &&
- (power_cap < pc->min_power_cap || power_cap > pc->max_power_cap))
+ (power_cap < pc->cpli[cpl_id].min_power_cap ||
+ power_cap > pc->cpli[cpl_id].max_power_cap))
return ret;
- if (pc->fc_info && pc->fc_info[POWERCAP_FC_CAP].set_addr) {
- struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_CAP];
+ if (pc->cpli[cpl_id].fc_info &&
+ pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].set_addr) {
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP];
iowrite32(power_cap, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
@@ -428,7 +456,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
domain_id, power_cap, 0);
ret = 0;
} else {
- ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap,
+ ret = scmi_powercap_xfer_cap_set(ph, pc, cpl_id, power_cap,
ignore_dresp);
}
@@ -440,7 +468,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 power_cap,
+ u32 domain_id, u32 cpl_id, u32 power_cap,
bool ignore_dresp)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -459,7 +487,7 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
return 0;
}
- return __scmi_powercap_cap_set(ph, pi, domain_id,
+ return __scmi_powercap_cap_set(ph, pi, domain_id, cpl_id,
power_cap, ignore_dresp);
}
@@ -485,7 +513,7 @@ static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *pai)
+ u32 domain_id, u32 cpl_id, u32 *pai)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
@@ -494,8 +522,11 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
return -EINVAL;
dom = pi->powercaps + domain_id;
- if (dom->fc_info && dom->fc_info[POWERCAP_FC_PAI].get_addr) {
- *pai = ioread32(dom->fc_info[POWERCAP_FC_PAI].get_addr);
+ if (cpl_id >= dom->num_cpli)
+ return -EINVAL;
+
+ if (dom->cpli[cpl_id].fc_info && dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr) {
+ *pai = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,
domain_id, *pai, 0);
return 0;
@@ -504,6 +535,12 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
return scmi_powercap_xfer_pai_get(ph, domain_id, pai);
}
+static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val)
+{
+ return scmi_powercap_pai_get(ph, domain_id, cpl_id, val);
+}
+
static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 pai)
{
@@ -528,17 +565,18 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 pai)
+ u32 domain_id, u32 cpl_id, u32 pai)
{
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_pai_config || !pai ||
- pai < pc->min_pai || pai > pc->max_pai)
+ if (!pc || cpl_id >= pc->num_cpli || !pc->cpli[cpl_id].avg_ivl_config ||
+ !pai || pai < pc->cpli[cpl_id].min_avg_ivl ||
+ pai > pc->cpli[cpl_id].max_avg_ivl)
return -EINVAL;
- if (pc->fc_info && pc->fc_info[POWERCAP_FC_PAI].set_addr) {
- struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_PAI];
+ if (pc->cpli[cpl_id].fc_info && pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].set_addr) {
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI];
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,
domain_id, pai, 0);
@@ -550,6 +588,12 @@ static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
return scmi_powercap_xfer_pai_set(ph, domain_id, pai);
}
+static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 val)
+{
+ return scmi_powercap_pai_set(ph, domain_id, cpl_id, val);
+}
+
static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *average_power,
u32 *pai)
@@ -645,11 +689,11 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
if (!pi->states[domain_id].last_pcap)
return -EINVAL;
- ret = __scmi_powercap_cap_set(ph, pi, domain_id,
+ ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0,
pi->states[domain_id].last_pcap,
true);
} else {
- ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true);
+ ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0, 0, true);
}
if (ret)
@@ -660,7 +704,7 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
* server could have ignored a disable request and kept enforcing some
* powercap limit requested by other agents.
*/
- ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+ ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
if (!ret)
pi->states[domain_id].enabled = !!power_cap;
@@ -682,7 +726,7 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
* Report always real platform state; platform could have ignored
* a previous disable request. Default true on any error.
*/
- ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+ ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
if (!ret)
*enable = !!power_cap;
@@ -699,8 +743,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.cap_set = scmi_powercap_cap_set,
.cap_enable_set = scmi_powercap_cap_enable_set,
.cap_enable_get = scmi_powercap_cap_enable_get,
- .pai_get = scmi_powercap_pai_get,
- .pai_set = scmi_powercap_pai_set,
+ .avg_interval_get = scmi_powercap_avg_interval_get,
+ .avg_interval_set = scmi_powercap_avg_interval_set,
.measurements_get = scmi_powercap_measurements_get,
.measurements_threshold_set = scmi_powercap_measurements_threshold_set,
.measurements_threshold_get = scmi_powercap_measurements_threshold_get,
@@ -991,18 +1035,18 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
* formed and correlated by sane parent-child relationship (if any).
*/
for (domain = 0; domain < pinfo->num_domains; domain++) {
- ret = scmi_powercap_domain_attributes_get(ph, pinfo, domain);
+ ret = scmi_powercap_domain_initialize(ph, pinfo, domain);
if (ret)
return ret;
if (pinfo->powercaps[domain].fastchannels)
scmi_powercap_domain_init_fc(ph, domain,
- &pinfo->powercaps[domain].fc_info);
+ &pinfo->powercaps[domain].cpli[CPL0].fc_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
ret = __scmi_powercap_cap_get(ph,
- &pinfo->powercaps[domain],
+ &pinfo->powercaps[domain], CPL0,
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index ab66e9a3b1e2..be3007206a74 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -97,7 +97,7 @@ static const struct powercap_zone_ops zone_ops = {
};
static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
- u64 power_limit_uw, u32 *norm)
+ u64 power_limit_uw, int cid, u32 *norm)
{
bool scale_mw = spz->info->powercap_scale_mw;
u64 val;
@@ -108,9 +108,9 @@ static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
* the range [min_power_cap, max_power_cap] whose bounds are assured to
* be two unsigned 32bits quantities.
*/
- *norm = clamp_t(u32, val, spz->info->min_power_cap,
- spz->info->max_power_cap);
- *norm = rounddown(*norm, spz->info->power_cap_step);
+ *norm = clamp_t(u32, val, spz->info->cpli[cid].min_power_cap,
+ spz->info->cpli[cid].max_power_cap);
+ *norm = rounddown(*norm, spz->info->cpli[cid].power_cap_step);
val = (scale_mw) ? *norm * 1000 : *norm;
if (power_limit_uw != val)
@@ -125,12 +125,12 @@ static int scmi_powercap_set_power_limit_uw(struct powercap_zone *pz, int cid,
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_power;
- if (!spz->info->powercap_cap_config)
+ if (!spz->info->cpli[cid].cap_config)
return -EINVAL;
- scmi_powercap_normalize_cap(spz, power_uw, &norm_power);
+ scmi_powercap_normalize_cap(spz, power_uw, cid, &norm_power);
- return powercap_ops->cap_set(spz->ph, spz->info->id, norm_power, false);
+ return powercap_ops->cap_set(spz->ph, spz->info->id, cid, norm_power, false);
}
static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
@@ -140,7 +140,7 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
u32 power;
int ret;
- ret = powercap_ops->cap_get(spz->ph, spz->info->id, &power);
+ ret = powercap_ops->cap_get(spz->ph, spz->info->id, cid, &power);
if (ret)
return ret;
@@ -152,19 +152,20 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
}
static void scmi_powercap_normalize_time(const struct scmi_powercap_zone *spz,
- u64 time_us, u32 *norm)
+ u64 time_us, int cid, u32 *norm)
{
/*
* This cast is lossless since here @time_us is certain to be within the
- * range [min_pai, max_pai] whose bounds are assured to be two unsigned
- * 32bits quantities.
+ * range [min_avg_ivl, max_avg_ivl] whose bounds are assured to be two
+ * unsigned 32bits quantities.
*/
- *norm = clamp_t(u32, time_us, spz->info->min_pai, spz->info->max_pai);
- *norm = rounddown(*norm, spz->info->pai_step);
+ *norm = clamp_t(u32, time_us, spz->info->cpli[cid].min_avg_ivl,
+ spz->info->cpli[cid].max_avg_ivl);
+ *norm = rounddown(*norm, spz->info->cpli[cid].avg_ivl_step);
if (time_us != *norm)
dev_dbg(spz->dev,
- "Normalized %s:PAI - requested:%llu - normalized:%u\n",
+ "Normalized %s:AVG_IVL - requested:%llu - normalized:%u\n",
spz->info->name, time_us, *norm);
}
@@ -174,12 +175,13 @@ static int scmi_powercap_set_time_window_us(struct powercap_zone *pz, int cid,
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_pai;
- if (!spz->info->powercap_pai_config)
+ if (!spz->info->cpli[cid].avg_ivl_config)
return -EINVAL;
- scmi_powercap_normalize_time(spz, time_window_us, &norm_pai);
+ scmi_powercap_normalize_time(spz, time_window_us, cid, &norm_pai);
- return powercap_ops->pai_set(spz->ph, spz->info->id, norm_pai);
+ return powercap_ops->avg_interval_set(spz->ph, spz->info->id,
+ cid, norm_pai);
}
static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
@@ -189,7 +191,7 @@ static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
int ret;
u32 pai;
- ret = powercap_ops->pai_get(spz->ph, spz->info->id, &pai);
+ ret = powercap_ops->avg_interval_get(spz->ph, spz->info->id, cid, &pai);
if (ret)
return ret;
@@ -203,7 +205,7 @@ static int scmi_powercap_get_max_power_uw(struct powercap_zone *pz, int cid,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *max_power_uw = spz->info->max_power_cap;
+ *max_power_uw = spz->info->cpli[cid].max_power_cap;
if (spz->info->powercap_scale_mw)
*max_power_uw *= 1000;
@@ -215,7 +217,7 @@ static int scmi_powercap_get_min_power_uw(struct powercap_zone *pz, int cid,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *min_power_uw = spz->info->min_power_cap;
+ *min_power_uw = spz->info->cpli[cid].min_power_cap;
if (spz->info->powercap_scale_mw)
*min_power_uw *= 1000;
@@ -227,7 +229,7 @@ static int scmi_powercap_get_max_time_window_us(struct powercap_zone *pz,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *time_window_us = spz->info->max_pai;
+ *time_window_us = spz->info->cpli[cid].max_avg_ivl;
return 0;
}
@@ -237,14 +239,16 @@ static int scmi_powercap_get_min_time_window_us(struct powercap_zone *pz,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *time_window_us = (u64)spz->info->min_pai;
+ *time_window_us = (u64)spz->info->cpli[cid].min_avg_ivl;
return 0;
}
static const char *scmi_powercap_get_name(struct powercap_zone *pz, int cid)
{
- return "SCMI power-cap";
+ struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
+
+ return spz->info->cpli[cid].name;
}
static const struct powercap_zone_constraint_ops constraint_ops = {
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..9918fb30100c 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,7 +2,7 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
@@ -609,6 +609,35 @@ struct scmi_voltage_proto_ops {
s32 *volt_uV);
};
+/**
+ * struct scmi_powercap_cpl_info - Describe one CPL - Concurrent Powercap Limit
+ *
+ * @id: CPL ID as advertised by the platform.
+ * @cap_config: CAP configuration support for this CPL.
+ * @min_power_cap: Minimum configurable CAP.
+ * @max_power_cap: Maximum configurable CAP.
+ * @power_cap_step: Step size between two consecutive CAP values.
+ * @avg_ivl_config: Powercap averaging interval configuration support.
+ * @min_avg_ivl: Minimum configurable powercap averaging interval.
+ * @max_avg_ivl: Maximum configurable powercap averaging interval.
+ * @avg_ivl_step: Step size between two consecutive averaging intervals.
+ * @name: name assigned to the Powercap Domain by platform.
+ * @fc_info: Reference to the FastChannels descriptors supported by this CPL
+ */
+struct scmi_powercap_cpl_info {
+ unsigned int id;
+ bool cap_config;
+ unsigned int min_power_cap;
+ unsigned int max_power_cap;
+ unsigned int power_cap_step;
+ bool avg_ivl_config;
+ unsigned int min_avg_ivl;
+ unsigned int max_avg_ivl;
+ unsigned int avg_ivl_step;
+ char name[SCMI_SHORT_NAME_MAX_SIZE];
+ struct scmi_fc_info *fc_info;
+};
+
/**
* struct scmi_powercap_info - Describe one available Powercap domain
*
@@ -617,21 +646,15 @@ struct scmi_voltage_proto_ops {
* @notify_powercap_measurement_change: MEASUREMENTS change notifications
* support.
* @async_powercap_cap_set: Asynchronous CAP set support.
- * @powercap_cap_config: CAP configuration support.
* @powercap_monitoring: Monitoring (measurements) support.
- * @powercap_pai_config: PAI configuration support.
* @powercap_scale_mw: Domain reports power data in milliwatt units.
* @powercap_scale_uw: Domain reports power data in microwatt units.
* Note that, when both @powercap_scale_mw and
* @powercap_scale_uw are set to false, the domain
* reports power data on an abstract linear scale.
+ * @extended_names: Support for long names.
+ * @fastchannels: Support for at least one fastchannel,
* @name: name assigned to the Powercap Domain by platform.
- * @min_pai: Minimum configurable PAI.
- * @max_pai: Maximum configurable PAI.
- * @pai_step: Step size between two consecutive PAI values.
- * @min_power_cap: Minimum configurable CAP.
- * @max_power_cap: Maximum configurable CAP.
- * @power_cap_step: Step size between two consecutive CAP values.
* @sustainable_power: Maximum sustainable power consumption for this domain
* under normal conditions.
* @accuracy: The accuracy with which the power is measured and reported in
@@ -639,30 +662,25 @@ struct scmi_voltage_proto_ops {
* @parent_id: Identifier of the containing parent power capping domain, or the
* value 0xFFFFFFFF if this powercap domain is a root domain not
* contained in any other domain.
+ * @num_cpli: Number of discovered CPLs.
+ * @cpli: Reference to an array holding descriptors to all the discovered CPLs.
*/
struct scmi_powercap_info {
unsigned int id;
bool notify_powercap_cap_change;
bool notify_powercap_measurement_change;
bool async_powercap_cap_set;
- bool powercap_cap_config;
bool powercap_monitoring;
- bool powercap_pai_config;
bool powercap_scale_mw;
bool powercap_scale_uw;
bool fastchannels;
char name[SCMI_MAX_STR_SIZE];
- unsigned int min_pai;
- unsigned int max_pai;
- unsigned int pai_step;
- unsigned int min_power_cap;
- unsigned int max_power_cap;
- unsigned int power_cap_step;
unsigned int sustainable_power;
unsigned int accuracy;
#define SCMI_POWERCAP_ROOT_ZONE_ID 0xFFFFFFFFUL
unsigned int parent_id;
- struct scmi_fc_info *fc_info;
+ unsigned int num_cpli;
+ struct scmi_powercap_cpl_info *cpli;
};
/**
@@ -691,8 +709,12 @@ struct scmi_powercap_info {
* on the system: for this reason @cap_get and @cap_enable_get
* will always report the final platform view of the powercaps.
* @cap_enable_get: get the current CAP enable status for the specified domain.
- * @pai_get: get the current PAI value for the specified domain.
- * @pai_set: set the PAI value for the specified domain to the provided value.
+ * @avg_interval_get: get the current averaging interval value for the specified
+ * domain. This will get the PAI or CAI depending on the used
+ * protocol version.
+ * @avg_interval_set: set the current averaging interval value for the specified
+ * domain. This will set the PAI or CAI depending on the used
+ * protocol version.
* @measurements_get: retrieve the current average power measurements for the
* specified domain and the related PAI upon which is
* calculated.
@@ -716,17 +738,17 @@ struct scmi_powercap_proto_ops {
const struct scmi_powercap_info __must_check *(*info_get)
(const struct scmi_protocol_handle *ph, u32 domain_id);
int (*cap_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 *power_cap);
+ u32 cpl_id, u32 *power_cap);
int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 power_cap, bool ignore_dresp);
+ u32 cpl_id, u32 power_cap, bool ignore_dresp);
int (*cap_enable_set)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool enable);
int (*cap_enable_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool *enable);
- int (*pai_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 *pai);
- int (*pai_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 pai);
+ int (*avg_interval_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val);
+ int (*avg_interval_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 val);
int (*measurements_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *average_power, u32 *pai);
int (*measurements_threshold_set)(const struct scmi_protocol_handle *ph,
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 03/12] firmware: arm_scmi: Add SCMIv4.0 Powercap basic support
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
2026-04-28 9:09 ` [PATCH v5 01/12] firmware: arm_scmi: Add an optional custom parameter to fastchannel helpers Philip Radford
2026-04-28 9:09 ` [PATCH v5 02/12] firmware: arm_scmi: Refactor powercap domain layout Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 04/12] firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support Philip Radford
` (8 subsequent siblings)
11 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
From: Cristian Marussi <cristian.marussi@arm.com>
Add SCMIv4.0 Powercap support for enumerating multiple CPLs of a domain
when available.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Fixed sparse issues where int was expected]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 472 +++++++++++++++++++++------
include/linux/scmi_protocol.h | 1 +
2 files changed, 376 insertions(+), 97 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 47aa6dde4a52..3291bde78367 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -33,6 +33,7 @@ enum scmi_powercap_protocol_cmd {
POWERCAP_CAP_NOTIFY = 0xa,
POWERCAP_MEASUREMENTS_NOTIFY = 0xb,
POWERCAP_DESCRIBE_FASTCHANNEL = 0xc,
+ POWERCAP_CPC_ATTRIBUTES = 0xd,
};
enum {
@@ -69,19 +70,58 @@ struct scmi_msg_resp_powercap_domain_attributes {
__le32 parent_id;
};
+struct scmi_msg_resp_powercap_domain_attributes_v3 {
+ __le32 attributes;
+#define SUPPORTS_POWERCAP_MAI_CONFIGURATION(x) ((x) & BIT(25))
+#define SUPPORTS_POWERCAP_FASTCHANNELS(x) ((x) & BIT(22))
+#define SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY_V3(x) ((x) & BIT(21))
+#define SUPPORTS_POWERCAP_CAI_CONFIGURATION(x) ((x) & BIT(20))
+ u8 name[SCMI_SHORT_NAME_MAX_SIZE];
+ __le32 min_mai;
+ __le32 max_mai;
+ __le32 mai_step;
+ __le32 min_power_cap;
+ __le32 max_power_cap;
+ __le32 power_cap_step;
+ __le32 sustainable_power;
+ __le32 accuracy;
+ __le32 parent_id;
+ __le32 min_cai;
+ __le32 max_cai;
+ __le32 cai_step;
+};
+
+struct scmi_msg_powercap_get_v3 {
+ __le32 domain_id;
+ __le32 cpli;
+};
+
struct scmi_msg_powercap_set_cap_or_pai {
- __le32 domain;
+ __le32 domain_id;
__le32 flags;
#define CAP_SET_ASYNC BIT(1)
#define CAP_SET_IGNORE_DRESP BIT(0)
__le32 value;
};
+struct scmi_msg_powercap_set_cap_v3 {
+ __le32 domain_id;
+ __le32 cpli;
+ __le32 flags;
+ __le32 power_cap;
+};
+
struct scmi_msg_resp_powercap_cap_set_complete {
- __le32 domain;
+ __le32 domain_id;
__le32 power_cap;
};
+struct scmi_msg_resp_powercap_cap_set_complete_v3 {
+ __le32 domain_id;
+ __le32 power_cap;
+ __le32 cpli;
+};
+
struct scmi_msg_resp_powercap_meas_get {
__le32 power;
__le32 pai;
@@ -112,6 +152,33 @@ struct scmi_powercap_meas_changed_notify_payld {
__le32 power;
};
+struct scmi_msg_powercap_cpc {
+ __le32 domain_id;
+ __le32 desc_index;
+};
+
+struct scmi_msg_resp_powercap_cpc {
+ __le32 num_cpl;
+#define NUM_RETURNED(n) (le32_get_bits((n), GENMASK(15, 0)))
+#define NUM_REMAINING(n) (le32_get_bits((n), GENMASK(31, 16)))
+ struct {
+ __le32 cpli;
+ __le32 flags;
+ __le32 min_power_cap;
+ __le32 max_power_cap;
+ __le32 power_cap_step;
+ __le32 min_cai;
+ __le32 max_cai;
+ __le32 cai_step;
+ u8 name[SCMI_SHORT_NAME_MAX_SIZE];
+ } desc[];
+};
+
+struct scmi_cpls_priv {
+ u32 domain_id;
+ struct scmi_powercap_cpl_info *cpli;
+};
+
struct scmi_powercap_state {
bool enabled;
u32 last_pcap;
@@ -129,6 +196,11 @@ struct powercap_info {
bool notify_measurements_cmd;
struct scmi_powercap_state *states;
struct scmi_powercap_info *powercaps;
+ int (*xfer_cap_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *power_cap);
+ int (*xfer_cap_set)(const struct scmi_protocol_handle *ph,
+ const struct scmi_powercap_info *pc,
+ u32 cpl_id, u32 power_cap, bool ignore_dresp);
};
static enum scmi_powercap_protocol_cmd evt_2_cmd[] = {
@@ -192,111 +264,240 @@ scmi_powercap_validate(unsigned int min_val, unsigned int max_val,
return 0;
}
+static void iter_powercap_cpls_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_powercap_cpc *msg = message;
+ const struct scmi_cpls_priv *p = priv;
+
+ msg->domain_id = cpu_to_le32(p->domain_id);
+ msg->desc_index = cpu_to_le32(desc_index);
+}
+
+static int iter_powercap_cpls_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_powercap_cpc *r = response;
+
+ st->num_returned = NUM_RETURNED(r->num_cpl);
+ st->num_remaining = NUM_REMAINING(r->num_cpl);
+
+ return 0;
+}
+
static int
-scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo,
- struct scmi_powercap_info *dom_info)
+iter_powercap_cpls_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
{
- int ret;
- u32 flags;
- struct scmi_xfer *t;
- struct scmi_msg_resp_powercap_domain_attributes *resp;
+ const struct scmi_msg_resp_powercap_cpc *r = response;
+ struct scmi_cpls_priv *p = priv;
+ struct scmi_powercap_cpl_info *cpl;
- ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
- sizeof(dom_info->id), sizeof(*resp), &t);
- if (ret)
- return ret;
+ cpl = &p->cpli[st->desc_index + st->loop_idx];
- put_unaligned_le32(dom_info->id, t->tx.buf);
- resp = t->rx.buf;
+ cpl->id = le32_to_cpu(r->desc[st->loop_idx].cpli);
+ cpl->cap_config = le32_to_cpu(r->desc[st->loop_idx].flags) & BIT(0);
- ret = ph->xops->do_xfer(ph, t);
- if (!ret) {
- flags = le32_to_cpu(resp->attributes);
+ cpl->min_power_cap = le32_to_cpu(r->desc[st->loop_idx].min_power_cap);
+ cpl->max_power_cap = le32_to_cpu(r->desc[st->loop_idx].max_power_cap);
+ cpl->power_cap_step = le32_to_cpu(r->desc[st->loop_idx].power_cap_step);
+ if (!cpl->power_cap_step && cpl->min_power_cap != cpl->max_power_cap)
+ return -EINVAL;
+
+ cpl->min_avg_ivl = le32_to_cpu(r->desc[st->loop_idx].min_cai);
+ cpl->max_avg_ivl = le32_to_cpu(r->desc[st->loop_idx].max_cai);
+ cpl->avg_ivl_step = le32_to_cpu(r->desc[st->loop_idx].cai_step);
+ if (!cpl->avg_ivl_step && cpl->min_avg_ivl != cpl->max_avg_ivl)
+ return -EINVAL;
+
+ cpl->avg_ivl_config = cpl->min_avg_ivl != cpl->max_avg_ivl;
+
+ strscpy(cpl->name, r->desc[st->loop_idx].name, SCMI_SHORT_NAME_MAX_SIZE);
+
+ return 0;
+}
- if (pinfo->notify_cap_cmd)
+static int scmi_powercap_cpls_enumerate(const struct scmi_protocol_handle *ph,
+ struct scmi_powercap_info *dom_info)
+{
+ void *iter;
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_powercap_cpls_prepare_message,
+ .update_state = iter_powercap_cpls_update_state,
+ .process_response = iter_powercap_cpls_process_response,
+ };
+ struct scmi_cpls_priv cpriv = {
+ .domain_id = dom_info->id,
+ .cpli = dom_info->cpli,
+ };
+
+ iter = ph->hops->iter_response_init(ph, &ops, dom_info->num_cpli,
+ POWERCAP_CPC_ATTRIBUTES,
+ sizeof(struct scmi_msg_powercap_cpc),
+ &cpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int
+scmi_powercap_domain_attrs_process(const struct scmi_protocol_handle *ph,
+ struct powercap_info *pinfo,
+ struct scmi_powercap_info *dom_info, void *r)
+{
+ struct scmi_msg_resp_powercap_domain_attributes *resp = r;
+ u32 flags = le32_to_cpu(resp->attributes);
+ bool cap_config;
+ int ret;
+
+ cap_config = SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
+ dom_info->num_cpli = 1;
+ } else {
+ dom_info->num_cpli = le32_get_bits(resp->attributes,
+ GENMASK(18, 15));
+ if (cap_config && !dom_info->num_cpli)
+ return -EINVAL;
+ }
+
+ dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
+ sizeof(*dom_info->cpli), GFP_KERNEL);
+ if (!dom_info->cpli)
+ return -ENOMEM;
+
+ if (pinfo->notify_cap_cmd) {
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3)
dom_info->notify_powercap_cap_change =
SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags);
- if (pinfo->notify_measurements_cmd)
- dom_info->notify_powercap_measurement_change =
- SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
- dom_info->async_powercap_cap_set =
- SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
-
- dom_info->powercap_monitoring =
- SUPPORTS_POWERCAP_MONITORING(flags);
- dom_info->powercap_scale_mw =
- SUPPORTS_POWER_UNITS_MW(flags);
- dom_info->powercap_scale_uw =
- SUPPORTS_POWER_UNITS_UW(flags);
- dom_info->fastchannels =
- SUPPORTS_POWERCAP_FASTCHANNELS(flags);
-
- strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
-
- dom_info->sustainable_power =
- le32_to_cpu(resp->sustainable_power);
- dom_info->accuracy = le32_to_cpu(resp->accuracy);
-
- dom_info->parent_id = le32_to_cpu(resp->parent_id);
- if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
- (dom_info->parent_id >= pinfo->num_domains ||
- dom_info->parent_id == dom_info->id)) {
- dev_err(ph->dev,
- "Platform reported inconsistent parent ID for domain %d - %s\n",
- dom_info->id, dom_info->name);
- ret = -ENODEV;
- }
+ else
+ dom_info->notify_powercap_cap_change =
+ SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY_V3(flags);
+ }
+
+ if (pinfo->notify_measurements_cmd)
+ dom_info->notify_powercap_measurement_change =
+ SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
+
+ dom_info->extended_names = SUPPORTS_EXTENDED_NAMES(flags);
+
+ dom_info->async_powercap_cap_set =
+ SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
+
+ dom_info->powercap_monitoring =
+ SUPPORTS_POWERCAP_MONITORING(flags);
+ dom_info->powercap_scale_mw =
+ SUPPORTS_POWER_UNITS_MW(flags);
+ dom_info->powercap_scale_uw =
+ SUPPORTS_POWER_UNITS_UW(flags);
+ dom_info->fastchannels =
+ SUPPORTS_POWERCAP_FASTCHANNELS(flags);
+
+ strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
+
+ dom_info->sustainable_power =
+ le32_to_cpu(resp->sustainable_power);
+ dom_info->accuracy = le32_to_cpu(resp->accuracy);
+
+ dom_info->parent_id = le32_to_cpu(resp->parent_id);
+ if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
+ (dom_info->parent_id >= pinfo->num_domains ||
+ dom_info->parent_id == dom_info->id)) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent parent ID for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ return -ENODEV;
+ }
+ dom_info->cpli[0].id = CPL0;
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3)
dom_info->cpli[0].avg_ivl_config =
SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
+ else
+ dom_info->cpli[0].avg_ivl_config =
+ SUPPORTS_POWERCAP_CAI_CONFIGURATION(flags);
+
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_pai);
dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_pai);
dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->pai_step);
- ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
- dom_info->cpli[0].max_avg_ivl,
- dom_info->cpli[0].avg_ivl_step,
- dom_info->cpli[0].avg_ivl_config);
- if (ret) {
- dev_err(ph->dev,
- "Platform reported inconsistent PAI config for domain %d - %s\n",
- dom_info->id, dom_info->name);
- goto clean;
- }
+ } else {
+ struct scmi_msg_resp_powercap_domain_attributes_v3 *resp = r;
- dom_info->cpli[0].cap_config =
- SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
- dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
- dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
- dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
- ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
- dom_info->cpli[0].max_power_cap,
- dom_info->cpli[0].power_cap_step,
- dom_info->cpli[0].cap_config);
- if (ret) {
- dev_err(ph->dev,
- "Platform reported inconsistent CAP config for domain %d - %s\n",
- dom_info->id, dom_info->name);
- goto clean;
- }
+ dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_cai);
+ dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_cai);
+ dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->cai_step);
+ }
+
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
+ dom_info->cpli[0].max_avg_ivl,
+ dom_info->cpli[0].avg_ivl_step,
+ dom_info->cpli[0].avg_ivl_config);
+ if (ret) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent PAI config for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ return ret;
+ }
- /* Just using same short name */
- strscpy(dom_info->cpli[0].name, dom_info->name,
- SCMI_SHORT_NAME_MAX_SIZE);
+ dom_info->cpli[0].cap_config = cap_config;
+ dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
+ dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
+ dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
+ dom_info->cpli[0].max_power_cap,
+ dom_info->cpli[0].power_cap_step,
+ dom_info->cpli[0].cap_config);
+ if (ret) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent CAP config for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ return ret;
}
+ /* Just using same short name */
+ strscpy(dom_info->cpli[0].name, dom_info->name, SCMI_SHORT_NAME_MAX_SIZE);
+
+ return 0;
+}
+
+static int
+scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
+ struct powercap_info *pinfo,
+ struct scmi_powercap_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_powercap_domain_attributes *resp;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
+ sizeof(dom_info->id), 0, &t);
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(dom_info->id, t->tx.buf);
+ resp = t->rx.buf;
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ ret = scmi_powercap_domain_attrs_process(ph, pinfo, dom_info, resp);
-clean:
ph->xops->xfer_put(ph, t);
/*
* If supported overwrite short name with the extended one;
* on error just carry on and use already provided short name.
*/
- if (!ret && SUPPORTS_EXTENDED_NAMES(flags))
+ if (!ret && dom_info->extended_names)
ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET,
dom_info->id, NULL, dom_info->name,
SCMI_MAX_STR_SIZE);
+ /* When protocol version > 0x3 there can possibly be more than 1 CPLs */
+ if (!ret && dom_info->num_cpli > 1)
+ ret = scmi_powercap_cpls_enumerate(ph, dom_info);
+
return ret;
}
@@ -306,14 +507,7 @@ scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
{
struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
- dom_info->num_cpli = 1;
- dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
- sizeof(*dom_info->cpli), GFP_KERNEL);
- if (!dom_info->cpli)
- return -ENOMEM;
-
dom_info->id = domain;
- dom_info->cpli[0].id = CPL0;
return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
}
@@ -337,7 +531,7 @@ scmi_powercap_dom_info_get(const struct scmi_protocol_handle *ph, u32 domain_id)
}
static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *power_cap)
+ u32 domain_id, u32 cpl_id, u32 *power_cap)
{
int ret;
struct scmi_xfer *t;
@@ -348,6 +542,33 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
return ret;
put_unaligned_le32(domain_id, t->tx.buf);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *power_cap = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_powercap_xfer_cap_get_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id,
+ u32 *power_cap)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_get_v3 *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_GET, sizeof(*msg),
+ sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->cpli = cpu_to_le32(cpl_id);
+
ret = ph->xops->do_xfer(ph, t);
if (!ret)
*power_cap = get_unaligned_le32(t->rx.buf);
@@ -361,6 +582,8 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *dom,
u32 cpl_id, u32 *power_cap)
{
+ struct powercap_info *pi = ph->get_priv(ph);
+
if (dom->cpli[cpl_id].fc_info &&
dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
*power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
@@ -369,7 +592,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
return 0;
}
- return scmi_powercap_xfer_cap_get(ph, dom->id, power_cap);
+ return pi->xfer_cap_get(ph, dom->id, cpl_id, power_cap);
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
@@ -402,7 +625,7 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
return ret;
msg = t->tx.buf;
- msg->domain = cpu_to_le32(pc->id);
+ msg->domain_id = cpu_to_le32(pc->id);
msg->flags =
cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, pc->async_powercap_cap_set) |
FIELD_PREP(CAP_SET_IGNORE_DRESP, ignore_dresp));
@@ -416,7 +639,7 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
struct scmi_msg_resp_powercap_cap_set_complete *resp;
resp = t->rx.buf;
- if (le32_to_cpu(resp->domain) == pc->id)
+ if (le32_to_cpu(resp->domain_id) == pc->id)
dev_dbg(ph->dev,
"Powercap ID %d CAP set async to %u\n",
pc->id,
@@ -430,6 +653,51 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
return ret;
}
+static int scmi_powercap_xfer_cap_set_v3(const struct scmi_protocol_handle *ph,
+ const struct scmi_powercap_info *pc,
+ u32 cpl_id, u32 power_cap,
+ bool ignore_dresp)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_set_cap_v3 *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(pc->id);
+ msg->cpli = cpu_to_le32(cpl_id);
+ msg->flags =
+ cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, pc->async_powercap_cap_set) |
+ FIELD_PREP(CAP_SET_IGNORE_DRESP, ignore_dresp));
+ msg->power_cap = cpu_to_le32(power_cap);
+
+ if (!pc->async_powercap_cap_set || ignore_dresp) {
+ ret = ph->xops->do_xfer(ph, t);
+ } else {
+ ret = ph->xops->do_xfer_with_response(ph, t);
+ if (!ret) {
+ struct scmi_msg_resp_powercap_cap_set_complete_v3 *resp;
+
+ resp = t->rx.buf;
+ if (le32_to_cpu(resp->domain_id) == pc->id &&
+ le32_to_cpu(resp->cpli) == pc->cpli[cpl_id].id)
+ dev_dbg(ph->dev,
+ "Powercap ID:%d/CPLI:%d CAP set async to %u\n",
+ pc->id, cpl_id,
+ get_unaligned_le32(&resp->power_cap));
+ else
+ ret = -EPROTO;
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
struct powercap_info *pi, u32 domain_id,
u32 cpl_id, u32 power_cap, bool ignore_dresp)
@@ -456,12 +724,12 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
domain_id, power_cap, 0);
ret = 0;
} else {
- ret = scmi_powercap_xfer_cap_set(ph, pc, cpl_id, power_cap,
- ignore_dresp);
+ ret = pi->xfer_cap_set(ph, pc, cpl_id, power_cap, ignore_dresp);
}
- /* Save the last explicitly set non-zero powercap value */
- if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && !ret && power_cap)
+ /* Save the last explicitly set non-zero powercap value for CPL0 */
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && !ret &&
+ cpl_id == CPL0 && power_cap)
pi->states[domain_id].last_pcap = power_cap;
return ret;
@@ -480,8 +748,8 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
if (!power_cap)
return -EINVAL;
- /* Just log the last set request if acting on a disabled domain */
- if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 &&
+ /* Just log the last set request on CPL0 on a disabled domain */
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && cpl_id == CPL0 &&
!pi->states[domain_id].enabled) {
pi->states[domain_id].last_pcap = power_cap;
return 0;
@@ -554,7 +822,7 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
return ret;
msg = t->tx.buf;
- msg->domain = cpu_to_le32(domain_id);
+ msg->domain_id = cpu_to_le32(domain_id);
msg->flags = cpu_to_le32(0);
msg->value = cpu_to_le32(pai);
@@ -1013,6 +1281,16 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (!pinfo)
return -ENOMEM;
+ ph->set_priv(ph, pinfo);
+
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
+ pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get;
+ pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set;
+ } else {
+ pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get_v3;
+ pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set_v3;
+ }
+
ret = scmi_powercap_attributes_get(ph, pinfo);
if (ret)
return ret;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 9918fb30100c..547ab4763a63 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -673,6 +673,7 @@ struct scmi_powercap_info {
bool powercap_monitoring;
bool powercap_scale_mw;
bool powercap_scale_uw;
+ bool extended_names;
bool fastchannels;
char name[SCMI_MAX_STR_SIZE];
unsigned int sustainable_power;
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 04/12] firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (2 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 03/12] firmware: arm_scmi: Add SCMIv4.0 Powercap basic support Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 05/12] firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support Philip Radford
` (7 subsequent siblings)
11 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
From: Cristian Marussi <cristian.marussi@arm.com>
Add support for new SCMIv4.0 Powercap Fastchannels.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: removed reference to old versioning logic]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
V2->V4
- moved fastchannel inits outside of loop
- renamed arguments for consistency
---
drivers/firmware/arm_scmi/powercap.c | 331 ++++++++++++++++++---------
1 file changed, 229 insertions(+), 102 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 3291bde78367..db5bc4f38ea4 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -27,19 +27,25 @@ enum scmi_powercap_protocol_cmd {
POWERCAP_CAP_GET = 0x4,
POWERCAP_CAP_SET = 0x5,
POWERCAP_PAI_GET = 0x6,
+ POWERCAP_MAI_GET = POWERCAP_PAI_GET,
POWERCAP_PAI_SET = 0x7,
+ POWERCAP_MAI_SET = POWERCAP_PAI_SET,
POWERCAP_DOMAIN_NAME_GET = 0x8,
POWERCAP_MEASUREMENTS_GET = 0x9,
POWERCAP_CAP_NOTIFY = 0xa,
POWERCAP_MEASUREMENTS_NOTIFY = 0xb,
POWERCAP_DESCRIBE_FASTCHANNEL = 0xc,
POWERCAP_CPC_ATTRIBUTES = 0xd,
+ POWERCAP_CAI_GET = 0xe,
+ POWERCAP_CAI_SET = 0xf,
};
enum {
POWERCAP_FC_CAP,
- POWERCAP_FC_PAI,
- POWERCAP_FC_MAX,
+ POWERCAP_FC_XAI,
+ POWERCAP_FC_MAI,
+ POWERCAP_FC_MEASUREMENT,
+ POWERCAP_FC_MAX
};
struct scmi_msg_resp_powercap_domain_attributes {
@@ -91,12 +97,12 @@ struct scmi_msg_resp_powercap_domain_attributes_v3 {
__le32 cai_step;
};
-struct scmi_msg_powercap_get_v3 {
+struct scmi_msg_powercap_cap_or_cai_get_v3 {
__le32 domain_id;
__le32 cpli;
};
-struct scmi_msg_powercap_set_cap_or_pai {
+struct scmi_msg_powercap_cap_or_pai_set {
__le32 domain_id;
__le32 flags;
#define CAP_SET_ASYNC BIT(1)
@@ -104,13 +110,20 @@ struct scmi_msg_powercap_set_cap_or_pai {
__le32 value;
};
-struct scmi_msg_powercap_set_cap_v3 {
+struct scmi_msg_powercap_cap_set_v3 {
__le32 domain_id;
__le32 cpli;
__le32 flags;
__le32 power_cap;
};
+struct scmi_msg_powercap_cai_set {
+ __le32 domain_id;
+ __le32 flags;
+ __le32 cai;
+ __le32 cpli;
+};
+
struct scmi_msg_resp_powercap_cap_set_complete {
__le32 domain_id;
__le32 power_cap;
@@ -201,6 +214,10 @@ struct powercap_info {
int (*xfer_cap_set)(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *pc,
u32 cpl_id, u32 power_cap, bool ignore_dresp);
+ int (*xfer_avg_ivl_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl);
+ int (*xfer_avg_ivl_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl);
};
static enum scmi_powercap_protocol_cmd evt_2_cmd[] = {
@@ -501,17 +518,6 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
return ret;
}
-static int
-scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo, u32 domain)
-{
- struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
-
- dom_info->id = domain;
-
- return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
-}
-
static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -558,7 +564,7 @@ static int scmi_powercap_xfer_cap_get_v3(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_get_v3 *msg;
+ struct scmi_msg_powercap_cap_or_cai_get_v3 *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_GET, sizeof(*msg),
sizeof(u32), &t);
@@ -617,7 +623,7 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_or_pai *msg;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
sizeof(*msg), 0, &t);
@@ -660,7 +666,7 @@ static int scmi_powercap_xfer_cap_set_v3(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_v3 *msg;
+ struct scmi_msg_powercap_cap_set_v3 *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
sizeof(*msg), 0, &t);
@@ -759,8 +765,9 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
power_cap, ignore_dresp);
}
-static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *pai)
+static int
+scmi_powercap_xfer_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl)
{
int ret;
struct scmi_xfer *t;
@@ -773,58 +780,105 @@ static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
put_unaligned_le32(domain_id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret)
- *pai = get_unaligned_le32(t->rx.buf);
+ *ivl = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_powercap_xfer_avg_interval_get_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_cai_get_v3 *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAI_GET, sizeof(*msg),
+ sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->cpli = cpu_to_le32(cpl_id);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *ivl = get_unaligned_le32(t->rx.buf);
ph->xops->xfer_put(ph, t);
return ret;
}
-static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 *pai)
+static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
- if (!pai || domain_id >= pi->num_domains)
+ if (!val || domain_id >= pi->num_domains)
return -EINVAL;
dom = pi->powercaps + domain_id;
if (cpl_id >= dom->num_cpli)
return -EINVAL;
- if (dom->cpli[cpl_id].fc_info && dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr) {
- *pai = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr);
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,
- domain_id, *pai, 0);
+ if (dom->cpli[cpl_id].fc_info &&
+ dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr) {
+ int trace_cmd = (PROTOCOL_REV_MAJOR(ph->version) < 0x3) ?
+ POWERCAP_PAI_GET : POWERCAP_CAI_GET;
+
+ *val = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, *val, 0);
return 0;
}
- return scmi_powercap_xfer_pai_get(ph, domain_id, pai);
+ return pi->xfer_avg_ivl_get(ph, domain_id, cpl_id, val);
}
-static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 *val)
+static int
+scmi_powercap_xfer_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
- return scmi_powercap_pai_get(ph, domain_id, cpl_id, val);
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET, sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->flags = cpu_to_le32(0);
+ msg->value = cpu_to_le32(ivl);
+
+ ret = ph->xops->do_xfer(ph, t);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
}
-static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 pai)
+static int
+scmi_powercap_xfer_avg_interval_set_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_or_pai *msg;
+ struct scmi_msg_powercap_cai_set *msg;
- ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET,
- sizeof(*msg), 0, &t);
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAI_SET, sizeof(*msg), 0, &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->domain_id = cpu_to_le32(domain_id);
msg->flags = cpu_to_le32(0);
- msg->value = cpu_to_le32(pai);
+ msg->cai = cpu_to_le32(ivl);
+ msg->cpli = cpu_to_le32(cpl_id);
ret = ph->xops->do_xfer(ph, t);
@@ -832,48 +886,42 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
return ret;
}
-static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 pai)
+static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
const struct scmi_powercap_info *pc;
+ struct powercap_info *pi = ph->get_priv(ph);
pc = scmi_powercap_dom_info_get(ph, domain_id);
if (!pc || cpl_id >= pc->num_cpli || !pc->cpli[cpl_id].avg_ivl_config ||
- !pai || pai < pc->cpli[cpl_id].min_avg_ivl ||
- pai > pc->cpli[cpl_id].max_avg_ivl)
+ !ivl || ivl < pc->cpli[cpl_id].min_avg_ivl ||
+ ivl > pc->cpli[cpl_id].max_avg_ivl)
return -EINVAL;
- if (pc->cpli[cpl_id].fc_info && pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].set_addr) {
- struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI];
+ /* Note that fc_info descriptors for any unsupported FC will be NULL */
+ if (pc->cpli[cpl_id].fc_info &&
+ pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].set_addr) {
+ int trace_cmd = (PROTOCOL_REV_MAJOR(ph->version) < 0x3) ?
+ POWERCAP_PAI_SET : POWERCAP_CAI_SET;
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI];
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,
- domain_id, pai, 0);
- iowrite32(pai, fci->set_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, ivl, 0);
+ iowrite32(ivl, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
return 0;
}
- return scmi_powercap_xfer_pai_set(ph, domain_id, pai);
-}
-
-static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 val)
-{
- return scmi_powercap_pai_set(ph, domain_id, cpl_id, val);
+ return pi->xfer_avg_ivl_set(ph, domain_id, cpl_id, ivl);
}
-static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *average_power,
- u32 *pai)
+static int
+scmi_powercap_xfer_measurements_get(const struct scmi_protocol_handle *ph,
+ const struct scmi_powercap_info *pc,
+ u32 *avg_power, u32 *avg_ivl)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_powercap_meas_get *resp;
- const struct scmi_powercap_info *pc;
-
- pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_monitoring || !pai || !average_power)
- return -EINVAL;
ret = ph->xops->xfer_get_init(ph, POWERCAP_MEASUREMENTS_GET,
sizeof(u32), sizeof(*resp), &t);
@@ -881,17 +929,42 @@ static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
return ret;
resp = t->rx.buf;
- put_unaligned_le32(domain_id, t->tx.buf);
+ put_unaligned_le32(pc->id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
- *average_power = le32_to_cpu(resp->power);
- *pai = le32_to_cpu(resp->pai);
+ *avg_power = le32_to_cpu(resp->power);
+ *avg_ivl = le32_to_cpu(resp->pai);
}
ph->xops->xfer_put(ph, t);
return ret;
}
+static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 *avg_power,
+ u32 *avg_ivl)
+{
+ const struct scmi_powercap_info *pc;
+ struct scmi_fc_info *fci;
+
+ pc = scmi_powercap_dom_info_get(ph, domain_id);
+ if (!pc || !pc->powercap_monitoring || !avg_ivl || !avg_power)
+ return -EINVAL;
+
+ /* Note that fc_info descriptors for any unsupported FC will be NULL */
+ fci = pc->cpli[CPL0].fc_info;
+ if (fci && fci[POWERCAP_FC_MEASUREMENT].get_addr) {
+ *avg_power = ioread32(fci[POWERCAP_FC_MEASUREMENT].get_addr);
+ /* See SCMIv4.0 3.10.2 - Payload is 32bit ONLY avg_power */
+ *avg_ivl = 0;
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MEASUREMENTS_GET,
+ pc->id, *avg_power, *avg_ivl);
+ return 0;
+ }
+
+ return scmi_powercap_xfer_measurements_get(ph, pc, avg_power, avg_ivl);
+}
+
static int
scmi_powercap_measurements_threshold_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_thresh_low,
@@ -1019,37 +1092,85 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
};
static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
- u32 domain, struct scmi_fc_info **p_fc)
+ struct scmi_powercap_info *dom_info)
{
- struct scmi_fc_info *fc;
-
- fc = devm_kcalloc(ph->dev, POWERCAP_FC_MAX, sizeof(*fc), GFP_KERNEL);
- if (!fc)
- return;
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_SET, 4, domain, NULL,
- &fc[POWERCAP_FC_CAP].set_addr,
- &fc[POWERCAP_FC_CAP].set_db,
- &fc[POWERCAP_FC_CAP].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_GET, 4, domain, NULL,
- &fc[POWERCAP_FC_CAP].get_addr, NULL,
- &fc[POWERCAP_FC_CAP].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_SET, 4, domain, NULL,
- &fc[POWERCAP_FC_PAI].set_addr,
- &fc[POWERCAP_FC_PAI].set_db,
- &fc[POWERCAP_FC_PAI].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_GET, 4, domain, NULL,
- &fc[POWERCAP_FC_PAI].get_addr, NULL,
- &fc[POWERCAP_FC_PAI].rate_limit);
-
- *p_fc = fc;
+ struct scmi_fc_info *fc_cpl0;
+
+ for (int id = 0; id < dom_info->num_cpli; id++) {
+ struct scmi_fc_info *fc;
+ u32 *cpl_id, zero_cpl_id = 0;
+
+ fc = devm_kcalloc(ph->dev, POWERCAP_FC_MAX, sizeof(*fc), GFP_KERNEL);
+ if (!fc)
+ return;
+
+ /* NOTE THAT when num_cpli == 1 the arg *cpl_id is 0 */
+ cpl_id = (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) ? &id : NULL;
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAP_SET, 4, dom_info->id,
+ cpl_id,
+ &fc[POWERCAP_FC_CAP].set_addr,
+ &fc[POWERCAP_FC_CAP].set_db,
+ &fc[POWERCAP_FC_CAP].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAP_GET, 4, dom_info->id,
+ cpl_id,
+ &fc[POWERCAP_FC_CAP].get_addr, NULL,
+ &fc[POWERCAP_FC_CAP].rate_limit);
+
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAI_SET, 4,
+ dom_info->id, cpl_id,
+ &fc[POWERCAP_FC_XAI].set_addr,
+ &fc[POWERCAP_FC_XAI].set_db,
+ &fc[POWERCAP_FC_XAI].rate_limit);
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAI_GET, 4,
+ dom_info->id, cpl_id,
+ &fc[POWERCAP_FC_XAI].get_addr, NULL,
+ &fc[POWERCAP_FC_XAI].rate_limit);
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MAI_SET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MAI].set_addr,
+ &fc[POWERCAP_FC_MAI].set_db,
+ &fc[POWERCAP_FC_MAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MAI_GET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MAI].get_addr, NULL,
+ &fc[POWERCAP_FC_MAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MEASUREMENTS_GET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MEASUREMENT].get_addr, NULL,
+ &fc[POWERCAP_FC_MEASUREMENT].rate_limit);
+ }
+
+ dom_info->cpli[id].fc_info = fc;
+ }
+
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
+ fc_cpl0 = dom_info->cpli[CPL0].fc_info;
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_PAI_SET, 4,
+ dom_info->id, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].set_addr,
+ &fc_cpl0[POWERCAP_FC_XAI].set_db,
+ &fc_cpl0[POWERCAP_FC_XAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_PAI_GET, 4,
+ dom_info->id, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].get_addr, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].rate_limit);
+ }
+
}
static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,
@@ -1286,9 +1407,14 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get;
pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set;
+ pinfo->xfer_avg_ivl_get = scmi_powercap_xfer_avg_interval_get;
+ pinfo->xfer_avg_ivl_set = scmi_powercap_xfer_avg_interval_set;
+
} else {
pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get_v3;
pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set_v3;
+ pinfo->xfer_avg_ivl_get = scmi_powercap_xfer_avg_interval_get_v3;
+ pinfo->xfer_avg_ivl_set = scmi_powercap_xfer_avg_interval_set_v3;
}
ret = scmi_powercap_attributes_get(ph, pinfo);
@@ -1313,18 +1439,19 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
* formed and correlated by sane parent-child relationship (if any).
*/
for (domain = 0; domain < pinfo->num_domains; domain++) {
- ret = scmi_powercap_domain_initialize(ph, pinfo, domain);
+ struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
+
+ dom_info->id = domain;
+ ret = scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
if (ret)
return ret;
- if (pinfo->powercaps[domain].fastchannels)
- scmi_powercap_domain_init_fc(ph, domain,
- &pinfo->powercaps[domain].cpli[CPL0].fc_info);
+ if (dom_info->fastchannels)
+ scmi_powercap_domain_init_fc(ph, dom_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
- ret = __scmi_powercap_cap_get(ph,
- &pinfo->powercaps[domain], CPL0,
+ ret = __scmi_powercap_cap_get(ph, dom_info, CPL0,
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 05/12] firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (3 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 04/12] firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 06/12] firmware: arm_scmi: Extend powercap report to include MAI Philip Radford
` (6 subsequent siblings)
11 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
From: Cristian Marussi <cristian.marussi@arm.com>
Extend notification support to the new SCMIv4.0 Powercap format that carry
also a CPL identifier where specified.
Since this addition completes SCMIv4.0 Powercap support bump also the
protocol version define.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 13 +++++++++----
include/linux/scmi_protocol.h | 3 ++-
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index db5bc4f38ea4..1d1188e98d49 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -18,7 +18,7 @@
#include "notify.h"
/* Updated only after ALL the mandatory features for that version are merged */
-#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x30000
#define CPL0 0
@@ -156,7 +156,8 @@ struct scmi_powercap_cap_changed_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 power_cap;
- __le32 pai;
+ __le32 avg_ivl;
+ __le32 cpli;
};
struct scmi_powercap_meas_changed_notify_payld {
@@ -1312,14 +1313,18 @@ scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_cap_changed_notify_payld *p = payld;
struct scmi_powercap_cap_changed_report *r = report;
- if (sizeof(*p) != payld_sz)
+ if (sizeof(*p) > payld_sz)
break;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->domain_id = le32_to_cpu(p->domain_id);
r->power_cap = le32_to_cpu(p->power_cap);
- r->pai = le32_to_cpu(p->pai);
+ r->avg_ivl = le32_to_cpu(p->avg_ivl);
+ if (sizeof(*p) == payld_sz)
+ r->cpli = le32_to_cpu(p->cpli);
+ else
+ r->cpli = 0;
*src_id = r->domain_id;
rep = r;
break;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 547ab4763a63..299fa8499b3f 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1125,7 +1125,8 @@ struct scmi_powercap_cap_changed_report {
unsigned int agent_id;
unsigned int domain_id;
unsigned int power_cap;
- unsigned int pai;
+ unsigned int avg_ivl;
+ unsigned int cpli;
};
struct scmi_powercap_meas_changed_report {
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 06/12] firmware: arm_scmi: Extend powercap report to include MAI
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (4 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 05/12] firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-05-05 20:13 ` Cristian Marussi
2026-04-28 9:09 ` [PATCH v5 07/12] include: trace: Add new parameter to trace_scmi_fc_call Philip Radford
` (5 subsequent siblings)
11 siblings, 1 reply; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
Extend scmi_powercap_meas_changed_report to include MAI change
notifications.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 20 ++++++++++++--------
include/linux/scmi_protocol.h | 1 +
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 1d1188e98d49..b9d50f4e8ae5 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -11,6 +11,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/scmi_protocol.h>
+#include <linux/stddef.h>
#include <trace/events/scmi.h>
@@ -164,6 +165,7 @@ struct scmi_powercap_meas_changed_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 power;
+ __le32 mai;
};
struct scmi_msg_powercap_cpc {
@@ -1212,13 +1214,6 @@ static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,
if (ret)
return ret;
- if (enable && !low && !high) {
- dev_err(ph->dev,
- "Invalid Measurements Notify thresholds: %u/%u\n",
- low, high);
- return -EINVAL;
- }
-
ret = ph->xops->xfer_get_init(ph, message_id,
sizeof(*notify), 0, &t);
if (ret)
@@ -1333,14 +1328,23 @@ scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph,
{
const struct scmi_powercap_meas_changed_notify_payld *p = payld;
struct scmi_powercap_meas_changed_report *r = report;
+ const size_t sz_v2 = offsetofend(struct scmi_powercap_meas_changed_notify_payld,
+ power);
+ const size_t sz_v3 = sizeof(*p);
- if (sizeof(*p) != payld_sz)
+ if (payld_sz != sz_v2 && payld_sz != sz_v3)
break;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->domain_id = le32_to_cpu(p->domain_id);
r->power = le32_to_cpu(p->power);
+
+ if (payld_sz == sz_v3 && PROTOCOL_REV_MAJOR(ph->version) >= 0x3)
+ r->mai = le32_to_cpu(p->mai);
+ else
+ r->mai = 0;
+
*src_id = r->domain_id;
rep = r;
break;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 299fa8499b3f..d0f6c0102559 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1134,5 +1134,6 @@ struct scmi_powercap_meas_changed_report {
unsigned int agent_id;
unsigned int domain_id;
unsigned int power;
+ unsigned int mai;
};
#endif /* _LINUX_SCMI_PROTOCOL_H */
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 07/12] include: trace: Add new parameter to trace_scmi_fc_call
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (5 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 06/12] firmware: arm_scmi: Extend powercap report to include MAI Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 08/12] powercap: arm_scmi: Enable multiple constraints support Philip Radford
` (4 subsequent siblings)
11 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
From: Cristian Marussi <cristian.marussi@arm.com>
Since SCMIv4.0 some of the supported Fastchannels can be configured using
an additional parameter like CPL_ID or Capability_ID.
Add equivalent support in the SCMI fastchannel traces to printout also such
parameter and fix all the existent call sites.
When such parameter is not used, it will simply show up as zero.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/perf.c | 8 ++++----
drivers/firmware/arm_scmi/powercap.c | 12 +++++++-----
include/trace/events/scmi.h | 12 +++++++-----
3 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 7f283f457e02..88d614e3184b 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -552,7 +552,7 @@ static int __scmi_perf_limits_set(const struct scmi_protocol_handle *ph,
struct scmi_fc_info *fci = &dom->fc_info[PERF_FC_LIMIT];
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LIMITS_SET,
- dom->id, min_perf, max_perf);
+ dom->id, 0, min_perf, max_perf);
iowrite32(max_perf, fci->set_addr);
iowrite32(min_perf, fci->set_addr + 4);
ph->hops->fastchannel_db_ring(fci->set_db);
@@ -636,7 +636,7 @@ static int __scmi_perf_limits_get(const struct scmi_protocol_handle *ph,
*max_perf = ioread32(fci->get_addr);
*min_perf = ioread32(fci->get_addr + 4);
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LIMITS_GET,
- dom->id, *min_perf, *max_perf);
+ dom->id, 0, *min_perf, *max_perf);
return 0;
}
@@ -706,7 +706,7 @@ static int __scmi_perf_level_set(const struct scmi_protocol_handle *ph,
struct scmi_fc_info *fci = &dom->fc_info[PERF_FC_LEVEL];
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LEVEL_SET,
- dom->id, level, 0);
+ dom->id, 0, level, 0);
iowrite32(level, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
return 0;
@@ -769,7 +769,7 @@ static int __scmi_perf_level_get(const struct scmi_protocol_handle *ph,
if (dom->fc_info && dom->fc_info[PERF_FC_LEVEL].get_addr) {
*level = ioread32(dom->fc_info[PERF_FC_LEVEL].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_PERF, PERF_LEVEL_GET,
- dom->id, *level, 0);
+ dom->id, 0, *level, 0);
return 0;
}
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index b9d50f4e8ae5..86262eb0f34a 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -597,7 +597,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
*power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
- dom->id, *power_cap, 0);
+ dom->id, cpl_id, *power_cap, 0);
return 0;
}
@@ -730,7 +730,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
iowrite32(power_cap, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_SET,
- domain_id, power_cap, 0);
+ domain_id, cpl_id, power_cap, 0);
ret = 0;
} else {
ret = pi->xfer_cap_set(ph, pc, cpl_id, power_cap, ignore_dresp);
@@ -835,7 +835,8 @@ static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
POWERCAP_PAI_GET : POWERCAP_CAI_GET;
*val = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr);
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, *val, 0);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id,
+ cpl_id, *val, 0);
return 0;
}
@@ -908,7 +909,8 @@ static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
POWERCAP_PAI_SET : POWERCAP_CAI_SET;
struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI];
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, ivl, 0);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id,
+ cpl_id, ivl, 0);
iowrite32(ivl, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
return 0;
@@ -961,7 +963,7 @@ static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
/* See SCMIv4.0 3.10.2 - Payload is 32bit ONLY avg_power */
*avg_ivl = 0;
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MEASUREMENTS_GET,
- pc->id, *avg_power, *avg_ivl);
+ pc->id, 0, *avg_power, *avg_ivl);
return 0;
}
diff --git a/include/trace/events/scmi.h b/include/trace/events/scmi.h
index 703b7bb68e44..b03da7323d04 100644
--- a/include/trace/events/scmi.h
+++ b/include/trace/events/scmi.h
@@ -10,13 +10,14 @@
#define TRACE_SCMI_MAX_TAG_LEN 6
TRACE_EVENT(scmi_fc_call,
- TP_PROTO(u8 protocol_id, u8 msg_id, u32 res_id, u32 val1, u32 val2),
- TP_ARGS(protocol_id, msg_id, res_id, val1, val2),
+ TP_PROTO(u8 protocol_id, u8 msg_id, u32 res_id, u32 sub_id, u32 val1, u32 val2),
+ TP_ARGS(protocol_id, msg_id, res_id, sub_id, val1, val2),
TP_STRUCT__entry(
__field(u8, protocol_id)
__field(u8, msg_id)
__field(u32, res_id)
+ __field(u32, sub_id)
__field(u32, val1)
__field(u32, val2)
),
@@ -25,13 +26,14 @@ TRACE_EVENT(scmi_fc_call,
__entry->protocol_id = protocol_id;
__entry->msg_id = msg_id;
__entry->res_id = res_id;
+ __entry->sub_id = sub_id;
__entry->val1 = val1;
__entry->val2 = val2;
),
- TP_printk("pt=%02X msg_id=%02X res_id:%u vals=%u:%u",
- __entry->protocol_id, __entry->msg_id,
- __entry->res_id, __entry->val1, __entry->val2)
+ TP_printk("pt=%02X msg_id=%02X res_id:%u sub_id:%u vals=%u:%u",
+ __entry->protocol_id, __entry->msg_id,
+ __entry->res_id, __entry->sub_id, __entry->val1, __entry->val2)
);
TRACE_EVENT(scmi_xfer_begin,
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 08/12] powercap: arm_scmi: Enable multiple constraints support
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (6 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 07/12] include: trace: Add new parameter to trace_scmi_fc_call Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support Philip Radford
` (3 subsequent siblings)
11 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Rafael J. Wysocki, Philip Radford
From: Cristian Marussi <cristian.marussi@arm.com>
Initialize the domains with all the discovered available constraints,
making available multiple per-domain constraints when the platform has
advertised support for multiple concurrent power limits.
CC: "Rafael J. Wysocki" <rafael@kernel.org>
CC: linux-pm@vger.kernel.org
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Amended Copyright]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/powercap/arm_scmi_powercap.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index be3007206a74..7f2bb162f96c 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -2,7 +2,7 @@
/*
* SCMI Powercap support.
*
- * Copyright (C) 2022 ARM Ltd.
+ * Copyright (C) 2022-2026 ARM Ltd.
*/
#include <linux/device.h>
@@ -311,7 +311,7 @@ static int scmi_powercap_register_zone(struct scmi_powercap_root *pr,
z = powercap_register_zone(&spz->zone, scmi_top_pcntrl, spz->info->name,
parent ? &parent->zone : NULL,
- &zone_ops, 1, &constraint_ops);
+ &zone_ops, spz->info->num_cpli, &constraint_ops);
if (!IS_ERR(z)) {
spz->height = scmi_powercap_get_zone_height(spz);
spz->registered = true;
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (7 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 08/12] powercap: arm_scmi: Enable multiple constraints support Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-05-05 20:36 ` Cristian Marussi
2026-04-28 9:09 ` [PATCH v5 10/12] powercap: arm_scmi: Create synthetic parent node for multi-instance Philip Radford
` (2 subsequent siblings)
11 siblings, 1 reply; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
Add support for Power Measurement Averaging Interval (MAI)
get and set operations to the SCMI powercap protocol driver.
Extends scmi_powercap_info to store MAI configuration and
implement MAI get/set via xfer and optional fast-channel
support.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 120 +++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 8 ++
2 files changed, 128 insertions(+)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 86262eb0f34a..b5879f204b5e 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -401,6 +401,34 @@ scmi_powercap_domain_attrs_process(const struct scmi_protocol_handle *ph,
dom_info->notify_powercap_measurement_change =
SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
+ struct scmi_msg_resp_powercap_domain_attributes_v3 *resp_v3 = r;
+
+ flags = le32_to_cpu(resp_v3->attributes);
+ if (pinfo->notify_measurements_cmd)
+ dom_info->notify_powercap_measurement_change =
+ SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
+
+ dom_info->mai_config = SUPPORTS_POWERCAP_MAI_CONFIGURATION(flags);
+ dom_info->min_mai = le32_to_cpu(resp_v3->min_mai);
+ dom_info->max_mai = le32_to_cpu(resp_v3->max_mai);
+ dom_info->mai_step = le32_to_cpu(resp_v3->mai_step);
+
+ if (dom_info->mai_config) {
+ ret = scmi_powercap_validate(dom_info->min_mai,
+ dom_info->max_mai,
+ dom_info->mai_step,
+ dom_info->mai_config);
+
+ if (ret) {
+ dev_warn(ph->dev, "Platform reported problem MAI config for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+
+ return ret;
+ }
+ }
+ }
+
dom_info->extended_names = SUPPORTS_EXTENDED_NAMES(flags);
dom_info->async_powercap_cap_set =
@@ -1082,6 +1110,96 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
return 0;
}
+static int scmi_powercap_xfer_mai_get(const struct scmi_protocol_handle *ph, u32 domain_id,
+ u32 *mai)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_GET, sizeof(u32),
+ sizeof(u32), &t);
+
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(domain_id, t->tx.buf);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *mai = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
+static int scmi_powercap_xfer_mai_set(const struct scmi_protocol_handle *ph, u32 domain_id, u32 mai)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_SET, sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->flags = cpu_to_le32(0);
+ msg->value = cpu_to_le32(mai);
+
+ ret = ph->xops->do_xfer(ph, t);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
+static int scmi_powercap_measurements_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 *val)
+{
+ const struct scmi_powercap_info *pc;
+ struct scmi_fc_info *fci;
+
+ if (!val)
+ return -EINVAL;
+
+ pc = scmi_powercap_dom_info_get(ph, domain_id);
+ if (!pc)
+ return -EINVAL;
+
+ fci = pc->cpli[CPL0].fc_info;
+ if (fci && fci[POWERCAP_FC_MAI].get_addr) {
+ *val = ioread32(fci[POWERCAP_FC_MAI].get_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_GET, domain_id, 0, *val, 0);
+ return 0;
+ }
+
+ return scmi_powercap_xfer_mai_get(ph, domain_id, val);
+}
+
+static int scmi_powercap_measurements_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 val)
+{
+ const struct scmi_powercap_info *pc;
+ struct scmi_fc_info *fci;
+
+ pc = scmi_powercap_dom_info_get(ph, domain_id);
+ if (!pc)
+ return -EINVAL;
+
+ if (!pc->mai_config || !val || val < pc->min_mai || val > pc->max_mai)
+ return -EINVAL;
+
+ fci = pc->cpli[CPL0].fc_info;
+ if (fci && fci[POWERCAP_FC_MAI].set_addr) {
+ iowrite32(val, fci[POWERCAP_FC_MAI].set_addr);
+ ph->hops->fastchannel_db_ring(fci[POWERCAP_FC_MAI].set_db);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_SET, domain_id, 0, val, 0);
+ return 0;
+ }
+
+ return scmi_powercap_xfer_mai_set(ph, domain_id, val);
+}
+
static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.num_domains_get = scmi_powercap_num_domains_get,
.info_get = scmi_powercap_dom_info_get,
@@ -1094,6 +1212,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.measurements_get = scmi_powercap_measurements_get,
.measurements_threshold_set = scmi_powercap_measurements_threshold_set,
.measurements_threshold_get = scmi_powercap_measurements_threshold_get,
+ .measurements_interval_get = scmi_powercap_measurements_interval_get,
+ .measurements_interval_set = scmi_powercap_measurements_interval_set,
};
static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index d0f6c0102559..73d66281dcc3 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -675,6 +675,10 @@ struct scmi_powercap_info {
bool powercap_scale_uw;
bool extended_names;
bool fastchannels;
+ bool mai_config;
+ u32 min_mai;
+ u32 max_mai;
+ u32 mai_step;
char name[SCMI_MAX_STR_SIZE];
unsigned int sustainable_power;
unsigned int accuracy;
@@ -758,6 +762,10 @@ struct scmi_powercap_proto_ops {
int (*measurements_threshold_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_thresh_low,
u32 *power_thresh_high);
+ int (*measurements_interval_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 *val);
+ int (*measurements_interval_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 val);
};
enum scmi_pinctrl_selector_type {
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 10/12] powercap: arm_scmi: Create synthetic parent node for multi-instance
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (8 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-05-05 22:03 ` Cristian Marussi
2026-04-28 9:09 ` [PATCH v5 11/12] powercap: arm_scmi: Add get_power_uw to synthetic node Philip Radford
2026-04-28 9:09 ` [PATCH v5 12/12] powercap: arm_scmi: Synthetic zone enable/disable Philip Radford
11 siblings, 1 reply; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
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
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 11/12] powercap: arm_scmi: Add get_power_uw to synthetic node
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (9 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 10/12] powercap: arm_scmi: Create synthetic parent node for multi-instance Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-05-05 22:13 ` Cristian Marussi
2026-04-28 9:09 ` [PATCH v5 12/12] powercap: arm_scmi: Synthetic zone enable/disable Philip Radford
11 siblings, 1 reply; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
Exposes the current power usage from the immediate children of
the synthetic (root) powercap node. Iterates over pr->spzones and
sums per-zone power.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/powercap/arm_scmi_powercap.c | 29 ++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index d74869af1633..81b5214acda4 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -270,9 +270,34 @@ static int instance_root_release(struct powercap_zone *pz)
return 0;
}
-static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *v)
+static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *power_uw)
{
- *v = 0;
+ struct scmi_powercap_zone *root = to_scmi_powercap_zone(pz);
+ struct scmi_powercap_zone *child;
+ struct scmi_powercap_root *pr;
+ u64 acc = 0;
+ u64 p;
+ int ret;
+
+ if (!pz || !power_uw)
+ return -EINVAL;
+
+ pr = container_of(root, struct scmi_powercap_root, instance_root);
+ if (!pr)
+ return -ENODEV;
+
+ list_for_each_entry(child, &pr->registered_zones[0], node) {
+ if (child == &pr->instance_root)
+ continue;
+
+ ret = scmi_powercap_get_power_uw(&child->zone, &p);
+ if (!ret)
+ acc += p;
+ else
+ dev_dbg(child->dev, "Failed to read child power: %u\n", ret);
+ }
+
+ *power_uw = acc;
return 0;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v5 12/12] powercap: arm_scmi: Synthetic zone enable/disable
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
` (10 preceding siblings ...)
2026-04-28 9:09 ` [PATCH v5 11/12] powercap: arm_scmi: Add get_power_uw to synthetic node Philip Radford
@ 2026-04-28 9:09 ` Philip Radford
2026-05-05 22:28 ` Cristian Marussi
11 siblings, 1 reply; 21+ messages in thread
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
Add functionality to disable and enable the synthetic zone which
also affects the immediate children of the synthetic zone by applying
the same command to them.
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/powercap/arm_scmi_powercap.c | 81 ++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index 81b5214acda4..1ed2949b06cb 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -270,6 +270,85 @@ static int instance_root_release(struct powercap_zone *pz)
return 0;
}
+static int instance_root_set_enable_state(struct powercap_zone *pz, bool enable)
+{
+ struct scmi_powercap_zone *root;
+ struct scmi_powercap_root *pr;
+ struct scmi_powercap_zone *child;
+ int ret, first_err = 0;
+
+ if (!pz)
+ return -EINVAL;
+
+ root = to_scmi_powercap_zone(pz);
+ pr = container_of(root, struct scmi_powercap_root, instance_root);
+
+ list_for_each_entry(child, &pr->registered_zones[0], node) {
+ if (child == &pr->instance_root)
+ continue;
+
+ if (child->info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID)
+ continue;
+
+ if (!child->info->cpli[0].cap_config)
+ continue;
+
+ ret = powercap_ops->cap_enable_set(child->ph, child->info->id, enable);
+
+ if (ret && !first_err) {
+ first_err = ret;
+ dev_err(child->dev, "failed to %s zone %s: %d\n",
+ enable ? "enable" : "disable",
+ child->info->name, ret);
+ }
+ }
+
+ return first_err;
+}
+
+static int instance_root_set_enable(struct powercap_zone *pz, bool mode)
+{
+ return instance_root_set_enable_state(pz, mode);
+}
+
+static int instance_root_get_enable(struct powercap_zone *pz, bool *mode)
+{
+ struct scmi_powercap_zone *root;
+ struct scmi_powercap_root *pr;
+ struct scmi_powercap_zone *child;
+ bool enabled;
+ int ret;
+
+ if (!pz || !mode)
+ return -EINVAL;
+
+ root = to_scmi_powercap_zone(pz);
+ pr = container_of(root, struct scmi_powercap_root, instance_root);
+
+ *mode = true;
+
+ list_for_each_entry(child, &pr->registered_zones[0], node) {
+ if (child == &pr->instance_root)
+ continue;
+ if (child->info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID)
+ continue;
+ if (!child->info->cpli[0].cap_config)
+ continue;
+
+ ret = powercap_ops->cap_enable_get(child->ph, child->info->id, &enabled);
+
+ if (ret)
+ return ret;
+
+ if (!enabled) {
+ *mode = false;
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *power_uw)
{
struct scmi_powercap_zone *root = to_scmi_powercap_zone(pz);
@@ -315,6 +394,8 @@ 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,
+ .set_enable = instance_root_set_enable,
+ .get_enable = instance_root_get_enable,
};
static const struct powercap_zone_constraint_ops instance_root_const_ops = {
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH v5 06/12] firmware: arm_scmi: Extend powercap report to include MAI
2026-04-28 9:09 ` [PATCH v5 06/12] firmware: arm_scmi: Extend powercap report to include MAI Philip Radford
@ 2026-05-05 20:13 ` Cristian Marussi
2026-05-05 21:21 ` Philip Radford
0 siblings, 1 reply; 21+ messages in thread
From: Cristian Marussi @ 2026-05-05 20:13 UTC (permalink / raw)
To: Philip Radford
Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty, cristian.marussi
On Tue, Apr 28, 2026 at 10:09:15AM +0100, Philip Radford wrote:
> Extend scmi_powercap_meas_changed_report to include MAI change
> notifications.
>
Hi
> Signed-off-by: Philip Radford <philip.radford@arm.com>
> ---
> drivers/firmware/arm_scmi/powercap.c | 20 ++++++++++++--------
> include/linux/scmi_protocol.h | 1 +
> 2 files changed, 13 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
> index 1d1188e98d49..b9d50f4e8ae5 100644
> --- a/drivers/firmware/arm_scmi/powercap.c
> +++ b/drivers/firmware/arm_scmi/powercap.c
> @@ -11,6 +11,7 @@
> #include <linux/io.h>
> #include <linux/module.h>
> #include <linux/scmi_protocol.h>
> +#include <linux/stddef.h>
>
> #include <trace/events/scmi.h>
>
> @@ -164,6 +165,7 @@ struct scmi_powercap_meas_changed_notify_payld {
> __le32 agent_id;
> __le32 domain_id;
> __le32 power;
> + __le32 mai;
> };
>
> struct scmi_msg_powercap_cpc {
> @@ -1212,13 +1214,6 @@ static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,
> if (ret)
> return ret;
>
> - if (enable && !low && !high) {
> - dev_err(ph->dev,
> - "Invalid Measurements Notify thresholds: %u/%u\n",
> - low, high);
> - return -EINVAL;
> - }
> -
Ok so you removed this check because now that a notification can be
emitted even only to notify a MAI change, it is possible that the
thresholds are zero and the notification will be emitted anyway due to
a MAI change....BUT you have to review or completely remove the comment
block that precedes this that says:
/*
* Note that we have to pick the most recently configured
* thresholds to build a proper POWERCAP_MEASUREMENTS_NOTIFY
* enable request and we fail, complaining, if no thresholds
* were ever set, since this is an indication the API has been
* used wrongly.
*/
...becasue NOW is no more true and misleading, since you just removed the
fail and complain part...
It would be good to shortly explain in a comment the new possible
scenarios in which notification can be enabled...
> ret = ph->xops->xfer_get_init(ph, message_id,
> sizeof(*notify), 0, &t);
> if (ret)
> @@ -1333,14 +1328,23 @@ scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph,
> {
> const struct scmi_powercap_meas_changed_notify_payld *p = payld;
> struct scmi_powercap_meas_changed_report *r = report;
> + const size_t sz_v2 = offsetofend(struct scmi_powercap_meas_changed_notify_payld,
> + power);
> + const size_t sz_v3 = sizeof(*p);
While this is a valid and nice construct, I think is overkill here since
these offsets/sizes will never change at runtime....
...you can just define above a couples of DEFINE that hardcodes the
sizes of the v2 and v3 by using the
#define SZ_V2 (sizeof(struct scmi_powercap_meas_changed_notify_payld))
and
#define SZ_V3 (SZ_V2 - sizeof(__le32))
>
> - if (sizeof(*p) != payld_sz)
> + if (payld_sz != sz_v2 && payld_sz != sz_v3)
> break;
>
> r->timestamp = timestamp;
> r->agent_id = le32_to_cpu(p->agent_id);
> r->domain_id = le32_to_cpu(p->domain_id);
> r->power = le32_to_cpu(p->power);
maybe more clear to simply:
r->mai = 0;
if (payld_sz == SZ_V3 && PROTOCOL_REV_MAJOR(ph->version) >= 0x3)
r->mai = le32_to_cpu(p->mai);
> +
> + if (payld_sz == sz_v3 && PROTOCOL_REV_MAJOR(ph->version) >= 0x3)
> + r->mai = le32_to_cpu(p->mai);
> + else
> + r->mai = 0;
> +
> *src_id = r->domain_id;
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support
2026-04-28 9:09 ` [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support Philip Radford
@ 2026-05-05 20:36 ` Cristian Marussi
2026-05-05 21:44 ` Philip Radford
0 siblings, 1 reply; 21+ messages in thread
From: Cristian Marussi @ 2026-05-05 20:36 UTC (permalink / raw)
To: Philip Radford
Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty, cristian.marussi
On Tue, Apr 28, 2026 at 10:09:18AM +0100, Philip Radford wrote:
> Add support for Power Measurement Averaging Interval (MAI)
Hi,
> get and set operations to the SCMI powercap protocol driver.
> Extends scmi_powercap_info to store MAI configuration and
> implement MAI get/set via xfer and optional fast-channel
> support.
You have to stay under 75 chars...ok...but I'd say this commit message
lines are way to short...you can stretch a bit more towards 75chars
without having to split words I think....because
t
o
o
s
h
o
r
t
l
i
n
e
s
are not so good anyway :P
>
> Signed-off-by: Philip Radford <philip.radford@arm.com>
> ---
> drivers/firmware/arm_scmi/powercap.c | 120 +++++++++++++++++++++++++++
> include/linux/scmi_protocol.h | 8 ++
> 2 files changed, 128 insertions(+)
>
> diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
> index 86262eb0f34a..b5879f204b5e 100644
> --- a/drivers/firmware/arm_scmi/powercap.c
> +++ b/drivers/firmware/arm_scmi/powercap.c
> @@ -401,6 +401,34 @@ scmi_powercap_domain_attrs_process(const struct scmi_protocol_handle *ph,
> dom_info->notify_powercap_measurement_change =
> SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
>
> + if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
> + struct scmi_msg_resp_powercap_domain_attributes_v3 *resp_v3 = r;
> +
> + flags = le32_to_cpu(resp_v3->attributes);
> + if (pinfo->notify_measurements_cmd)
> + dom_info->notify_powercap_measurement_change =
> + SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
> +
> + dom_info->mai_config = SUPPORTS_POWERCAP_MAI_CONFIGURATION(flags);
> + dom_info->min_mai = le32_to_cpu(resp_v3->min_mai);
> + dom_info->max_mai = le32_to_cpu(resp_v3->max_mai);
> + dom_info->mai_step = le32_to_cpu(resp_v3->mai_step);
> +
> + if (dom_info->mai_config) {
> + ret = scmi_powercap_validate(dom_info->min_mai,
> + dom_info->max_mai,
> + dom_info->mai_step,
> + dom_info->mai_config);
> +
> + if (ret) {
> + dev_warn(ph->dev, "Platform reported problem MAI config for domain %d - %s\n",
"....reported invalid MAI config for domain..."
> + dom_info->id, dom_info->name);
> +
> + return ret;
> + }
> + }
> + }
> +
> dom_info->extended_names = SUPPORTS_EXTENDED_NAMES(flags);
>
> dom_info->async_powercap_cap_set =
> @@ -1082,6 +1110,96 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
> return 0;
> }
>
> +static int scmi_powercap_xfer_mai_get(const struct scmi_protocol_handle *ph, u32 domain_id,
> + u32 *mai)
..bad alignment and till now we try still to stick tpo 80cols in the SCMI
stack if it does NOT really hamper readability...
> +{
> + int ret;
> + struct scmi_xfer *t;
> +
> + ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_GET, sizeof(u32),
> + sizeof(u32), &t);
...terrible alignment...and you know why :D
> +
> + if (ret)
> + return ret;
> +
> + put_unaligned_le32(domain_id, t->tx.buf);
> +
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret)
> + *mai = get_unaligned_le32(t->rx.buf);
> +
> + ph->xops->xfer_put(ph, t);
> + return ret;
> +}
> +
> +static int scmi_powercap_xfer_mai_set(const struct scmi_protocol_handle *ph, u32 domain_id, u32 mai)
..same...try to stick to 80 cols when not impossibly ugly..
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_msg_powercap_cap_or_pai_set *msg;
> +
> + ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_SET, sizeof(*msg), 0, &t);
same
> + if (ret)
> + return ret;
> +
> + msg = t->tx.buf;
> + msg->domain_id = cpu_to_le32(domain_id);
> + msg->flags = cpu_to_le32(0);
> + msg->value = cpu_to_le32(mai);
> +
> + ret = ph->xops->do_xfer(ph, t);
> +
> + ph->xops->xfer_put(ph, t);
> + return ret;
> +}
> +
> +static int scmi_powercap_measurements_interval_get(const struct scmi_protocol_handle *ph,
> + u32 domain_id, u32 *val)
> +{
ditto
> + const struct scmi_powercap_info *pc;
> + struct scmi_fc_info *fci;
> +
> + if (!val)
> + return -EINVAL;
> +
> + pc = scmi_powercap_dom_info_get(ph, domain_id);
> + if (!pc)
> + return -EINVAL;
> +
> + fci = pc->cpli[CPL0].fc_info;
> + if (fci && fci[POWERCAP_FC_MAI].get_addr) {
> + *val = ioread32(fci[POWERCAP_FC_MAI].get_addr);
> + trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_GET, domain_id, 0, *val, 0);
ditto
> + return 0;
> + }
> +
> + return scmi_powercap_xfer_mai_get(ph, domain_id, val);
> +}
> +
> +static int scmi_powercap_measurements_interval_set(const struct scmi_protocol_handle *ph,
> + u32 domain_id, u32 val)
> +{
ditto
> + const struct scmi_powercap_info *pc;
> + struct scmi_fc_info *fci;
> +
> + pc = scmi_powercap_dom_info_get(ph, domain_id);
> + if (!pc)
> + return -EINVAL;
> +
> + if (!pc->mai_config || !val || val < pc->min_mai || val > pc->max_mai)
> + return -EINVAL;
> +
> + fci = pc->cpli[CPL0].fc_info;
> + if (fci && fci[POWERCAP_FC_MAI].set_addr) {
> + iowrite32(val, fci[POWERCAP_FC_MAI].set_addr);
> + ph->hops->fastchannel_db_ring(fci[POWERCAP_FC_MAI].set_db);
> + trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_SET, domain_id, 0, val, 0);
> + return 0;
> + }
> +
> + return scmi_powercap_xfer_mai_set(ph, domain_id, val);
> +}
> +
> static const struct scmi_powercap_proto_ops powercap_proto_ops = {
> .num_domains_get = scmi_powercap_num_domains_get,
> .info_get = scmi_powercap_dom_info_get,
> @@ -1094,6 +1212,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
> .measurements_get = scmi_powercap_measurements_get,
> .measurements_threshold_set = scmi_powercap_measurements_threshold_set,
> .measurements_threshold_get = scmi_powercap_measurements_threshold_get,
> + .measurements_interval_get = scmi_powercap_measurements_interval_get,
> + .measurements_interval_set = scmi_powercap_measurements_interval_set,
> };
>
> static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
> diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> index d0f6c0102559..73d66281dcc3 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h
> @@ -675,6 +675,10 @@ struct scmi_powercap_info {
> bool powercap_scale_uw;
> bool extended_names;
> bool fastchannels;
> + bool mai_config;
> + u32 min_mai;
> + u32 max_mai;
> + u32 mai_step;
No docs for new fields ?
> char name[SCMI_MAX_STR_SIZE];
> unsigned int sustainable_power;
> unsigned int accuracy;
> @@ -758,6 +762,10 @@ struct scmi_powercap_proto_ops {
> int (*measurements_threshold_get)(const struct scmi_protocol_handle *ph,
> u32 domain_id, u32 *power_thresh_low,
> u32 *power_thresh_high);
> + int (*measurements_interval_get)(const struct scmi_protocol_handle *ph,
> + u32 domain_id, u32 *val);
> + int (*measurements_interval_set)(const struct scmi_protocol_handle *ph,
> + u32 domain_id, u32 val);
No docs for new fields ?
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v5 06/12] firmware: arm_scmi: Extend powercap report to include MAI
2026-05-05 20:13 ` Cristian Marussi
@ 2026-05-05 21:21 ` Philip Radford
0 siblings, 0 replies; 21+ messages in thread
From: Philip Radford @ 2026-05-05 21:21 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty
On Tue, May 05, 2026 at 09:13:32PM +0100, Cristian Marussi wrote:
> On Tue, Apr 28, 2026 at 10:09:15AM +0100, Philip Radford wrote:
> > Extend scmi_powercap_meas_changed_report to include MAI change
> > notifications.
> >
>
> Hi
>
Hi,
Thanks for the review.
> > Signed-off-by: Philip Radford <philip.radford@arm.com>
> > ---
> > drivers/firmware/arm_scmi/powercap.c | 20 ++++++++++++--------
> > include/linux/scmi_protocol.h | 1 +
> > 2 files changed, 13 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
> > index 1d1188e98d49..b9d50f4e8ae5 100644
> > --- a/drivers/firmware/arm_scmi/powercap.c
> > +++ b/drivers/firmware/arm_scmi/powercap.c
> > @@ -11,6 +11,7 @@
> > #include <linux/io.h>
> > #include <linux/module.h>
> > #include <linux/scmi_protocol.h>
> > +#include <linux/stddef.h>
> >
> > #include <trace/events/scmi.h>
> >
> > @@ -164,6 +165,7 @@ struct scmi_powercap_meas_changed_notify_payld {
> > __le32 agent_id;
> > __le32 domain_id;
> > __le32 power;
> > + __le32 mai;
> > };
> >
> > struct scmi_msg_powercap_cpc {
> > @@ -1212,13 +1214,6 @@ static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,
> > if (ret)
> > return ret;
> >
> > - if (enable && !low && !high) {
> > - dev_err(ph->dev,
> > - "Invalid Measurements Notify thresholds: %u/%u\n",
> > - low, high);
> > - return -EINVAL;
> > - }
> > -
>
> Ok so you removed this check because now that a notification can be
> emitted even only to notify a MAI change, it is possible that the
> thresholds are zero and the notification will be emitted anyway due to
> a MAI change....BUT you have to review or completely remove the comment
> block that precedes this that says:
>
> /*
> * Note that we have to pick the most recently configured
> * thresholds to build a proper POWERCAP_MEASUREMENTS_NOTIFY
> * enable request and we fail, complaining, if no thresholds
> * were ever set, since this is an indication the API has been
> * used wrongly.
> */
>
> ...becasue NOW is no more true and misleading, since you just removed the
> fail and complain part...
>
> It would be good to shortly explain in a comment the new possible
> scenarios in which notification can be enabled...
>
Apologies, I have overlooked this comment. I will amend it.
> > ret = ph->xops->xfer_get_init(ph, message_id,
> > sizeof(*notify), 0, &t);
> > if (ret)
> > @@ -1333,14 +1328,23 @@ scmi_powercap_fill_custom_report(const struct scmi_protocol_handle *ph,
> > {
> > const struct scmi_powercap_meas_changed_notify_payld *p = payld;
> > struct scmi_powercap_meas_changed_report *r = report;
> > + const size_t sz_v2 = offsetofend(struct scmi_powercap_meas_changed_notify_payld,
> > + power);
> > + const size_t sz_v3 = sizeof(*p);
>
> While this is a valid and nice construct, I think is overkill here since
> these offsets/sizes will never change at runtime....
>
> ...you can just define above a couples of DEFINE that hardcodes the
> sizes of the v2 and v3 by using the
>
> #define SZ_V2 (sizeof(struct scmi_powercap_meas_changed_notify_payld))
>
> and
> #define SZ_V3 (SZ_V2 - sizeof(__le32))
>
Noted, will do.
> >
> > - if (sizeof(*p) != payld_sz)
> > + if (payld_sz != sz_v2 && payld_sz != sz_v3)
> > break;
> >
> > r->timestamp = timestamp;
> > r->agent_id = le32_to_cpu(p->agent_id);
> > r->domain_id = le32_to_cpu(p->domain_id);
> > r->power = le32_to_cpu(p->power);
>
> maybe more clear to simply:
>
> r->mai = 0;
> if (payld_sz == SZ_V3 && PROTOCOL_REV_MAJOR(ph->version) >= 0x3)
> r->mai = le32_to_cpu(p->mai);
>
>
Agreed.
> > +
> > + if (payld_sz == sz_v3 && PROTOCOL_REV_MAJOR(ph->version) >= 0x3)
> > + r->mai = le32_to_cpu(p->mai);
> > + else
> > + r->mai = 0;
> > +
> > *src_id = r->domain_id;
>
Thanks,
Phil
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support
2026-05-05 20:36 ` Cristian Marussi
@ 2026-05-05 21:44 ` Philip Radford
2026-05-05 22:09 ` Cristian Marussi
0 siblings, 1 reply; 21+ messages in thread
From: Philip Radford @ 2026-05-05 21:44 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty
On Tue, May 05, 2026 at 09:36:26PM +0100, Cristian Marussi wrote:
> On Tue, Apr 28, 2026 at 10:09:18AM +0100, Philip Radford wrote:
> > Add support for Power Measurement Averaging Interval (MAI)
>
> Hi,
>
Hi,
Thanks for the review.
> > get and set operations to the SCMI powercap protocol driver.
> > Extends scmi_powercap_info to store MAI configuration and
> > implement MAI get/set via xfer and optional fast-channel
> > support.
>
> You have to stay under 75 chars...ok...but I'd say this commit message
> lines are way to short...you can stretch a bit more towards 75chars
> without having to split words I think....because
>
> t
> o
> o
>
> s
> h
> o
> r
> t
>
> l
> i
> n
> e
> s
>
> are not so good anyway :P
>
Noted. I was too concerned about going over the limit and didn't think
about the opposite being an issue.
> >
> > Signed-off-by: Philip Radford <philip.radford@arm.com>
> > ---
> > drivers/firmware/arm_scmi/powercap.c | 120 +++++++++++++++++++++++++++
> > include/linux/scmi_protocol.h | 8 ++
> > 2 files changed, 128 insertions(+)
> >
> > diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
> > index 86262eb0f34a..b5879f204b5e 100644
> > --- a/drivers/firmware/arm_scmi/powercap.c
> > +++ b/drivers/firmware/arm_scmi/powercap.c
> > @@ -401,6 +401,34 @@ scmi_powercap_domain_attrs_process(const struct scmi_protocol_handle *ph,
> > dom_info->notify_powercap_measurement_change =
> > SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
> >
> > + if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
> > + struct scmi_msg_resp_powercap_domain_attributes_v3 *resp_v3 = r;
> > +
> > + flags = le32_to_cpu(resp_v3->attributes);
> > + if (pinfo->notify_measurements_cmd)
> > + dom_info->notify_powercap_measurement_change =
> > + SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
> > +
> > + dom_info->mai_config = SUPPORTS_POWERCAP_MAI_CONFIGURATION(flags);
> > + dom_info->min_mai = le32_to_cpu(resp_v3->min_mai);
> > + dom_info->max_mai = le32_to_cpu(resp_v3->max_mai);
> > + dom_info->mai_step = le32_to_cpu(resp_v3->mai_step);
> > +
> > + if (dom_info->mai_config) {
> > + ret = scmi_powercap_validate(dom_info->min_mai,
> > + dom_info->max_mai,
> > + dom_info->mai_step,
> > + dom_info->mai_config);
> > +
> > + if (ret) {
> > + dev_warn(ph->dev, "Platform reported problem MAI config for domain %d - %s\n",
>
> "....reported invalid MAI config for domain..."
>
Noted, will change.
> > + dom_info->id, dom_info->name);
> > +
> > + return ret;
> > + }
> > + }
> > + }
> > +
> > dom_info->extended_names = SUPPORTS_EXTENDED_NAMES(flags);
> >
> > dom_info->async_powercap_cap_set =
> > @@ -1082,6 +1110,96 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
> > return 0;
> > }
> >
> > +static int scmi_powercap_xfer_mai_get(const struct scmi_protocol_handle *ph, u32 domain_id,
> > + u32 *mai)
>
> ..bad alignment and till now we try still to stick tpo 80cols in the SCMI
> stack if it does NOT really hamper readability...
>
I will address this, but I'm quite certain it wasn't flagged by checkpatch.
> > +{
> > + int ret;
> > + struct scmi_xfer *t;
> > +
> > + ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_GET, sizeof(u32),
> > + sizeof(u32), &t);
>
> ...terrible alignment...and you know why :D
>
Will address.
> > +
> > + if (ret)
> > + return ret;
> > +
> > + put_unaligned_le32(domain_id, t->tx.buf);
> > +
> > + ret = ph->xops->do_xfer(ph, t);
> > + if (!ret)
> > + *mai = get_unaligned_le32(t->rx.buf);
> > +
> > + ph->xops->xfer_put(ph, t);
> > + return ret;
> > +}
> > +
> > +static int scmi_powercap_xfer_mai_set(const struct scmi_protocol_handle *ph, u32 domain_id, u32 mai)
>
> ..same...try to stick to 80 cols when not impossibly ugly..
>
Will do.
> > +{
> > + int ret;
> > + struct scmi_xfer *t;
> > + struct scmi_msg_powercap_cap_or_pai_set *msg;
> > +
> > + ret = ph->xops->xfer_get_init(ph, POWERCAP_MAI_SET, sizeof(*msg), 0, &t);
>
> same
>
I'm not sure why I split that over two lines, will change.
> > + if (ret)
> > + return ret;
> > +
> > + msg = t->tx.buf;
> > + msg->domain_id = cpu_to_le32(domain_id);
> > + msg->flags = cpu_to_le32(0);
> > + msg->value = cpu_to_le32(mai);
> > +
> > + ret = ph->xops->do_xfer(ph, t);
> > +
> > + ph->xops->xfer_put(ph, t);
> > + return ret;
> > +}
> > +
> > +static int scmi_powercap_measurements_interval_get(const struct scmi_protocol_handle *ph,
> > + u32 domain_id, u32 *val)
> > +{
>
> ditto
>
Will do.
> > + const struct scmi_powercap_info *pc;
> > + struct scmi_fc_info *fci;
> > +
> > + if (!val)
> > + return -EINVAL;
> > +
> > + pc = scmi_powercap_dom_info_get(ph, domain_id);
> > + if (!pc)
> > + return -EINVAL;
> > +
> > + fci = pc->cpli[CPL0].fc_info;
> > + if (fci && fci[POWERCAP_FC_MAI].get_addr) {
> > + *val = ioread32(fci[POWERCAP_FC_MAI].get_addr);
> > + trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_GET, domain_id, 0, *val, 0);
> ditto
Noted
> > + return 0;
> > + }
> > +
> > + return scmi_powercap_xfer_mai_get(ph, domain_id, val);
> > +}
> > +
> > +static int scmi_powercap_measurements_interval_set(const struct scmi_protocol_handle *ph,
> > + u32 domain_id, u32 val)
> > +{
>
> ditto
>
Noted.
> > + const struct scmi_powercap_info *pc;
> > + struct scmi_fc_info *fci;
> > +
> > + pc = scmi_powercap_dom_info_get(ph, domain_id);
> > + if (!pc)
> > + return -EINVAL;
> > +
> > + if (!pc->mai_config || !val || val < pc->min_mai || val > pc->max_mai)
> > + return -EINVAL;
> > +
> > + fci = pc->cpli[CPL0].fc_info;
> > + if (fci && fci[POWERCAP_FC_MAI].set_addr) {
> > + iowrite32(val, fci[POWERCAP_FC_MAI].set_addr);
> > + ph->hops->fastchannel_db_ring(fci[POWERCAP_FC_MAI].set_db);
> > + trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MAI_SET, domain_id, 0, val, 0);
> > + return 0;
> > + }
> > +
> > + return scmi_powercap_xfer_mai_set(ph, domain_id, val);
> > +}
> > +
> > static const struct scmi_powercap_proto_ops powercap_proto_ops = {
> > .num_domains_get = scmi_powercap_num_domains_get,
> > .info_get = scmi_powercap_dom_info_get,
> > @@ -1094,6 +1212,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
> > .measurements_get = scmi_powercap_measurements_get,
> > .measurements_threshold_set = scmi_powercap_measurements_threshold_set,
> > .measurements_threshold_get = scmi_powercap_measurements_threshold_get,
> > + .measurements_interval_get = scmi_powercap_measurements_interval_get,
> > + .measurements_interval_set = scmi_powercap_measurements_interval_set,
> > };
> >
> > static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
> > diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> > index d0f6c0102559..73d66281dcc3 100644
> > --- a/include/linux/scmi_protocol.h
> > +++ b/include/linux/scmi_protocol.h
> > @@ -675,6 +675,10 @@ struct scmi_powercap_info {
> > bool powercap_scale_uw;
> > bool extended_names;
> > bool fastchannels;
> > + bool mai_config;
> > + u32 min_mai;
> > + u32 max_mai;
> > + u32 mai_step;
>
> No docs for new fields ?
>
Will add them.
> > char name[SCMI_MAX_STR_SIZE];
> > unsigned int sustainable_power;
> > unsigned int accuracy;
> > @@ -758,6 +762,10 @@ struct scmi_powercap_proto_ops {
> > int (*measurements_threshold_get)(const struct scmi_protocol_handle *ph,
> > u32 domain_id, u32 *power_thresh_low,
> > u32 *power_thresh_high);
> > + int (*measurements_interval_get)(const struct scmi_protocol_handle *ph,
> > + u32 domain_id, u32 *val);
> > + int (*measurements_interval_set)(const struct scmi_protocol_handle *ph,
> > + u32 domain_id, u32 val);
>
> No docs for new fields ?
>
Will add them.
And will pay more attention to col length from now on.
Regards,
Phil.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v5 10/12] powercap: arm_scmi: Create synthetic parent node for multi-instance
2026-04-28 9:09 ` [PATCH v5 10/12] powercap: arm_scmi: Create synthetic parent node for multi-instance Philip Radford
@ 2026-05-05 22:03 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2026-05-05 22:03 UTC (permalink / raw)
To: Philip Radford
Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty, cristian.marussi
On Tue, Apr 28, 2026 at 10:09:19AM +0100, Philip Radford wrote:
> 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.
Hi,
I know the problem you are trying to solve is real and advised for this
solution BUT saying that the domains are exposed in a flat list upfront
is not correct and misleading I think...the problem revolves around the
fact that...
SCMI powercap domains are represented with a hierarchical structure
(if any such structure exists platform side) which can be comprised
of multiple trees (a forest of powercap zones trees) each with its own
top root powercap zone.
On a system designed to expose multipe SCMI instances each of such
instance could enumerate a pretty much similar/simmetrical hierarchy of
powercap zomes, all sharing most probably the same IDs for children and
parents...all of these simmetrical hierarchies can coexist as long as
they are confined into their own instance 'space'.
Given that the Powercap framework does NOT have the concept of instances
collating all of these zones into the same Linux Powercap top node leads
to an artificially flattened tree of powercap where all the boudnaries
between instances are squashes into one single structure....possibly
combining multiple unrelated SCMI zones under a single unrelated Linux
powercap zone (by virtue of simmetrical parent-child relations)
So the need for a (functionally limited) unifying synthetic top root for
each instance...
I am sure you can summarize all of these better :P):
>
> 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;
> +}
I would explain here better WHY you cannot really implement all the
fuctionalities on these toproot syntehtc zones...and WHY it is NOT an
issue but STILL it is enough to solve our problem with the multiple
instances configurations...
> +
> +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);
I have to admit I'd have to look back at how I implemneted this powercap zone
walking at registration tome :D .. but HOW can you get rid of spz->height here ?
...also because now spz->height is NOT set anywhere (presumable zeroed
at init) and also why you have NOT removed also scmi_powercap_get_zone_height
since nobody is calling it ?
...also because I am pretty sure that all the management of the height of
the nodes in the binary trees where crucial when tearing down the hierarchy of
powercap zones ... (see explanation preceding scmi_zones_register())
...mmmm.... have you tried to unload the arm_scmi_powercap driver with these
new changes for the syntethic powercap zones ?
I just did :P
---8<----
root@deb-guest:~# modprobe -r arm_scmi_powercap
[ 28.939470] Unable to handle kernel paging request at virtual address fffffffffffffc90
[ 28.942910] Mem abort info:
[ 28.947866] ESR = 0x0000000096000006
[ 28.953034] EC = 0x25: DABT (current EL), IL = 32 bits
[ 28.957189] SET = 0, FnV = 0
[ 28.957437] EA = 0, S1PTW = 0
[ 28.958254] FSC = 0x06: level 2 translation fault
[ 28.959025] Data abort info:
[ 28.959511] ISV = 0, ISS = 0x00000006, ISS2 = 0x00000000
[ 28.960049] CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[ 28.961191] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[ 28.962227] swapper pgtable: 4k pages, 48-bit VAs, pgdp=000000010261b000
[ 28.963105] [fffffffffffffc90] pgd=0000000000000000, p4d=00000001030ff403, pud=0000000103100403, pmd=0000000000000000
[ 28.964189] Internal error: Oops: 0000000096000006 [#1] SMP
[ 28.964994] Modules linked in: arm_scmi_powercap(-) cpufreq_powersave cpufreq_conservative
[ 28.966300] CPU: 2 UID: 0 PID: 227 Comm: modprobe Not tainted 7.1.0-rc1-00012-g383eb3db9548 #2 PREEMPT
[ 28.967170] Hardware name: linux,dummy-virt (DT)
[ 28.967925] pstate: 61400005 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
[ 28.969245] pc : device_del+0x3c/0x3b0
[ 28.969963] lr : device_unregister+0x28/0x90
[ 28.970342] sp : ffff800083b73b30
[ 28.970753] x29: ffff800083b73b50 x28: ffff000005d86000 x27: 0000000000000000
[ 28.971448] x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000000
[ 28.972159] x23: fffffffffffffff0 x22: ffff80007b1e30c0 x21: ffff000006f3c080
[ 28.972701] x20: fffffffffffffc30 x19: fffffffffffffc50 x18: 0000000000000000
[ 28.973237] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000
[ 28.973899] x14: 0000000000000000 x13: 0000000000000000 x12: ffff000007338880
[ 28.974795] x11: ffff0000056bbc10 x10: ffff000009ca0c00 x9 : ffff800080c44480
[ 28.975483] x8 : 0101010101010101 x7 : 7f7f7f7f7f7f7f7f x6 : fefefeff6364626d
[ 28.976161] x5 : 8080808000000000 x4 : 0000000000000020 x3 : ffff80008243c534
[ 28.976842] x2 : ffff000005d86000 x1 : 0000000000000000 x0 : fffffffffffffce0
[ 28.977511] Call trace:
[ 28.977738] device_del+0x3c/0x3b0 (P)
[ 28.978047] device_unregister+0x28/0x90
[ 28.978371] powercap_unregister_zone+0x54/0x78
[ 28.978781] scmi_powercap_unregister_all_zones+0x84/0xc8 [arm_scmi_powercap]
[ 28.979344] scmi_powercap_remove+0x1c/0x38 [arm_scmi_powercap]
[ 28.979821] scmi_dev_remove+0x28/0x40
[ 28.980136] device_remove+0x54/0x90
[ 28.980428] device_release_driver_internal+0x1cc/0x230
[ 28.980845] driver_detach+0x54/0xc0
[ 28.981136] bus_remove_driver+0x74/0x100
[ 28.981454] driver_unregister+0x38/0x68
[ 28.981774] scmi_driver_unregister+0x28/0x1a0
[ 28.982147] scmi_powercap_exit+0x18/0xe20 [arm_scmi_powercap]
[ 28.982560] __arm64_sys_delete_module+0x1b0/0x2e0
[ 28.982870] invoke_syscall.constprop.0+0x48/0x120
[ 28.983219] el0_svc_common.constprop.0+0x40/0xe8
[ 28.983628] do_el0_svc+0x24/0x38
[ 28.983916] el0_svc+0x38/0x138
[ 28.984201] el0t_64_sync_handler+0xa0/0xe8
[ 28.984557] el0t_64_sync+0x198/0x1a0
[ 28.984901] Code: f9429c01 f9000fe1 d2800001 91024260 (f9402276)
[ 28.985443] ---[ end trace 0000000000000000 ]---
Segmentation fault
--->8----
This does NOT happen without your syntethic node changes since the driver on
unload took care to remove/unregister powercap zones starting from the
leaves so as not to remove parents that were still referenced ....
.... you should always test also as a loadable module and manually load/unload
then testing...
I will review this again in V6...plesse double check to preserve the
original zone-walking algo...
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support
2026-05-05 21:44 ` Philip Radford
@ 2026-05-05 22:09 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2026-05-05 22:09 UTC (permalink / raw)
To: Philip Radford
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
linux-pm, sudeep.holla, james.quinlan, f.fainelli,
vincent.guittot, etienne.carriere, peng.fan, michal.simek,
quic_sibis, dan.carpenter, d-gole, souvik.chakravarty
On Tue, May 05, 2026 at 10:44:05PM +0100, Philip Radford wrote:
> On Tue, May 05, 2026 at 09:36:26PM +0100, Cristian Marussi wrote:
> > On Tue, Apr 28, 2026 at 10:09:18AM +0100, Philip Radford wrote:
> > > Add support for Power Measurement Averaging Interval (MAI)
> >
> > Hi,
> >
>
> Hi,
> Thanks for the review.
>
Helo,
> > > get and set operations to the SCMI powercap protocol driver.
> > > Extends scmi_powercap_info to store MAI configuration and
> > > implement MAI get/set via xfer and optional fast-channel
> > > support.
[snip]
> > > +static int scmi_powercap_xfer_mai_get(const struct scmi_protocol_handle *ph, u32 domain_id,
> > > + u32 *mai)
> >
> > ..bad alignment and till now we try still to stick tpo 80cols in the SCMI
> > stack if it does NOT really hamper readability...
> >
>
> I will address this, but I'm quite certain it wasn't flagged by checkpatch.
Oh yes..this is currently a convention we kept in the SCMI subsystem (the
original 80 cols) BUT kernel wise this line length req was relaxed to
100cols a few years ago..so checkoatch will never flag it (not sure if
it is configurable in such regards)...
... so you are excused :P
....anyway maybe Sudeep is fine to relax this...I dont have strong opinions
BUT since all the stack is 80cols at this point...
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v5 11/12] powercap: arm_scmi: Add get_power_uw to synthetic node
2026-04-28 9:09 ` [PATCH v5 11/12] powercap: arm_scmi: Add get_power_uw to synthetic node Philip Radford
@ 2026-05-05 22:13 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2026-05-05 22:13 UTC (permalink / raw)
To: Philip Radford
Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty, cristian.marussi
On Tue, Apr 28, 2026 at 10:09:20AM +0100, Philip Radford wrote:
> Exposes the current power usage from the immediate children of
> the synthetic (root) powercap node. Iterates over pr->spzones and
> sums per-zone power.
>
> Signed-off-by: Philip Radford <philip.radford@arm.com>
> ---
> drivers/powercap/arm_scmi_powercap.c | 29 ++++++++++++++++++++++++++--
> 1 file changed, 27 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
> index d74869af1633..81b5214acda4 100644
> --- a/drivers/powercap/arm_scmi_powercap.c
> +++ b/drivers/powercap/arm_scmi_powercap.c
> @@ -270,9 +270,34 @@ static int instance_root_release(struct powercap_zone *pz)
> return 0;
> }
>
> -static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *v)
> +static int instance_root_get_power_uw(struct powercap_zone *pz, u64 *power_uw)
> {
> - *v = 0;
> + struct scmi_powercap_zone *root = to_scmi_powercap_zone(pz);
> + struct scmi_powercap_zone *child;
> + struct scmi_powercap_root *pr;
> + u64 acc = 0;
> + u64 p;
These u64 can live on a single line...
> + int ret;
> +
> + if (!pz || !power_uw)
> + return -EINVAL;
> +
> + pr = container_of(root, struct scmi_powercap_root, instance_root);
usually we define macros to wrap this like to_scmi_powercap_zone()...
...even if used only once...this could be a new to_scmi_powercap_root()
> + if (!pr)
> + return -ENODEV;
> +
> + list_for_each_entry(child, &pr->registered_zones[0], node) {
> + if (child == &pr->instance_root)
> + continue;
You'll need to check this in light of the fixes of previous patch
> +
> + ret = scmi_powercap_get_power_uw(&child->zone, &p);
> + if (!ret)
> + acc += p;
> + else
> + dev_dbg(child->dev, "Failed to read child power: %u\n", ret);
> + }
> +
> + *power_uw = acc;
> return 0;
> }
>
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v5 12/12] powercap: arm_scmi: Synthetic zone enable/disable
2026-04-28 9:09 ` [PATCH v5 12/12] powercap: arm_scmi: Synthetic zone enable/disable Philip Radford
@ 2026-05-05 22:28 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2026-05-05 22:28 UTC (permalink / raw)
To: Philip Radford
Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty, cristian.marussi
On Tue, Apr 28, 2026 at 10:09:21AM +0100, Philip Radford wrote:
> Add functionality to disable and enable the synthetic zone which
> also affects the immediate children of the synthetic zone by applying
> the same command to them.
>
> Signed-off-by: Philip Radford <philip.radford@arm.com>
> ---
> drivers/powercap/arm_scmi_powercap.c | 81 ++++++++++++++++++++++++++++
> 1 file changed, 81 insertions(+)
>
> diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
> index 81b5214acda4..1ed2949b06cb 100644
> --- a/drivers/powercap/arm_scmi_powercap.c
> +++ b/drivers/powercap/arm_scmi_powercap.c
> @@ -270,6 +270,85 @@ static int instance_root_release(struct powercap_zone *pz)
> return 0;
> }
>
> +static int instance_root_set_enable_state(struct powercap_zone *pz, bool enable)
> +{
> + struct scmi_powercap_zone *root;
> + struct scmi_powercap_root *pr;
> + struct scmi_powercap_zone *child;
...child and root on the same line of declarations...
> + int ret, first_err = 0;
> +
> + if (!pz)
> + return -EINVAL;
> +
> + root = to_scmi_powercap_zone(pz);
> + pr = container_of(root, struct scmi_powercap_root, instance_root);
...another user of you new macro !
> +
> + list_for_each_entry(child, &pr->registered_zones[0], node) {
> + if (child == &pr->instance_root)
> + continue;
> +
> + if (child->info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID)
> + continue;
> +
> + if (!child->info->cpli[0].cap_config)
> + continue;
> +
> + ret = powercap_ops->cap_enable_set(child->ph, child->info->id, enable);
> +
> + if (ret && !first_err) {
...mmm what is the logic here ? why not bailing out on any error ?
> + first_err = ret;
> + dev_err(child->dev, "failed to %s zone %s: %d\n",
> + enable ? "enable" : "disable",
> + child->info->name, ret);
> + }
> + }
> +
> + return first_err;
...especially if you anyway fails globally on any error....
..I am not completely sure bit given that youare operating on a
synthetic zone that you enable as a whole, while acting on its children
in the backstage...I would say that if any enable/disable fails on a
chidlren you should revert the enable status of the children thate were
succesffull and report the error...I mean the state of top synthatic
zones AND the states of the children MUST remain consistent...
...it CANNOT be that some chidlren fails, some succeeds and you report
an error..it must be all or nothing...
...example..top syntethic zone is OFF if all children were successfully
disabled...on a failure with one of the children you shoudl revert the
already successfully set children and report the global error...
> +}
> +
> +static int instance_root_set_enable(struct powercap_zone *pz, bool mode)
> +{
> + return instance_root_set_enable_state(pz, mode);
> +}
> +
> +static int instance_root_get_enable(struct powercap_zone *pz, bool *mode)
> +{
> + struct scmi_powercap_zone *root;
> + struct scmi_powercap_root *pr;
> + struct scmi_powercap_zone *child;
> + bool enabled;
> + int ret;
> +
> + if (!pz || !mode)
> + return -EINVAL;
> +
> + root = to_scmi_powercap_zone(pz);
> + pr = container_of(root, struct scmi_powercap_root, instance_root);
> +
> + *mode = true;
> +
> + list_for_each_entry(child, &pr->registered_zones[0], node) {
mmm...what is the point here of scanning the children to GET the
state...you should report the top syntethic zone state right ?
You could have disable children directly...that wont be reflected in the
Linux powercap hiearcrhy right ?
I mean should you NOT simply return the stae of the top syntethic zone
which is should have saved in the previous state_set operation above ?
I think that anyway if you disable a zone...any zone...ONLY that zone is
marked as disable in Lnux powercap ... am I right ?
Then probably our SCMI fw will do much more on all the children..
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-05-05 22:28 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-28 9:09 [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions Philip Radford
2026-04-28 9:09 ` [PATCH v5 01/12] firmware: arm_scmi: Add an optional custom parameter to fastchannel helpers Philip Radford
2026-04-28 9:09 ` [PATCH v5 02/12] firmware: arm_scmi: Refactor powercap domain layout Philip Radford
2026-04-28 9:09 ` [PATCH v5 03/12] firmware: arm_scmi: Add SCMIv4.0 Powercap basic support Philip Radford
2026-04-28 9:09 ` [PATCH v5 04/12] firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support Philip Radford
2026-04-28 9:09 ` [PATCH v5 05/12] firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support Philip Radford
2026-04-28 9:09 ` [PATCH v5 06/12] firmware: arm_scmi: Extend powercap report to include MAI Philip Radford
2026-05-05 20:13 ` Cristian Marussi
2026-05-05 21:21 ` Philip Radford
2026-04-28 9:09 ` [PATCH v5 07/12] include: trace: Add new parameter to trace_scmi_fc_call Philip Radford
2026-04-28 9:09 ` [PATCH v5 08/12] powercap: arm_scmi: Enable multiple constraints support Philip Radford
2026-04-28 9:09 ` [PATCH v5 09/12] firmware: arm_scmi: add Powercap MAI get/set support Philip Radford
2026-05-05 20:36 ` Cristian Marussi
2026-05-05 21:44 ` Philip Radford
2026-05-05 22:09 ` Cristian Marussi
2026-04-28 9:09 ` [PATCH v5 10/12] powercap: arm_scmi: Create synthetic parent node for multi-instance Philip Radford
2026-05-05 22:03 ` Cristian Marussi
2026-04-28 9:09 ` [PATCH v5 11/12] powercap: arm_scmi: Add get_power_uw to synthetic node Philip Radford
2026-05-05 22:13 ` Cristian Marussi
2026-04-28 9:09 ` [PATCH v5 12/12] powercap: arm_scmi: Synthetic zone enable/disable Philip Radford
2026-05-05 22:28 ` Cristian Marussi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox