Unverified Commit 2d0d3a10 authored by Arnd Bergmann's avatar Arnd Bergmann
Browse files

Merge tag 'scmi-updates-6.5' of...

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: default avatarArnd Bergmann <arnd@arndb.de>
parents 618d1293 aaffb4ca
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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);
	}

	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);
}

+29 −1
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
@@ -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,
Loading