Merge tag 'scmi-updates-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into soc/drivers

Arm SCMI updates for v6.5

Couple of main additions :-

1. Support for multiple SMC/HVC transports for SCMI:

   Some platforms need to support multiple SCMI instances within
   a platform(more commonly in a VM). The same SMC/HVC FID is used with
   all the instances. The platform or the hypervisor needs a way to
   distinguish among SMC/HVC calls made from different instances.

   This change adds support for passing shmem channel address as the
   parameters in the SMC/HVC call. The address is split into 4KB-page
   and offset for simiplicity.

2. Addition od SCMI v3.2 explicit powercap enable/disable support:

   SCMI v3.2 specification introduces support to disable powercapping
   as a whole on the desired zones.

   This change adds the needed support to the core SCMI powercap protocol,
   exposing enable/disable protocol operations and then wiring up the new
   operartions in the related powercap framework helpers.

* tag 'scmi-updates-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux:
  powercap: arm_scmi: Add support for disabling powercaps on a zone
  firmware: arm_scmi: Add Powercap protocol enable support
  firmware: arm_scmi: Refactor the internal powercap get/set helpers
  firmware: arm_scmi: Augment SMC/HVC to allow optional parameters
  dt-bindings: firmware: arm,scmi: support for parameter in smc/hvc call

