Module Name:    src
Committed By:   jmcneill
Date:           Wed Jan  8 22:55:35 UTC 2025

Modified Files:
        src/sys/conf: files
Added Files:
        src/sys/dev/ic: scmi.c scmi.h

Log Message:
scmi: Add Arm SCMI performance protocol support.

>From OpenBSD, adapted for the NetBSD cpufreq sysctl interface.


To generate a diff of this commit:
cvs rdiff -u -r1.1313 -r1.1314 src/sys/conf/files
cvs rdiff -u -r0 -r1.1 src/sys/dev/ic/scmi.c src/sys/dev/ic/scmi.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/conf/files
diff -u src/sys/conf/files:1.1313 src/sys/conf/files:1.1314
--- src/sys/conf/files:1.1313	Sun May 19 22:25:48 2024
+++ src/sys/conf/files	Wed Jan  8 22:55:35 2025
@@ -1,4 +1,4 @@
-#	$NetBSD: files,v 1.1313 2024/05/19 22:25:48 christos Exp $
+#	$NetBSD: files,v 1.1314 2025/01/08 22:55:35 jmcneill Exp $
 #	@(#)files.newconf	7.5 (Berkeley) 5/10/93
 
 version 	20171118
@@ -388,6 +388,7 @@ define 	pckbport	{[slot = -1]}
 define	pckbport_machdep_cnattach
 define	firmload
 define	ipmibus		{ }
+define	scmi
 define	smbios
 
 # speaker devices, attaches to audio or pcppi drivers
@@ -1446,6 +1447,9 @@ file	dev/ic/qemufwcfg.c		qemufwcfg
 device	amdccp
 file	dev/ic/amdccp.c			amdccp
 
+# System Control and Management Interface (SCMI)
+file	dev/ic/scmi.c			scmi
+
 # legitimate pseudo-devices
 #
 defpseudodev vnd:	disk

Added files:

