xref: /openbsd/sys/dev/fdt/scmi.c (revision 4bdff4be)
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, &reg)) {
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