1 /* $OpenBSD: scmi.c,v 1.1 2023/02/13 19:26:15 kettenis Exp $ */ 2 3 /* 4 * Copyright (c) 2023 Mark Kettenis <kettenis@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> 20 #include <sys/device.h> 21 #include <sys/systm.h> 22 23 #include <machine/bus.h> 24 #include <machine/fdt.h> 25 26 #include <dev/ofw/openfirm.h> 27 #include <dev/ofw/ofw_clock.h> 28 #include <dev/ofw/fdt.h> 29 30 #include <dev/fdt/pscivar.h> 31 32 struct scmi_shmem { 33 uint32_t reserved1; 34 uint32_t channel_status; 35 #define SCMI_CHANNEL_ERROR (1 << 1) 36 #define SCMI_CHANNEL_FREE (1 << 0) 37 uint32_t reserved2; 38 uint32_t reserved3; 39 uint32_t channel_flags; 40 uint32_t length; 41 uint32_t message_header; 42 uint32_t message_payload[]; 43 }; 44 45 #define SCMI_SUCCESS 0 46 #define SCMI_NOT_SUPPORTED -1 47 #define SCMI_BUSY -6 48 #define SCMI_COMMS_ERROR -7 49 50 /* Protocols */ 51 #define SCMI_BASE 0x10 52 #define SCMI_CLOCK 0x14 53 54 /* Common messages */ 55 #define SCMI_PROTOCOL_VERSION 0x0 56 #define SCMI_PROTOCOL_ATTRIBUTES 0x1 57 #define SCMI_PROTOCOL_MESSAGE_ATTRIBUTES 0x2 58 59 /* Clock management messages */ 60 #define SCMI_CLOCK_ATTRIBUTES 0x3 61 #define SCMI_CLOCK_DESCRIBE_RATES 0x4 62 #define SCMI_CLOCK_RATE_SET 0x5 63 #define SCMI_CLOCK_RATE_GET 0x6 64 #define SCMI_CLOCK_CONFIG_SET 0x7 65 #define SCMI_CLOCK_CONFIG_SET_ENABLE (1 << 0) 66 67 static inline void 68 scmi_message_header(volatile struct scmi_shmem *shmem, 69 uint32_t protocol_id, uint32_t message_id) 70 { 71 shmem->message_header = (protocol_id << 10) | (message_id << 0); 72 } 73 74 75 struct scmi_softc { 76 struct device sc_dev; 77 bus_space_tag_t sc_iot; 78 bus_space_handle_t sc_ioh; 79 volatile struct scmi_shmem *sc_shmem; 80 81 uint32_t sc_smc_id; 82 83 struct clock_device sc_cd; 84 }; 85 86 int scmi_match(struct device *, void *, void *); 87 void scmi_attach(struct device *, struct device *, void *); 88 89 const struct cfattach scmi_ca = { 90 sizeof(struct scmi_softc), scmi_match, scmi_attach 91 }; 92 93 struct cfdriver scmi_cd = { 94 NULL, "scmi", DV_DULL 95 }; 96 97 void scmi_attach_proto(struct scmi_softc *, int); 98 void scmi_attach_clock(struct scmi_softc *, int); 99 int32_t scmi_command(struct scmi_softc *); 100 101 int 102 scmi_match(struct device *parent, void *match, void *aux) 103 { 104 struct fdt_attach_args *faa = aux; 105 106 return OF_is_compatible(faa->fa_node, "arm,scmi-smc"); 107 } 108 109 void 110 scmi_attach(struct device *parent, struct device *self, void *aux) 111 { 112 struct scmi_softc *sc = (struct scmi_softc *)self; 113 volatile struct scmi_shmem *shmem; 114 struct fdt_attach_args *faa = aux; 115 struct fdt_reg reg; 116 int32_t status; 117 uint32_t version; 118 uint32_t phandle; 119 void *node; 120 int proto; 121 122 phandle = OF_getpropint(faa->fa_node, "shmem", 0); 123 node = fdt_find_phandle(phandle); 124 if (node == NULL || !fdt_is_compatible(node, "arm,scmi-shmem") || 125 fdt_get_reg(node, 0, ®)) { 126 printf(": no shared memory\n"); 127 return; 128 } 129 130 sc->sc_smc_id = OF_getpropint(faa->fa_node, "arm,smc-id", 0); 131 if (sc->sc_smc_id == 0) { 132 printf(": no SMC id\n"); 133 return; 134 } 135 136 sc->sc_iot = faa->fa_iot; 137 if (bus_space_map(sc->sc_iot, reg.addr, 138 reg.size, 0, &sc->sc_ioh)) { 139 printf(": can't map shared memory\n"); 140 return; 141 } 142 sc->sc_shmem = bus_space_vaddr(sc->sc_iot, sc->sc_ioh); 143 shmem = sc->sc_shmem; 144 145 if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0) { 146 printf(": channel busy\n"); 147 return; 148 } 149 150 scmi_message_header(shmem, SCMI_BASE, SCMI_PROTOCOL_VERSION); 151 shmem->length = sizeof(uint32_t); 152 status = scmi_command(sc); 153 if (status != SCMI_SUCCESS) { 154 printf(": protocol version command failed\n"); 155 return; 156 } 157 158 version = shmem->message_payload[1]; 159 printf(": SCMI %d.%d\n", version >> 16, version & 0xffff); 160 161 for (proto = OF_child(faa->fa_node); proto; proto = OF_peer(proto)) 162 scmi_attach_proto(sc, proto); 163 } 164 165 int32_t 166 scmi_command(struct scmi_softc *sc) 167 { 168 volatile struct scmi_shmem *shmem = sc->sc_shmem; 169 int32_t status; 170 171 shmem->channel_status = 0; 172 status = smccc(sc->sc_smc_id, 0, 0, 0); 173 if (status != PSCI_SUCCESS) 174 return SCMI_NOT_SUPPORTED; 175 if ((shmem->channel_status & SCMI_CHANNEL_ERROR)) 176 return SCMI_COMMS_ERROR; 177 if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0) 178 return SCMI_BUSY; 179 return shmem->message_payload[0]; 180 } 181 182 void 183 scmi_attach_proto(struct scmi_softc *sc, int node) 184 { 185 switch (OF_getpropint(node, "reg", -1)) { 186 case SCMI_CLOCK: 187 scmi_attach_clock(sc, node); 188 break; 189 default: 190 break; 191 } 192 } 193 194 /* Clock management. */ 195 196 void scmi_clock_enable(void *, uint32_t *, int); 197 uint32_t scmi_clock_get_frequency(void *, uint32_t *); 198 int scmi_clock_set_frequency(void *, uint32_t *, uint32_t); 199 200 void 201 scmi_attach_clock(struct scmi_softc *sc, int node) 202 { 203 volatile struct scmi_shmem *shmem = sc->sc_shmem; 204 int32_t status; 205 int nclocks; 206 207 scmi_message_header(shmem, SCMI_CLOCK, SCMI_PROTOCOL_ATTRIBUTES); 208 shmem->length = sizeof(uint32_t); 209 status = scmi_command(sc); 210 if (status != SCMI_SUCCESS) 211 return; 212 213 nclocks = shmem->message_payload[1] & 0xffff; 214 if (nclocks == 0) 215 return; 216 217 sc->sc_cd.cd_node = node; 218 sc->sc_cd.cd_cookie = sc; 219 sc->sc_cd.cd_enable = scmi_clock_enable; 220 sc->sc_cd.cd_get_frequency = scmi_clock_get_frequency; 221 sc->sc_cd.cd_set_frequency = scmi_clock_set_frequency; 222 clock_register(&sc->sc_cd); 223 } 224 225 void 226 scmi_clock_enable(void *cookie, uint32_t *cells, int on) 227 { 228 struct scmi_softc *sc = cookie; 229 volatile struct scmi_shmem *shmem = sc->sc_shmem; 230 uint32_t idx = cells[0]; 231 232 scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_CONFIG_SET); 233 shmem->length = 3 * sizeof(uint32_t); 234 shmem->message_payload[0] = idx; 235 shmem->message_payload[1] = on ? SCMI_CLOCK_CONFIG_SET_ENABLE : 0; 236 scmi_command(sc); 237 } 238 239 uint32_t 240 scmi_clock_get_frequency(void *cookie, uint32_t *cells) 241 { 242 struct scmi_softc *sc = cookie; 243 volatile struct scmi_shmem *shmem = sc->sc_shmem; 244 uint32_t idx = cells[0]; 245 int32_t status; 246 247 scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_RATE_GET); 248 shmem->length = 2 * sizeof(uint32_t); 249 shmem->message_payload[0] = idx; 250 status = scmi_command(sc); 251 if (status != SCMI_SUCCESS) 252 return 0; 253 if (shmem->message_payload[2] != 0) 254 return 0; 255 256 return shmem->message_payload[1]; 257 } 258 259 int 260 scmi_clock_set_frequency(void *cookie, uint32_t *cells, uint32_t freq) 261 { 262 struct scmi_softc *sc = cookie; 263 volatile struct scmi_shmem *shmem = sc->sc_shmem; 264 uint32_t idx = cells[0]; 265 int32_t status; 266 267 scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_RATE_SET); 268 shmem->length = 5 * sizeof(uint32_t); 269 shmem->message_payload[0] = 0; 270 shmem->message_payload[1] = idx; 271 shmem->message_payload[2] = freq; 272 shmem->message_payload[3] = 0; 273 status = scmi_command(sc); 274 if (status != SCMI_SUCCESS) 275 return -1; 276 277 return 0; 278 } 279