summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2023-02-13 19:26:16 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2023-02-13 19:26:16 +0000
commit46cb011a736a89cc866b80f3e30ad0ffac47064a (patch)
tree872241a2e6b006de4d07cc0656fc3b13c0020b80
parent4e4d003028cf782bb4b83a5952e57246c3ddbd58 (diff)
Add a driver for the ARM System Control and Management Interface, which,
among other things allows management of clocks under firmware management. ok patrick@
-rw-r--r--sys/dev/fdt/files.fdt7
-rw-r--r--sys/dev/fdt/psci.c13
-rw-r--r--sys/dev/fdt/pscivar.h2
-rw-r--r--sys/dev/fdt/scmi.c278
4 files changed, 298 insertions, 2 deletions
diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt
index 9f17b472058..ec9baadffb1 100644
--- a/sys/dev/fdt/files.fdt
+++ b/sys/dev/fdt/files.fdt
@@ -1,4 +1,4 @@
-# $OpenBSD: files.fdt,v 1.177 2023/02/13 19:18:53 patrick Exp $
+# $OpenBSD: files.fdt,v 1.178 2023/02/13 19:26:15 kettenis Exp $
#
# Config file and device description for machine-independent FDT code.
# Included by ports that need it.
@@ -195,6 +195,11 @@ device psci
attach psci at fdt
file dev/fdt/psci.c psci needs-flag
+# ARM System Control and Management Interface
+device scmi
+attach scmi at fdt
+file dev/fdt/scmi.c
+
attach virtio at fdt with virtio_mmio
file dev/fdt/virtio_mmio.c virtio_mmio
diff --git a/sys/dev/fdt/psci.c b/sys/dev/fdt/psci.c
index 10aaca1a320..469cf1f67d0 100644
--- a/sys/dev/fdt/psci.c
+++ b/sys/dev/fdt/psci.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: psci.c,v 1.12 2022/12/10 10:13:58 patrick Exp $ */
+/* $OpenBSD: psci.c,v 1.13 2023/02/13 19:26:15 kettenis Exp $ */
/*
* Copyright (c) 2016 Jonathan Gray <jsg@openbsd.org>
@@ -265,6 +265,17 @@ smccc_version(void)
}
int32_t
+smccc(uint32_t func_id, register_t arg0, register_t arg1, register_t arg2)
+{
+ struct psci_softc *sc = psci_sc;
+
+ if (sc && sc->sc_callfn)
+ return (*sc->sc_callfn)(func_id, arg0, arg1, arg2);
+
+ return PSCI_NOT_SUPPORTED;
+}
+
+int32_t
smccc_arch_features(uint32_t arch_func_id)
{
struct psci_softc *sc = psci_sc;
diff --git a/sys/dev/fdt/pscivar.h b/sys/dev/fdt/pscivar.h
index e8ec062f003..9a400290f93 100644
--- a/sys/dev/fdt/pscivar.h
+++ b/sys/dev/fdt/pscivar.h
@@ -19,4 +19,6 @@ void psci_flush_bp(void);
int psci_flush_bp_has_bhb(void);
int psci_method(void);
+int32_t smccc(uint32_t, register_t, register_t, register_t);
+
#endif /* _SYS_DEV_FDT_PSCIVAR_H_ */
diff --git a/sys/dev/fdt/scmi.c b/sys/dev/fdt/scmi.c
new file mode 100644
index 00000000000..5c567e56792
--- /dev/null
+++ b/sys/dev/fdt/scmi.c
@@ -0,0 +1,278 @@
+/* $OpenBSD: scmi.c,v 1.1 2023/02/13 19:26:15 kettenis Exp $ */
+
+/*
+ * Copyright (c) 2023 Mark Kettenis <kettenis@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.
+ */
+
+#include <sys/param.h>
+#include <sys/device.h>
+#include <sys/systm.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_clock.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/fdt/pscivar.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[];
+};
+
+#define SCMI_SUCCESS 0
+#define SCMI_NOT_SUPPORTED -1
+#define SCMI_BUSY -6
+#define SCMI_COMMS_ERROR -7
+
+/* Protocols */
+#define SCMI_BASE 0x10
+#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 (1 << 0)
+
+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);
+}
+
+
+struct scmi_softc {
+ struct device sc_dev;
+ bus_space_tag_t sc_iot;
+ bus_space_handle_t sc_ioh;
+ volatile struct scmi_shmem *sc_shmem;
+
+ uint32_t sc_smc_id;
+
+ struct clock_device sc_cd;
+};
+
+int scmi_match(struct device *, void *, void *);
+void scmi_attach(struct device *, struct device *, void *);
+
+const struct cfattach scmi_ca = {
+ sizeof(struct scmi_softc), scmi_match, scmi_attach
+};
+
+struct cfdriver scmi_cd = {
+ NULL, "scmi", DV_DULL
+};
+
+void scmi_attach_proto(struct scmi_softc *, int);
+void scmi_attach_clock(struct scmi_softc *, int);
+int32_t scmi_command(struct scmi_softc *);
+
+int
+scmi_match(struct device *parent, void *match, void *aux)
+{
+ struct fdt_attach_args *faa = aux;
+
+ return OF_is_compatible(faa->fa_node, "arm,scmi-smc");
+}
+
+void
+scmi_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct scmi_softc *sc = (struct scmi_softc *)self;
+ volatile struct scmi_shmem *shmem;
+ struct fdt_attach_args *faa = aux;
+ struct fdt_reg reg;
+ int32_t status;
+ uint32_t version;
+ uint32_t phandle;
+ void *node;
+ int proto;
+
+ phandle = OF_getpropint(faa->fa_node, "shmem", 0);
+ node = fdt_find_phandle(phandle);
+ if (node == NULL || !fdt_is_compatible(node, "arm,scmi-shmem") ||
+ fdt_get_reg(node, 0, &reg)) {
+ printf(": no shared memory\n");
+ return;
+ }
+
+ sc->sc_smc_id = OF_getpropint(faa->fa_node, "arm,smc-id", 0);
+ if (sc->sc_smc_id == 0) {
+ printf(": no SMC id\n");
+ return;
+ }
+
+ sc->sc_iot = faa->fa_iot;
+ if (bus_space_map(sc->sc_iot, reg.addr,
+ reg.size, 0, &sc->sc_ioh)) {
+ printf(": can't map shared memory\n");
+ return;
+ }
+ sc->sc_shmem = bus_space_vaddr(sc->sc_iot, sc->sc_ioh);
+ shmem = sc->sc_shmem;
+
+ if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0) {
+ printf(": channel busy\n");
+ return;
+ }
+
+ scmi_message_header(shmem, SCMI_BASE, SCMI_PROTOCOL_VERSION);
+ shmem->length = sizeof(uint32_t);
+ status = scmi_command(sc);
+ if (status != SCMI_SUCCESS) {
+ printf(": protocol version command failed\n");
+ return;
+ }
+
+ version = shmem->message_payload[1];
+ printf(": SCMI %d.%d\n", version >> 16, version & 0xffff);
+
+ for (proto = OF_child(faa->fa_node); proto; proto = OF_peer(proto))
+ scmi_attach_proto(sc, proto);
+}
+
+int32_t
+scmi_command(struct scmi_softc *sc)
+{
+ volatile struct scmi_shmem *shmem = sc->sc_shmem;
+ int32_t status;
+
+ shmem->channel_status = 0;
+ status = smccc(sc->sc_smc_id, 0, 0, 0);
+ if (status != PSCI_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];
+}
+
+void
+scmi_attach_proto(struct scmi_softc *sc, int node)
+{
+ switch (OF_getpropint(node, "reg", -1)) {
+ case SCMI_CLOCK:
+ scmi_attach_clock(sc, node);
+ break;
+ default:
+ break;
+ }
+}
+
+/* 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;
+ int32_t status;
+ int nclocks;
+
+ scmi_message_header(shmem, SCMI_CLOCK, SCMI_PROTOCOL_ATTRIBUTES);
+ shmem->length = sizeof(uint32_t);
+ status = scmi_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;
+ 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;
+ scmi_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;
+ 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 = scmi_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;
+ 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 = scmi_command(sc);
+ if (status != SCMI_SUCCESS)
+ return -1;
+
+ return 0;
+}