firmware: arm_scmi: Add Powercap protocol enable support

SCMI powercap protocol v3.2 supports disabling the powercap on a zone
by zone basis by providing a zero valued powercap.

Expose new operations to enable/disable powercapping on a per-zone base.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Link: https://lore.kernel.org/r/20230531152039.2363181-3-cristian.marussi@arm.com
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
This commit is contained in:
Cristian Marussi
2023-05-31 16:20:38 +01:00
committed by Sudeep Holla
parent 4e1a53b403
commit 758cd5fc13
2 changed files with 122 additions and 6 deletions

View File

@@ -108,6 +108,8 @@ struct scmi_powercap_meas_changed_notify_payld {
}; };
struct scmi_powercap_state { struct scmi_powercap_state {
bool enabled;
u32 last_pcap;
bool meas_notif_enabled; bool meas_notif_enabled;
u64 thresholds; u64 thresholds;
#define THRESH_LOW(p, id) \ #define THRESH_LOW(p, id) \
@@ -412,6 +414,10 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
ignore_dresp); ignore_dresp);
} }
/* Save the last explicitly set non-zero powercap value */
if (PROTOCOL_REV_MAJOR(pi->version) >= 0x2 && !ret && power_cap)
pi->states[domain_id].last_pcap = power_cap;
return ret; return ret;
} }
@@ -421,6 +427,20 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
{ {
struct powercap_info *pi = ph->get_priv(ph); struct powercap_info *pi = ph->get_priv(ph);
/*
* Disallow zero as a possible explicitly requested powercap:
* there are enable/disable operations for this.
*/
if (!power_cap)
return -EINVAL;
/* Just log the last set request if acting on a disabled domain */
if (PROTOCOL_REV_MAJOR(pi->version) >= 0x2 &&
!pi->states[domain_id].enabled) {
pi->states[domain_id].last_pcap = power_cap;
return 0;
}
return __scmi_powercap_cap_set(ph, pi, domain_id, return __scmi_powercap_cap_set(ph, pi, domain_id,
power_cap, ignore_dresp); power_cap, ignore_dresp);
} }
@@ -589,11 +609,78 @@ scmi_powercap_measurements_threshold_set(const struct scmi_protocol_handle *ph,
return ret; return ret;
} }
static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
u32 domain_id, bool enable)
{
int ret;
u32 power_cap;
struct powercap_info *pi = ph->get_priv(ph);
if (PROTOCOL_REV_MAJOR(pi->version) < 0x2)
return -EINVAL;
if (enable == pi->states[domain_id].enabled)
return 0;
if (enable) {
/* Cannot enable with a zero powercap. */
if (!pi->states[domain_id].last_pcap)
return -EINVAL;
ret = __scmi_powercap_cap_set(ph, pi, domain_id,
pi->states[domain_id].last_pcap,
true);
} else {
ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true);
}
if (ret)
return ret;
/*
* Update our internal state to reflect final platform state: the SCMI
* 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);
if (!ret)
pi->states[domain_id].enabled = !!power_cap;
return ret;
}
static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
u32 domain_id, bool *enable)
{
int ret;
u32 power_cap;
struct powercap_info *pi = ph->get_priv(ph);
*enable = true;
if (PROTOCOL_REV_MAJOR(pi->version) < 0x2)
return 0;
/*
* 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);
if (!ret)
*enable = !!power_cap;
/* Update internal state with current real platform state */
pi->states[domain_id].enabled = *enable;
return 0;
}
static const struct scmi_powercap_proto_ops powercap_proto_ops = { static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.num_domains_get = scmi_powercap_num_domains_get, .num_domains_get = scmi_powercap_num_domains_get,
.info_get = scmi_powercap_dom_info_get, .info_get = scmi_powercap_dom_info_get,
.cap_get = scmi_powercap_cap_get, .cap_get = scmi_powercap_cap_get,
.cap_set = scmi_powercap_cap_set, .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_get = scmi_powercap_pai_get,
.pai_set = scmi_powercap_pai_set, .pai_set = scmi_powercap_pai_set,
.measurements_get = scmi_powercap_measurements_get, .measurements_get = scmi_powercap_measurements_get,
@@ -854,6 +941,11 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (!pinfo->powercaps) if (!pinfo->powercaps)
return -ENOMEM; return -ENOMEM;
pinfo->states = devm_kcalloc(ph->dev, pinfo->num_domains,
sizeof(*pinfo->states), GFP_KERNEL);
if (!pinfo->states)
return -ENOMEM;
/* /*
* Note that any failure in retrieving any domain attribute leads to * Note that any failure in retrieving any domain attribute leads to
* the whole Powercap protocol initialization failure: this way the * the whole Powercap protocol initialization failure: this way the
@@ -868,15 +960,21 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (pinfo->powercaps[domain].fastchannels) if (pinfo->powercaps[domain].fastchannels)
scmi_powercap_domain_init_fc(ph, domain, scmi_powercap_domain_init_fc(ph, domain,
&pinfo->powercaps[domain].fc_info); &pinfo->powercaps[domain].fc_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(version) >= 0x2) {
ret = __scmi_powercap_cap_get(ph,
&pinfo->powercaps[domain],
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
pinfo->states[domain].enabled =
!!pinfo->states[domain].last_pcap;
}
} }
pinfo->states = devm_kcalloc(ph->dev, pinfo->num_domains,
sizeof(*pinfo->states), GFP_KERNEL);
if (!pinfo->states)
return -ENOMEM;
pinfo->version = version; pinfo->version = version;
return ph->set_priv(ph, pinfo); return ph->set_priv(ph, pinfo);
} }

View File

@@ -629,11 +629,25 @@ struct scmi_powercap_info {
* @num_domains_get: get the count of powercap domains provided by SCMI. * @num_domains_get: get the count of powercap domains provided by SCMI.
* @info_get: get the information for the specified domain. * @info_get: get the information for the specified domain.
* @cap_get: get the current CAP value for the specified domain. * @cap_get: get the current CAP value for the specified domain.
* On SCMI platforms supporting powercap zone disabling, this could
* report a zero value for a zone where powercapping is disabled.
* @cap_set: set the CAP value for the specified domain to the provided value; * @cap_set: set the CAP value for the specified domain to the provided value;
* if the domain supports setting the CAP with an asynchronous command * if the domain supports setting the CAP with an asynchronous command
* this request will finally trigger an asynchronous transfer, but, if * this request will finally trigger an asynchronous transfer, but, if
* @ignore_dresp here is set to true, this call will anyway return * @ignore_dresp here is set to true, this call will anyway return
* immediately without waiting for the related delayed response. * immediately without waiting for the related delayed response.
* Note that the powercap requested value must NOT be zero, even if
* the platform supports disabling a powercap by setting its cap to
* zero (since SCMI v3.2): there are dedicated operations that should
* be used for that. (@cap_enable_set/get)
* @cap_enable_set: enable or disable the powercapping on the specified domain,
* if supported by the SCMI platform implementation.
* Note that, by the SCMI specification, the platform can
* silently ignore our disable request and decide to enforce
* anyway some other powercap value requested by another agent
* 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_get: get the current PAI value for the specified domain.
* @pai_set: set the PAI value for the specified domain to the provided value. * @pai_set: set the PAI value for the specified domain to the provided value.
* @measurements_get: retrieve the current average power measurements for the * @measurements_get: retrieve the current average power measurements for the
@@ -662,6 +676,10 @@ struct scmi_powercap_proto_ops {
u32 *power_cap); u32 *power_cap);
int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_id, int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
u32 power_cap, bool ignore_dresp); 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, int (*pai_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
u32 *pai); u32 *pai);
int (*pai_set)(const struct scmi_protocol_handle *ph, u32 domain_id, int (*pai_set)(const struct scmi_protocol_handle *ph, u32 domain_id,