Skip to content

Commit

Permalink
firmware: arm_scmi: Add Powercap protocol enable support
Browse files Browse the repository at this point in the history
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 <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Sudeep Holla <[email protected]>
  • Loading branch information
freefall75 authored and sudeep-holla committed Jun 6, 2023
1 parent 4e1a53b commit 758cd5f
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 6 deletions.
110 changes: 104 additions & 6 deletions drivers/firmware/arm_scmi/powercap.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ struct scmi_powercap_meas_changed_notify_payld {
};

struct scmi_powercap_state {
bool enabled;
u32 last_pcap;
bool meas_notif_enabled;
u64 thresholds;
#define THRESH_LOW(p, id) \
Expand Down Expand Up @@ -412,6 +414,10 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
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;
}

Expand All @@ -421,6 +427,20 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *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,
power_cap, ignore_dresp);
}
Expand Down Expand Up @@ -589,11 +609,78 @@ scmi_powercap_measurements_threshold_set(const struct scmi_protocol_handle *ph,
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 = {
.num_domains_get = scmi_powercap_num_domains_get,
.info_get = scmi_powercap_dom_info_get,
.cap_get = scmi_powercap_cap_get,
.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,
.measurements_get = scmi_powercap_measurements_get,
Expand Down Expand Up @@ -854,6 +941,11 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (!pinfo->powercaps)
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
* the whole Powercap protocol initialization failure: this way the
Expand All @@ -868,15 +960,21 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (pinfo->powercaps[domain].fastchannels)
scmi_powercap_domain_init_fc(ph, domain,
&pinfo->powercaps[domain].fc_info);
}

pinfo->states = devm_kcalloc(ph->dev, pinfo->num_domains,
sizeof(*pinfo->states), GFP_KERNEL);
if (!pinfo->states)
return -ENOMEM;
/* 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->version = version;
pinfo->states[domain].enabled =
!!pinfo->states[domain].last_pcap;
}
}

pinfo->version = version;
return ph->set_priv(ph, pinfo);
}

Expand Down
18 changes: 18 additions & 0 deletions include/linux/scmi_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -629,11 +629,25 @@ struct scmi_powercap_info {
* @num_domains_get: get the count of powercap domains provided by SCMI.
* @info_get: get the information 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;
* if the domain supports setting the CAP with an asynchronous command
* this request will finally trigger an asynchronous transfer, but, if
* @ignore_dresp here is set to true, this call will anyway return
* 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_set: set the PAI value for the specified domain to the provided value.
* @measurements_get: retrieve the current average power measurements for the
Expand Down Expand Up @@ -662,6 +676,10 @@ struct scmi_powercap_proto_ops {
u32 *power_cap);
int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_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,
Expand Down

0 comments on commit 758cd5f

Please sign in to comment.