/* $OpenBSD: scmi.c,v 1.2 2024/11/25 22:12:18 tobhe Exp $ */ /* * Copyright (c) 2023 Mark Kettenis * Copyright (c) 2024 Tobias Heider * * 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 #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_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 (1 << 0) /* Performance management messages */ #define SCMI_PERF_DOMAIN_ATTRIBUTES 0x3 #define SCMI_PERF_DESCRIBE_LEVELS 0x4 #define SCMI_PERF_LEVEL_GET 0x8 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 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_perf_level { uint32_t pl_perf; uint32_t pl_cost; uint32_t pl_ifreq; }; struct scmi_perf_domain { size_t pd_nlevels; struct scmi_perf_level *pd_levels; int pd_curlevel; }; struct scmi_softc { struct device sc_dev; bus_space_tag_t sc_iot; int sc_node; bus_space_handle_t sc_ioh_tx; bus_space_handle_t sc_ioh_rx; volatile struct scmi_shmem *sc_shmem_tx; volatile struct scmi_shmem *sc_shmem_rx; uint32_t sc_smc_id; struct mbox_channel *sc_mc_tx; struct mbox_channel *sc_mc_rx; uint16_t sc_ver_major; uint16_t sc_ver_minor; /* SCMI_CLOCK */ struct clock_device sc_cd; /* 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; struct ksensordev sc_perf_sensordev; struct ksensordev sc_perf_psensordev; struct ksensor *sc_perf_fsensors; struct ksensor *sc_perf_psensors; int32_t (*sc_command)(struct scmi_softc *); }; int scmi_match(struct device *, void *, void *); void scmi_attach(struct device *, struct device *, void *); int scmi_attach_smc(struct scmi_softc *, struct fdt_attach_args *); void scmi_attach_mbox_deferred(struct device *); 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); void scmi_attach_perf(struct scmi_softc *, int); int32_t scmi_smc_command(struct scmi_softc *); int32_t scmi_mbox_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") || OF_is_compatible(faa->fa_node, "arm,scmi"); } void scmi_attach(struct device *parent, struct device *self, void *aux) { struct scmi_softc *sc = (struct scmi_softc *)self; struct fdt_attach_args *faa = aux; sc->sc_iot = faa->fa_iot; sc->sc_node = faa->fa_node; if (OF_is_compatible(faa->fa_node, "arm,scmi-smc")) { scmi_attach_smc(sc, faa); } else if (OF_is_compatible(faa->fa_node, "arm,scmi")) { printf("\n"); /* Defer because we need the mailbox driver attached first */ config_defer(self, scmi_attach_mbox_deferred); } } int scmi_attach_smc(struct scmi_softc *sc, struct fdt_attach_args *faa) { volatile struct scmi_shmem *shmem; struct fdt_reg reg; int32_t status; uint32_t version; uint32_t phandle; void *node; int proto; 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 -1; } 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 -1; } if (bus_space_map(sc->sc_iot, reg.addr, reg.size, 0, &sc->sc_ioh_tx)) { printf(": can't map shared memory\n"); return -1; } sc->sc_shmem_tx = bus_space_vaddr(sc->sc_iot, sc->sc_ioh_tx); shmem = sc->sc_shmem_tx; sc->sc_command = scmi_smc_command; if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0) { printf(": 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) { printf(": protocol version command failed\n"); return -1; } version = shmem->message_payload[1]; sc->sc_ver_major = version >> 16; sc->sc_ver_minor = version & 0xfffff; printf(": SCMI %d.%d\n", sc->sc_ver_major, sc->sc_ver_minor); for (proto = OF_child(faa->fa_node); proto; proto = OF_peer(proto)) scmi_attach_proto(sc, proto); return 0; } void scmi_attach_mbox_deferred(struct device *self) { struct scmi_softc *sc = (struct scmi_softc *)self; uint32_t *shmems; int32_t status; uint32_t version; struct fdt_reg reg; int len; void *node; int proto; /* we only support the 2 mbox / 2 shmem case */ len = OF_getproplen(sc->sc_node, "mboxes"); if (len != 4 * sizeof(uint32_t)) { printf("%s: invalid number of mboxes\n", sc->sc_dev.dv_xname); return; } len = OF_getproplen(sc->sc_node, "shmem"); if (len != 2 * sizeof(uint32_t)) { printf("%s: invalid number of shmems\n", sc->sc_dev.dv_xname); return; } shmems = malloc(len, M_DEVBUF, M_WAITOK); OF_getpropintarray(sc->sc_node, "shmem", shmems, len); sc->sc_mc_tx = mbox_channel(sc->sc_node, "tx", NULL); if (sc->sc_mc_tx == NULL) { printf("%s: no tx mbox\n", sc->sc_dev.dv_xname); return; } sc->sc_mc_rx = mbox_channel(sc->sc_node, "rx", NULL); if (sc->sc_mc_rx == NULL) { printf("%s: no rx mbox\n", sc->sc_dev.dv_xname); return; } node = fdt_find_phandle(shmems[0]); if (node == NULL || !fdt_is_compatible(node, "arm,scmi-shmem") || fdt_get_reg(node, 0, ®)) { printf("%s: no shared memory\n", sc->sc_dev.dv_xname); return; } if (bus_space_map(sc->sc_iot, reg.addr, reg.size, 0, &sc->sc_ioh_tx)) { printf("%s: can't map shared memory\n", sc->sc_dev.dv_xname); return; } sc->sc_shmem_tx = bus_space_vaddr(sc->sc_iot, sc->sc_ioh_tx); node = fdt_find_phandle(shmems[1]); if (node == NULL || !fdt_is_compatible(node, "arm,scmi-shmem") || fdt_get_reg(node, 0, ®)) { printf("%s: no shared memory\n", sc->sc_dev.dv_xname); return; } if (bus_space_map(sc->sc_iot, reg.addr, reg.size, 0, &sc->sc_ioh_rx)) { printf("%s: can't map shared memory\n", sc->sc_dev.dv_xname); return; } sc->sc_shmem_rx = bus_space_vaddr(sc->sc_iot, sc->sc_ioh_rx); 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) { printf("%s: protocol version command failed\n", sc->sc_dev.dv_xname); return; } version = sc->sc_shmem_tx->message_payload[1]; sc->sc_ver_major = version >> 16; sc->sc_ver_minor = version & 0xfffff; printf("%s: SCMI %d.%d\n", sc->sc_dev.dv_xname, sc->sc_ver_major, sc->sc_ver_minor); for (proto = OF_child(sc->sc_node); proto; proto = OF_peer(proto)) scmi_attach_proto(sc, proto); } 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(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]; } 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 = mbox_send(sc->sc_mc_tx, NULL, 0); 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]; } 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; case SCMI_PERF: scmi_attach_perf(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_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; } /* Performance management */ void scmi_perf_descr_levels(struct scmi_softc *, int); void scmi_perf_refresh_sensor(void *); void scmi_attach_perf(struct scmi_softc *sc, int node) { volatile struct scmi_shmem *shmem = sc->sc_shmem_tx; int32_t status; uint32_t version; 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) { printf("%s: SCMI_PROTOCOL_VERSION failed\n", sc->sc_dev.dv_xname); return; } version = shmem->message_payload[1]; if (version != 0x40000) { printf("%s: invalid perf protocol version (0x%x != 0x4000)", sc->sc_dev.dv_xname, version); return; } scmi_message_header(shmem, SCMI_PERF, SCMI_PROTOCOL_ATTRIBUTES); shmem->length = sizeof(uint32_t); status = sc->sc_command(sc); if (status != SCMI_SUCCESS) { printf("%s: SCMI_PROTOCOL_ATTRIBUTES failed\n", sc->sc_dev.dv_xname); return; } sc->sc_perf_ndomains = shmem->message_payload[1] & 0xffff; sc->sc_perf_domains = malloc(sc->sc_perf_ndomains * sizeof(struct scmi_perf_domain), M_DEVBUF, M_ZERO | M_WAITOK); sc->sc_perf_power_unit = (shmem->message_payload[1] >> 16) & 0x3; strlcpy(sc->sc_perf_sensordev.xname, sc->sc_dev.dv_xname, sizeof(sc->sc_perf_sensordev.xname)); sc->sc_perf_fsensors = malloc(sc->sc_perf_ndomains * sizeof(struct ksensor), M_DEVBUF, M_ZERO | M_WAITOK); sc->sc_perf_psensors = malloc(sc->sc_perf_ndomains * sizeof(struct ksensor), M_DEVBUF, M_ZERO | M_WAITOK); /* Add one frequency sensor per perf domain */ for (i = 0; i < sc->sc_perf_ndomains; i++) { 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) { printf("%s: SCMI_PERF_DOMAIN_ATTRIBUTES failed\n", sc->sc_dev.dv_xname); goto err; } scmi_perf_descr_levels(sc, i); sc->sc_perf_fsensors[i].type = SENSOR_FREQ; sensor_attach(&sc->sc_perf_sensordev, &sc->sc_perf_fsensors[i]); sc->sc_perf_psensors[i].type = SENSOR_WATTS; sensor_attach(&sc->sc_perf_sensordev, &sc->sc_perf_psensors[i]); } sensordev_install(&sc->sc_perf_sensordev); sensor_task_register(sc, scmi_perf_refresh_sensor, 1); return; err: free(sc->sc_perf_fsensors, M_DEVBUF, sc->sc_perf_ndomains * sizeof(struct ksensor)); free(sc->sc_perf_psensors, M_DEVBUF, sc->sc_perf_ndomains * sizeof(struct ksensor)); } 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) { printf("%s: SCMI_PERF_DESCRIBE_LEVELS failed\n", sc->sc_dev.dv_xname); return; } pl = (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 = malloc(pd->pd_nlevels * sizeof(struct scmi_perf_level), M_DEVBUF, M_ZERO | M_WAITOK); } 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; } idx += pl->pl_nret; } while (pl->pl_nrem); } void scmi_perf_refresh_sensor(void *arg) { struct scmi_softc *sc = arg; volatile struct scmi_shmem *shmem = sc->sc_shmem_tx; uint64_t power_cost; int32_t status; int level, i; if (sc->sc_perf_domains == NULL) return; for (i = 0; i < sc->sc_perf_ndomains; i++) { if (sc->sc_perf_domains[i].pd_levels == NULL) return; scmi_message_header(shmem, SCMI_PERF, SCMI_PERF_LEVEL_GET); shmem->length = sizeof(uint32_t) * 2; shmem->message_payload[0] = i; status = sc->sc_command(sc); if (status != SCMI_SUCCESS) { printf("%s: SCMI_PERF_LEVEL_GET failed\n", sc->sc_dev.dv_xname); return; } level = shmem->message_payload[1]; if (sc->sc_perf_fsensors == NULL || sc->sc_perf_psensors == NULL) return; sc->sc_perf_domains[i].pd_curlevel = level; sc->sc_perf_fsensors[i].value = (uint64_t)sc->sc_perf_domains[i]. pd_levels[level].pl_ifreq * 1000000000; switch (sc->sc_perf_power_unit) { case SCMI_POWER_UNIT_UW: power_cost = (uint64_t)sc->sc_perf_domains[i]. pd_levels[level].pl_cost; break; case SCMI_POWER_UNIT_MW: power_cost = (uint64_t)sc->sc_perf_domains[i]. pd_levels[level].pl_cost * 1000; break; default: continue; } sc->sc_perf_psensors[i].value = power_cost; } }