From 46cb011a736a89cc866b80f3e30ad0ffac47064a Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Mon, 13 Feb 2023 19:26:16 +0000 Subject: Add a driver for the ARM System Control and Management Interface, which, among other things allows management of clocks under firmware management. ok patrick@ --- sys/dev/fdt/files.fdt | 7 +- sys/dev/fdt/psci.c | 13 ++- sys/dev/fdt/pscivar.h | 2 + sys/dev/fdt/scmi.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 sys/dev/fdt/scmi.c 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 @@ -264,6 +264,17 @@ smccc_version(void) return 0x10000; } +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) { 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 + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include + +#include + +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, ®)) { + 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; +} -- cgit v1.2.3