Link: https://lore.kernel.org/r/20230612121017.4108104-1-sudeep.holla@arm.com
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann
2023-06-22 17:33:11 +02:00
6 changed files with 219 additions and 27 deletions
@@ -34,6 +34,10 @@ properties:
- description: SCMI compliant firmware with ARM SMC/HVC transport
items:
- const: arm,scmi-smc
- description: SCMI compliant firmware with ARM SMC/HVC transport
with shmem address(4KB-page, offset) as parameters
items:
- const: arm,scmi-smc-param
- description: SCMI compliant firmware with SCMI Virtio transport.
The virtio transport only supports a single device.
items:
@@ -299,7 +303,9 @@ else:
properties:
compatible:
contains:
const: arm,scmi-smc
enum:
- arm,scmi-smc
- arm,scmi-smc-param
then:
required:
- arm,smc-id
+1
View File
@@ -2914,6 +2914,7 @@ static const struct of_device_id scmi_of_match[] = {
#endif
#ifdef CONFIG_ARM_SCMI_TRANSPORT_SMC
{ .compatible = "arm,scmi-smc", .data = &scmi_smc_desc},
{ .compatible = "arm,scmi-smc-param", .data = &scmi_smc_desc},
#endif
#ifdef CONFIG_ARM_SCMI_TRANSPORT_VIRTIO
{ .compatible = "arm,scmi-virtio", .data = &scmi_virtio_desc},
+148 -25
View File
@@ -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) \
@@ -313,24 +315,33 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
return ret;
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_cap)
static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *dom,
u32 *power_cap)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
if (!power_cap || domain_id >= pi->num_domains)
return -EINVAL;
dom = pi->powercaps + domain_id;
if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) {
*power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
domain_id, *power_cap, 0);
dom->id, *power_cap, 0);
return 0;
}
return scmi_powercap_xfer_cap_get(ph, domain_id, power_cap);
return scmi_powercap_xfer_cap_get(ph, dom->id, power_cap);
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_cap)
{
const struct scmi_powercap_info *dom;
if (!power_cap)
return -EINVAL;
dom = scmi_powercap_dom_info_get(ph, domain_id);
if (!dom)
return -EINVAL;
return __scmi_powercap_cap_get(ph, dom, power_cap);
}
static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
@@ -375,17 +386,20 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
return ret;
}
static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 power_cap,
bool ignore_dresp)
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)
{
int ret = -EINVAL;
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
if (!pc || !pc->powercap_cap_config || !power_cap ||
power_cap < pc->min_power_cap ||
power_cap > pc->max_power_cap)
return -EINVAL;
if (!pc || !pc->powercap_cap_config)
return ret;
if (power_cap &&
(power_cap < pc->min_power_cap || power_cap > pc->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];
@@ -394,10 +408,41 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
ph->hops->fastchannel_db_ring(fci->set_db);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_SET,
domain_id, power_cap, 0);
ret = 0;
} else {
ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap,
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;
}
static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 power_cap,
bool ignore_dresp)
{
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_xfer_cap_set(ph, pc, power_cap, ignore_dresp);
return __scmi_powercap_cap_set(ph, pi, domain_id,
power_cap, ignore_dresp);
}
static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
@@ -564,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,
@@ -829,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
@@ -843,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);
/* 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;
return ph->set_priv(ph, pinfo);
}
+29 -1
View File
@@ -20,6 +20,23 @@
#include "common.h"
/*
* The shmem address is split into 4K page and offset.
* This is to make sure the parameters fit in 32bit arguments of the
* smc/hvc call to keep it uniform across smc32/smc64 conventions.
* This however limits the shmem address to 44 bit.
*
* These optional parameters can be used to distinguish among multiple
* scmi instances that are using the same smc-id.
* The page parameter is passed in r1/x1/w1 register and the offset parameter
* is passed in r2/x2/w2 register.
*/
#define SHMEM_SIZE (SZ_4K)
#define SHMEM_SHIFT 12
#define SHMEM_PAGE(x) (_UL((x) >> SHMEM_SHIFT))
#define SHMEM_OFFSET(x) ((x) & (SHMEM_SIZE - 1))
/**
* struct scmi_smc - Structure representing a SCMI smc transport
*
@@ -30,6 +47,8 @@
* @inflight: Atomic flag to protect access to Tx/Rx shared memory area.
* Used when operating in atomic mode.
* @func_id: smc/hvc call function id
* @param_page: 4K page number of the shmem channel
* @param_offset: Offset within the 4K page of the shmem channel
*/
struct scmi_smc {
@@ -40,6 +59,8 @@ struct scmi_smc {
#define INFLIGHT_NONE MSG_TOKEN_MAX
atomic_t inflight;
u32 func_id;
u32 param_page;
u32 param_offset;
};
static irqreturn_t smc_msg_done_isr(int irq, void *data)
@@ -137,6 +158,10 @@ static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
if (ret < 0)
return ret;
if (of_device_is_compatible(dev->of_node, "arm,scmi-smc-param")) {
scmi_info->param_page = SHMEM_PAGE(res.start);
scmi_info->param_offset = SHMEM_OFFSET(res.start);
}
/*
* If there is an interrupt named "a2p", then the service and
* completion of a message is signaled by an interrupt rather than by
@@ -179,6 +204,8 @@ static int smc_send_message(struct scmi_chan_info *cinfo,
{
struct scmi_smc *scmi_info = cinfo->transport_info;
struct arm_smccc_res res;
unsigned long page = scmi_info->param_page;
unsigned long offset = scmi_info->param_offset;
/*
* Channel will be released only once response has been
@@ -188,7 +215,8 @@ static int smc_send_message(struct scmi_chan_info *cinfo,
shmem_tx_prepare(scmi_info->shmem, xfer, cinfo);
arm_smccc_1_1_invoke(scmi_info->func_id, 0, 0, 0, 0, 0, 0, 0, &res);
arm_smccc_1_1_invoke(scmi_info->func_id, page, offset, 0, 0, 0, 0, 0,
&res);
/* Only SMCCC_RET_NOT_SUPPORTED is valid error code */
if (res.a0) {
+16
View File
@@ -70,10 +70,26 @@ static int scmi_powercap_get_power_uw(struct powercap_zone *pz,
return 0;
}
static int scmi_powercap_zone_enable_set(struct powercap_zone *pz, bool mode)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
return powercap_ops->cap_enable_set(spz->ph, spz->info->id, mode);
}
static int scmi_powercap_zone_enable_get(struct powercap_zone *pz, bool *mode)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
return powercap_ops->cap_enable_get(spz->ph, spz->info->id, mode);
}
static const struct powercap_zone_ops zone_ops = {
.get_max_power_range_uw = scmi_powercap_get_max_power_range_uw,
.get_power_uw = scmi_powercap_get_power_uw,
.release = scmi_powercap_zone_release,
.set_enable = scmi_powercap_zone_enable_set,
.get_enable = scmi_powercap_zone_enable_get,
};
static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
+18
View File
@@ -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
@@ -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,