Index: src/sys/dev/ic/scmi.c
diff -u /dev/null src/sys/dev/ic/scmi.c:1.1
--- /dev/null	Wed Jan  8 22:55:35 2025
+++ src/sys/dev/ic/scmi.c	Wed Jan  8 22:55:35 2025
@@ -0,0 +1,727 @@
+/* $NetBSD: scmi.c,v 1.1 2025/01/08 22:55:35 jmcneill Exp $ */
+/*	$OpenBSD: scmi.c,v 1.2 2024/11/25 22:12:18 tobhe Exp $	*/
+
+/*
+ * Copyright (c) 2023 Mark Kettenis <kette...@openbsd.org>
+ * Copyright (c) 2024 Tobias Heider <to...@openbsd.org>
+ * Copyright (c) 2025 Jared McNeill <jmcne...@invisible.ca>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/device.h>
+#include <sys/systm.h>
+#include <sys/kmem.h>
+#include <sys/sysctl.h>
+#include <sys/cpu.h>
+
+#include <arm/arm/smccc.h>
+#include <dev/ic/scmi.h>
+
+#define SCMI_SUCCESS		0
+#define SCMI_NOT_SUPPORTED	-1
+#define SCMI_DENIED		-3
+#define SCMI_BUSY		-6
+#define SCMI_COMMS_ERROR	-7
+
+/* Protocols */
+#define SCMI_BASE		0x10
+#define SCMI_PERF		0x13
+#define SCMI_CLOCK		0x14
+
+/* Common messages */
+#define SCMI_PROTOCOL_VERSION			0x0
+#define SCMI_PROTOCOL_ATTRIBUTES		0x1
+#define SCMI_PROTOCOL_MESSAGE_ATTRIBUTES	0x2
+
+/* Clock management messages */
+#define SCMI_CLOCK_ATTRIBUTES			0x3
+#define SCMI_CLOCK_DESCRIBE_RATES		0x4
+#define SCMI_CLOCK_RATE_SET			0x5
+#define SCMI_CLOCK_RATE_GET			0x6
+#define SCMI_CLOCK_CONFIG_SET			0x7
+#define  SCMI_CLOCK_CONFIG_SET_ENABLE		(1U << 0)
+
+/* Performance management messages */
+#define SCMI_PERF_DOMAIN_ATTRIBUTES		0x3
+#define SCMI_PERF_DESCRIBE_LEVELS		0x4
+#define SCMI_PERF_LIMITS_GET			0x6
+#define SCMI_PERF_LEVEL_SET			0x7
+#define SCMI_PERF_LEVEL_GET			0x8
+
+struct scmi_resp_perf_domain_attributes_40 {
+	uint32_t pa_attrs;
+#define SCMI_PERF_ATTR_CAN_LEVEL_SET		(1U << 30)
+#define SCMI_PERF_ATTR_LEVEL_INDEX_MODE		(1U << 25)
+	uint32_t pa_ratelimit;
+	uint32_t pa_sustifreq;
+	uint32_t pa_sustperf;
+	char 	 pa_name[16];
+};
+
+struct scmi_resp_perf_describe_levels_40 {
+	uint16_t pl_nret;
+	uint16_t pl_nrem;
+	struct {
+		uint32_t	pe_perf;
+		uint32_t	pe_cost;
+		uint16_t	pe_latency;
+		uint16_t	pe_reserved;
+		uint32_t	pe_ifreq;
+		uint32_t	pe_lindex;
+	} pl_entry[];
+};
+
+static void scmi_cpufreq_init_sysctl(struct scmi_softc *, uint32_t);
+
+static inline void
+scmi_message_header(volatile struct scmi_shmem *shmem,
+    uint32_t protocol_id, uint32_t message_id)
+{
+	shmem->message_header = (protocol_id << 10) | (message_id << 0);
+}
+
+int32_t	scmi_smc_command(struct scmi_softc *);
+int32_t	scmi_mbox_command(struct scmi_softc *);
+
+int
+scmi_init_smc(struct scmi_softc *sc)
+{
+	volatile struct scmi_shmem *shmem;
+	int32_t status;
+	uint32_t vers;
+
+	if (sc->sc_smc_id == 0) {
+		aprint_error_dev(sc->sc_dev, "no SMC id\n");
+		return -1;
+	}
+
+	shmem = sc->sc_shmem_tx;
+
+	sc->sc_command = scmi_smc_command;
+
+	if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0) {
+		aprint_error_dev(sc->sc_dev, "channel busy\n");
+		return -1;
+	}
+
+	scmi_message_header(shmem, SCMI_BASE, SCMI_PROTOCOL_VERSION);
+	shmem->length = sizeof(uint32_t);
+	status = sc->sc_command(sc);
+	if (status != SCMI_SUCCESS) {
+		aprint_error_dev(sc->sc_dev, "protocol version command failed\n");
+		return -1;
+	}
+
+	vers = shmem->message_payload[1];
+	sc->sc_ver_major = vers >> 16;
+	sc->sc_ver_minor = vers & 0xfffff;
+	aprint_normal_dev(sc->sc_dev, "SCMI %d.%d\n",
+	    sc->sc_ver_major, sc->sc_ver_minor);
+
+	mutex_init(&sc->sc_shmem_tx_lock, MUTEX_DEFAULT, IPL_NONE);
+	mutex_init(&sc->sc_shmem_rx_lock, MUTEX_DEFAULT, IPL_NONE);
+
+	return 0;
+}
+
+int
+scmi_init_mbox(struct scmi_softc *sc)
+{
+	int32_t status;
+	uint32_t vers;
+
+	if (sc->sc_mbox_tx == NULL) {
+		aprint_error_dev(sc->sc_dev, "no tx mbox\n");
+		return -1;
+	}
+	if (sc->sc_mbox_rx == NULL) {
+		aprint_error_dev(sc->sc_dev, "no rx mbox\n");
+		return -1;
+	}
+
+	sc->sc_command = scmi_mbox_command;
+
+	scmi_message_header(sc->sc_shmem_tx, SCMI_BASE, SCMI_PROTOCOL_VERSION);
+	sc->sc_shmem_tx->length = sizeof(uint32_t);
+	status = sc->sc_command(sc);
+	if (status != SCMI_SUCCESS) {
+		aprint_error_dev(sc->sc_dev,
+		    "protocol version command failed\n");
+		return -1;
+	}
+
+	vers = sc->sc_shmem_tx->message_payload[1];
+	sc->sc_ver_major = vers >> 16;
+	sc->sc_ver_minor = vers & 0xfffff;
+	aprint_normal_dev(sc->sc_dev, "SCMI %d.%d\n",
+	    sc->sc_ver_major, sc->sc_ver_minor);
+
+	mutex_init(&sc->sc_shmem_tx_lock, MUTEX_DEFAULT, IPL_NONE);
+	mutex_init(&sc->sc_shmem_rx_lock, MUTEX_DEFAULT, IPL_NONE);
+
+	return 0;
+}
+
+int32_t
+scmi_smc_command(struct scmi_softc *sc)
+{
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	int32_t status;
+
+	shmem->channel_status = 0;
+	status = smccc_call(sc->sc_smc_id, 0, 0, 0, 0,
+			    NULL, NULL, NULL, NULL);
+	if (status != SMCCC_SUCCESS)
+		return SCMI_NOT_SUPPORTED;
+	if ((shmem->channel_status & SCMI_CHANNEL_ERROR))
+		return SCMI_COMMS_ERROR;
+	if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0)
+		return SCMI_BUSY;
+	return shmem->message_payload[0];
+}
+
+int32_t
+scmi_mbox_command(struct scmi_softc *sc)
+{
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	int ret;
+	int i;
+
+	shmem->channel_status = 0;
+	ret = sc->sc_mbox_tx_send(sc->sc_mbox_tx);
+	if (ret != 0)
+		return SCMI_NOT_SUPPORTED; 
+
+	/* XXX: poll for now */
+	for (i = 0; i < 20; i++) {
+		if (shmem->channel_status & SCMI_CHANNEL_FREE)
+			break;
+		delay(10);
+	}
+	if ((shmem->channel_status & SCMI_CHANNEL_ERROR))
+		return SCMI_COMMS_ERROR;
+	if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0)
+		return SCMI_BUSY;
+
+	return shmem->message_payload[0];
+}
+
+#if notyet
+/* Clock management. */
+
+void	scmi_clock_enable(void *, uint32_t *, int);
+uint32_t scmi_clock_get_frequency(void *, uint32_t *);
+int	scmi_clock_set_frequency(void *, uint32_t *, uint32_t);
+
+void
+scmi_attach_clock(struct scmi_softc *sc, int node)
+{
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	int32_t status;
+	int nclocks;
+
+	scmi_message_header(shmem, SCMI_CLOCK, SCMI_PROTOCOL_ATTRIBUTES);
+	shmem->length = sizeof(uint32_t);
+	status = sc->sc_command(sc);
+	if (status != SCMI_SUCCESS)
+		return;
+
+	nclocks = shmem->message_payload[1] & 0xffff;
+	if (nclocks == 0)
+		return;
+
+	sc->sc_cd.cd_node = node;
+	sc->sc_cd.cd_cookie = sc;
+	sc->sc_cd.cd_enable = scmi_clock_enable;
+	sc->sc_cd.cd_get_frequency = scmi_clock_get_frequency;
+	sc->sc_cd.cd_set_frequency = scmi_clock_set_frequency;
+	clock_register(&sc->sc_cd);
+}
+
+void
+scmi_clock_enable(void *cookie, uint32_t *cells, int on)
+{
+	struct scmi_softc *sc = cookie;
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	uint32_t idx = cells[0];
+
+	scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_CONFIG_SET);
+	shmem->length = 3 * sizeof(uint32_t);
+	shmem->message_payload[0] = idx;
+	shmem->message_payload[1] = on ? SCMI_CLOCK_CONFIG_SET_ENABLE : 0;
+	sc->sc_command(sc);
+}
+
+uint32_t
+scmi_clock_get_frequency(void *cookie, uint32_t *cells)
+{
+	struct scmi_softc *sc = cookie;
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	uint32_t idx = cells[0];
+	int32_t status;
+
+	scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_RATE_GET);
+	shmem->length = 2 * sizeof(uint32_t);
+	shmem->message_payload[0] = idx;
+	status = sc->sc_command(sc);
+	if (status != SCMI_SUCCESS)
+		return 0;
+	if (shmem->message_payload[2] != 0)
+		return 0;
+
+	return shmem->message_payload[1];
+}
+
+int
+scmi_clock_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
+{
+	struct scmi_softc *sc = cookie;
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	uint32_t idx = cells[0];
+	int32_t status;
+
+	scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_RATE_SET);
+	shmem->length = 5 * sizeof(uint32_t);
+	shmem->message_payload[0] = 0;
+	shmem->message_payload[1] = idx;
+	shmem->message_payload[2] = freq;
+	shmem->message_payload[3] = 0;
+	status = sc->sc_command(sc);
+	if (status != SCMI_SUCCESS)
+		return -1;
+
+	return 0;
+}
+#endif
+
+/* Performance management */
+void	scmi_perf_descr_levels(struct scmi_softc *, int);
+
+void
+scmi_attach_perf(struct scmi_softc *sc)
+{
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	int32_t status;
+	uint32_t vers;
+	int i;
+
+	scmi_message_header(sc->sc_shmem_tx, SCMI_PERF, SCMI_PROTOCOL_VERSION);
+	sc->sc_shmem_tx->length = sizeof(uint32_t);
+	status = sc->sc_command(sc);
+	if (status != SCMI_SUCCESS) {
+		aprint_error_dev(sc->sc_dev,
+		    "SCMI_PROTOCOL_VERSION failed\n");
+		return;
+	}
+
+	vers = shmem->message_payload[1];
+	if (vers != 0x40000) {
+		aprint_error_dev(sc->sc_dev,
+		    "invalid perf protocol version (0x%x != 0x4000)", vers);
+		return;
+	}
+
+	scmi_message_header(shmem, SCMI_PERF, SCMI_PROTOCOL_ATTRIBUTES);
+	shmem->length = sizeof(uint32_t);
+	status = sc->sc_command(sc);
+	if (status != SCMI_SUCCESS) {
+		aprint_error_dev(sc->sc_dev,
+		    "SCMI_PROTOCOL_ATTRIBUTES failed\n");
+		return;
+	}
+
+	sc->sc_perf_ndomains = shmem->message_payload[1] & 0xffff;
+	sc->sc_perf_domains = kmem_zalloc(sc->sc_perf_ndomains *
+	    sizeof(struct scmi_perf_domain), KM_SLEEP);
+	sc->sc_perf_power_unit = (shmem->message_payload[1] >> 16) & 0x3;
+
+	/* Add one frequency sensor per perf domain */
+	for (i = 0; i < sc->sc_perf_ndomains; i++) {
+		volatile struct scmi_resp_perf_domain_attributes_40 *pa;
+
+		scmi_message_header(shmem, SCMI_PERF,
+		    SCMI_PERF_DOMAIN_ATTRIBUTES);
+		shmem->length = 2 * sizeof(uint32_t);
+		shmem->message_payload[0] = i;
+		status = sc->sc_command(sc);
+		if (status != SCMI_SUCCESS) {
+			aprint_error_dev(sc->sc_dev,
+			    "SCMI_PERF_DOMAIN_ATTRIBUTES failed\n");
+			return;
+		}
+
+		pa = (volatile struct scmi_resp_perf_domain_attributes_40 *)
+		    &shmem->message_payload[1];
+		aprint_debug_dev(sc->sc_dev,
+		    "dom %u attr %#x rate_limit %u sfreq %u sperf %u "
+		    "name \"%s\"\n",
+		    i, pa->pa_attrs, pa->pa_ratelimit, pa->pa_sustifreq,
+		    pa->pa_sustperf, pa->pa_name);
+
+		sc->sc_perf_domains[i].pd_domain_id = i;
+		sc->sc_perf_domains[i].pd_sc = sc;
+		for (int map = 0; map < sc->sc_perf_ndmap; map++) {
+			if (sc->sc_perf_dmap[map].pm_domain == i) {
+				sc->sc_perf_domains[i].pd_ci =
+				    sc->sc_perf_dmap[map].pm_ci;
+				break;
+			}
+		}
+		snprintf(sc->sc_perf_domains[i].pd_name,
+		    sizeof(sc->sc_perf_domains[i].pd_name), "%s", pa->pa_name);
+		sc->sc_perf_domains[i].pd_can_level_set =
+		    (pa->pa_attrs & SCMI_PERF_ATTR_CAN_LEVEL_SET) != 0;
+		sc->sc_perf_domains[i].pd_level_index_mode =
+		    (pa->pa_attrs & SCMI_PERF_ATTR_LEVEL_INDEX_MODE) != 0;
+		sc->sc_perf_domains[i].pd_rate_limit = pa->pa_ratelimit;
+		sc->sc_perf_domains[i].pd_sustained_perf = pa->pa_sustperf;
+
+		scmi_perf_descr_levels(sc, i);
+
+		if (sc->sc_perf_domains[i].pd_can_level_set &&
+		    sc->sc_perf_domains[i].pd_nlevels > 0 &&
+		    sc->sc_perf_domains[i].pd_levels[0].pl_ifreq != 0) {
+			scmi_cpufreq_init_sysctl(sc, i);
+		}
+	}
+	return;
+}
+
+void
+scmi_perf_descr_levels(struct scmi_softc *sc, int domain)
+{
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	volatile struct scmi_resp_perf_describe_levels_40 *pl;
+	struct scmi_perf_domain *pd = &sc->sc_perf_domains[domain];
+	int status, i, idx;
+
+	idx = 0;
+	do {
+		scmi_message_header(shmem, SCMI_PERF,
+		    SCMI_PERF_DESCRIBE_LEVELS);
+		shmem->length = sizeof(uint32_t) * 3;
+		shmem->message_payload[0] = domain;
+		shmem->message_payload[1] = idx;
+		status = sc->sc_command(sc);
+		if (status != SCMI_SUCCESS) {
+			aprint_error_dev(sc->sc_dev,
+			    "SCMI_PERF_DESCRIBE_LEVELS failed\n");
+			return;
+		}
+
+		pl = (volatile struct scmi_resp_perf_describe_levels_40 *)
+		    &shmem->message_payload[1];
+
+		if (pd->pd_levels == NULL) {
+			pd->pd_nlevels = pl->pl_nret + pl->pl_nrem;
+			pd->pd_levels = kmem_zalloc(pd->pd_nlevels *
+			    sizeof(struct scmi_perf_level),
+			    KM_SLEEP);
+		}
+
+		for (i = 0; i < pl->pl_nret; i++) {
+			pd->pd_levels[idx + i].pl_cost =
+			    pl->pl_entry[i].pe_cost;
+			pd->pd_levels[idx + i].pl_perf =
+			    pl->pl_entry[i].pe_perf;
+			pd->pd_levels[idx + i].pl_ifreq =
+			    pl->pl_entry[i].pe_ifreq;
+			aprint_debug_dev(sc->sc_dev,
+			    "dom %u pl %u cost %u perf %i ifreq %u\n",
+			    domain, idx + i,
+			    pl->pl_entry[i].pe_cost,
+			    pl->pl_entry[i].pe_perf,
+			    pl->pl_entry[i].pe_ifreq);
+		}
+		idx += pl->pl_nret;
+	} while (pl->pl_nrem);
+}
+
+static int32_t
+scmi_perf_limits_get(struct scmi_perf_domain *pd, uint32_t *max_level,
+    uint32_t *min_level)
+{
+	struct scmi_softc *sc = pd->pd_sc;
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	int32_t status;
+
+	if (pd->pd_levels == NULL) {
+		return SCMI_NOT_SUPPORTED;
+	}
+
+	mutex_enter(&sc->sc_shmem_tx_lock);
+	scmi_message_header(shmem, SCMI_PERF, SCMI_PERF_LIMITS_GET);
+	shmem->length = sizeof(uint32_t) * 2;
+	shmem->message_payload[0] = pd->pd_domain_id;
+	status = sc->sc_command(sc);
+	if (status == SCMI_SUCCESS) {
+		*max_level = shmem->message_payload[1];
+		*min_level = shmem->message_payload[2];
+	}
+	mutex_exit(&sc->sc_shmem_tx_lock);
+
+	return status;
+}
+
+static int32_t
+scmi_perf_level_get(struct scmi_perf_domain *pd, uint32_t *perf_level)
+{
+	struct scmi_softc *sc = pd->pd_sc;
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	int32_t status;
+
+	if (pd->pd_levels == NULL) {
+		return SCMI_NOT_SUPPORTED;
+	}
+
+	mutex_enter(&sc->sc_shmem_tx_lock);
+	scmi_message_header(shmem, SCMI_PERF, SCMI_PERF_LEVEL_GET);
+	shmem->length = sizeof(uint32_t) * 2;
+	shmem->message_payload[0] = pd->pd_domain_id;
+	status = sc->sc_command(sc);
+	if (status == SCMI_SUCCESS) {
+		*perf_level = shmem->message_payload[1];
+	}
+	mutex_exit(&sc->sc_shmem_tx_lock);
+
+	return status;
+}
+
+static int32_t
+scmi_perf_level_set(struct scmi_perf_domain *pd, uint32_t perf_level)
+{
+	struct scmi_softc *sc = pd->pd_sc;
+	volatile struct scmi_shmem *shmem = sc->sc_shmem_tx;
+	int32_t status;
+
+	if (pd->pd_levels == NULL) {
+		return SCMI_NOT_SUPPORTED;
+	}
+
+	mutex_enter(&sc->sc_shmem_tx_lock);
+	scmi_message_header(shmem, SCMI_PERF, SCMI_PERF_LEVEL_SET);
+	shmem->length = sizeof(uint32_t) * 3;
+	shmem->message_payload[0] = pd->pd_domain_id;
+	shmem->message_payload[1] = perf_level;
+	status = sc->sc_command(sc);
+	mutex_exit(&sc->sc_shmem_tx_lock);
+
+	return status;
+}
+
+static u_int
+scmi_cpufreq_level_to_mhz(struct scmi_perf_domain *pd, uint32_t level)
+{
+	ssize_t n;
+
+	if (pd->pd_level_index_mode) {
+		if (level < pd->pd_nlevels) {
+			return pd->pd_levels[level].pl_ifreq / 1000;
+		}
+	} else {
+		for (n = 0; n < pd->pd_nlevels; n++) {
+			if (pd->pd_levels[n].pl_perf == level) {
+				return pd->pd_levels[n].pl_ifreq / 1000;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int
+scmi_cpufreq_set_rate(struct scmi_softc *sc, struct scmi_perf_domain *pd,
+    u_int freq_mhz)
+{
+	uint32_t perf_level = -1;
+	int32_t status;
+	ssize_t n;
+
+	for (n = 0; n < pd->pd_nlevels; n++) {
+		if (pd->pd_levels[n].pl_ifreq / 1000 == freq_mhz) {
+			perf_level = pd->pd_level_index_mode ?
+			    n : pd->pd_levels[n].pl_perf;
+			break;
+		}
+	}
+	if (n == pd->pd_nlevels)
+		return EINVAL;
+
+	status = scmi_perf_level_set(pd, perf_level);
+	if (status != SCMI_SUCCESS) {
+		return EIO;
+	}
+
+	if (pd->pd_rate_limit > 0)
+		delay(pd->pd_rate_limit);
+
+	return 0;
+}
+
+static int
+scmi_cpufreq_sysctl_helper(SYSCTLFN_ARGS)
+{
+	struct scmi_perf_domain * const pd = rnode->sysctl_data;
+	struct scmi_softc * const sc = pd->pd_sc;
+	struct sysctlnode node;
+	u_int fq, oldfq = 0, old_target;
+	uint32_t level;
+	int32_t status;
+	int error;
+
+	node = *rnode;
+	node.sysctl_data = &fq;
+
+	if (rnode->sysctl_num == pd->pd_node_target) {
+		if (pd->pd_freq_target == 0) {
+			status = scmi_perf_level_get(pd, &level);
+			if (status != SCMI_SUCCESS) {
+				return EIO;
+			}
+			pd->pd_freq_target =
+			    scmi_cpufreq_level_to_mhz(pd, level);
+		}
+		fq = pd->pd_freq_target;
+	} else {
+		status = scmi_perf_level_get(pd, &level);
+		if (status != SCMI_SUCCESS) {
+			return EIO;
+		}
+		fq = scmi_cpufreq_level_to_mhz(pd, level);
+	}
+
+	if (rnode->sysctl_num == pd->pd_node_target)
+		oldfq = fq;
+
+	if (pd->pd_freq_target == 0)
+		pd->pd_freq_target = fq;
+
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return error;
+
+	if (fq == oldfq || rnode->sysctl_num != pd->pd_node_target)
+		return 0;
+
+	if (atomic_cas_uint(&pd->pd_busy, 0, 1) != 0)
+		return EBUSY;
+
+	old_target = pd->pd_freq_target;
+	pd->pd_freq_target = fq;
+
+	error = scmi_cpufreq_set_rate(sc, pd, fq);
+	if (error != 0) {
+		pd->pd_freq_target = old_target;
+	}
+
+	atomic_dec_uint(&pd->pd_busy);
+
+	return error;
+}
+
+static void
+scmi_cpufreq_init_sysctl(struct scmi_softc *sc, uint32_t domain_id)
+{
+	const struct sysctlnode *node, *cpunode;
+	struct scmi_perf_domain *pd = &sc->sc_perf_domains[domain_id];
+	struct cpu_info *ci = pd->pd_ci;
+	struct sysctllog *cpufreq_log = NULL;
+	uint32_t max_level, min_level;
+	int32_t status;
+	int error, i;
+
+	if (ci == NULL)
+		return;
+
+	status = scmi_perf_limits_get(pd, &max_level, &min_level);
+	if (status != SCMI_SUCCESS) {
+		/*
+		 * Not supposed to happen, but at least one implementation
+		 * returns DENIED here. Assume that there are no limits.
+		 */
+		min_level = 0;
+		max_level = UINT32_MAX;
+	}
+	aprint_debug_dev(sc->sc_dev, "dom %u limits max %u min %u\n",
+	    domain_id, max_level, min_level);
+
+	pd->pd_freq_available = kmem_zalloc(strlen("XXXX ") *
+	    pd->pd_nlevels, KM_SLEEP);
+	for (i = 0; i < pd->pd_nlevels; i++) {
+		char buf[6];
+		uint32_t level = pd->pd_level_index_mode ?
+				 i : pd->pd_levels[i].pl_perf;
+
+		if (level < min_level) {
+			continue;
+		} else if (level > max_level) {
+			break;
+		}
+
+		snprintf(buf, sizeof(buf), i ? " %u" : "%u",
+		    pd->pd_levels[i].pl_ifreq / 1000);
+		strcat(pd->pd_freq_available, buf);
+		if (level == pd->pd_sustained_perf) {
+			break;
+		}
+	}
+
+	error = sysctl_createv(&cpufreq_log, 0, NULL, &node,
+	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
+	    NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	error = sysctl_createv(&cpufreq_log, 0, &node, &node,
+	    0, CTLTYPE_NODE, "cpufreq", NULL,
+	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	error = sysctl_createv(&cpufreq_log, 0, &node, &cpunode,
+	    0, CTLTYPE_NODE, cpu_name(ci), NULL,
+	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+
+	error = sysctl_createv(&cpufreq_log, 0, &cpunode, &node,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "target", NULL,
+	    scmi_cpufreq_sysctl_helper, 0, (void *)pd, 0,
+	    CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	pd->pd_node_target = node->sysctl_num;
+
+	error = sysctl_createv(&cpufreq_log, 0, &cpunode, &node,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "current", NULL,
+	    scmi_cpufreq_sysctl_helper, 0, (void *)pd, 0,
+	    CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	pd->pd_node_current = node->sysctl_num;
+
+	error = sysctl_createv(&cpufreq_log, 0, &cpunode, &node,
+	    0, CTLTYPE_STRING, "available", NULL,
+	    NULL, 0, pd->pd_freq_available, 0,
+	    CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	pd->pd_node_available = node->sysctl_num;
+
+	return;
+
+sysctl_failed:
+	aprint_error_dev(sc->sc_dev, "couldn't create sysctl nodes: %d\n",
+	    error);
+	sysctl_teardown(&cpufreq_log);
+}
Index: src/sys/dev/ic/scmi.h
diff -u /dev/null src/sys/dev/ic/scmi.h:1.1
--- /dev/null	Wed Jan  8 22:55:35 2025
+++ src/sys/dev/ic/scmi.h	Wed Jan  8 22:55:35 2025
@@ -0,0 +1,116 @@
+/* $NetBSD: scmi.h,v 1.1 2025/01/08 22:55:35 jmcneill Exp $ */
+
+/*
+ * Copyright (c) 2023 Mark Kettenis <kette...@openbsd.org>
+ * Copyright (c) 2024 Tobias Heider <to...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#pragma once
+
+#include <sys/param.h>
+#include <sys/device.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kmem.h>
+#include <sys/mutex.h>
+
+struct scmi_shmem {
+	uint32_t reserved1;
+	uint32_t channel_status;
+#define SCMI_CHANNEL_ERROR		(1 << 1)
+#define SCMI_CHANNEL_FREE		(1 << 0)
+	uint32_t reserved2;
+	uint32_t reserved3;
+	uint32_t channel_flags;
+	uint32_t length;
+	uint32_t message_header;
+	uint32_t message_payload[];
+};
+
+struct scmi_softc;
+
+struct scmi_perf_level {
+	uint32_t	pl_perf;
+	uint32_t	pl_cost;
+	uint32_t	pl_ifreq;
+};
+
+struct scmi_perf_domain {
+	struct scmi_softc		*pd_sc;
+	uint32_t			pd_domain_id;
+	size_t				pd_nlevels;
+	struct scmi_perf_level		*pd_levels;
+	int				pd_curlevel;
+	char				pd_name[16];
+	bool				pd_can_level_set;
+	bool				pd_level_index_mode;
+	uint32_t			pd_rate_limit;
+	uint32_t			pd_sustained_perf;
+
+	struct cpu_info			*pd_ci;	/* first CPU in domain */
+	u_int				pd_busy;
+	char				*pd_freq_available;
+	u_int				pd_freq_target;
+	int				pd_node_target;
+	int				pd_node_current;
+	int				pd_node_available;
+};
+
+struct scmi_perf_domain_map {
+	uint32_t			pm_domain;
+	struct cpu_info			*pm_ci;	/* first CPU in domain */
+};
+
+struct scmi_softc {
+	device_t			sc_dev;
+	bus_space_tag_t			sc_iot;
+	volatile struct scmi_shmem	*sc_shmem_tx;
+	volatile struct scmi_shmem	*sc_shmem_rx;
+	kmutex_t			sc_shmem_tx_lock;
+	kmutex_t			sc_shmem_rx_lock;
+
+	uint32_t			sc_smc_id;
+
+	void				*sc_mbox_tx;
+	int				(*sc_mbox_tx_send)(void *);
+	void				*sc_mbox_rx;
+	int				(*sc_mbox_rx_send)(void *);
+
+	size_t				sc_perf_ndmap;
+	struct scmi_perf_domain_map	*sc_perf_dmap;
+
+	uint16_t			sc_ver_major;
+	uint16_t			sc_ver_minor;
+
+#if notyet
+	/* SCMI_CLOCK */
+	struct clock_device		sc_cd;
+#endif
+
+	/* SCMI_PERF */
+	int				sc_perf_power_unit;
+#define SCMI_POWER_UNIT_UW	0x2
+#define SCMI_POWER_UNIT_MW	0x1
+#define SCMI_POWER_UNIT_NONE	0x0
+	size_t				sc_perf_ndomains;
+	struct scmi_perf_domain		*sc_perf_domains;
+
+	int32_t				(*sc_command)(struct scmi_softc *);
+};
+
+int	scmi_init_smc(struct scmi_softc *);
+int	scmi_init_mbox(struct scmi_softc *);
+
+void    scmi_attach_perf(struct scmi_softc *);

Reply via email to