xref: /openbsd-src/sys/arch/powerpc64/dev/opal.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: opal.c,v 1.10 2020/09/23 03:03:11 gkoehler Exp $	*/
2 /*
3  * Copyright (c) 2020 Mark Kettenis <kettenis@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/device.h>
20 #include <sys/malloc.h>
21 #include <sys/sysctl.h>
22 #include <sys/systm.h>
23 
24 #include <machine/bus.h>
25 #include <machine/cpu.h>
26 #include <machine/fdt.h>
27 #include <machine/opal.h>
28 
29 #include <dev/clock_subr.h>
30 
31 #include <dev/ofw/openfirm.h>
32 #include <dev/ofw/fdt.h>
33 
34 #define OPAL_NUM_HANDLERS	4
35 
36 struct opal_intr {
37 	struct opal_softc	*oi_sc;
38 	uint32_t		oi_isn;
39 };
40 
41 struct intrhand {
42 	uint64_t		ih_events;
43 	int			(*ih_func)(void *);
44 	void			*ih_arg;
45 };
46 
47 struct opal_softc {
48 	struct device		sc_dev;
49 
50 	struct opal_intr	*sc_intr;
51 	int			sc_nintr;
52 
53 	struct intrhand		*sc_handler[OPAL_NUM_HANDLERS];
54 
55 	struct todr_chip_handle	sc_todr;
56 
57 	int			*sc_pstate;
58 	int			*sc_freq;
59 	int			sc_npstate;
60 };
61 
62 struct opal_softc *opal_sc;
63 
64 int	opal_match(struct device *, void *, void *);
65 void	opal_attach(struct device *, struct device *, void *);
66 
67 struct cfattach	opal_ca = {
68 	sizeof (struct opal_softc), opal_match, opal_attach
69 };
70 
71 struct cfdriver opal_cd = {
72 	NULL, "opal", DV_DULL
73 };
74 
75 void	opal_attach_deferred(struct device *);
76 void	opal_attach_node(struct opal_softc *, int);
77 int	opal_gettime(struct todr_chip_handle *, struct timeval *);
78 int	opal_settime(struct todr_chip_handle *, struct timeval *);
79 
80 extern int perflevel;
81 
82 void	opalpm_init(struct opal_softc *, int);
83 int	opalpm_find_index(struct opal_softc *);
84 int	opalpm_cpuspeed(int *);
85 void	opalpm_setperf(int);
86 
87 int
88 opal_match(struct device *parent, void *match, void *aux)
89 {
90 	struct fdt_attach_args *faa = aux;
91 
92 	return OF_is_compatible(faa->fa_node, "ibm,opal-v3");
93 }
94 
95 void
96 opal_attach(struct device *parent, struct device *self, void *aux)
97 {
98 	struct opal_softc *sc = (struct opal_softc *)self;
99 	struct fdt_attach_args *faa = aux;
100 	uint32_t *interrupts;
101 	int len, i;
102 	int node;
103 
104 	node = OF_getnodebyname(faa->fa_node, "firmware");
105 	if (node) {
106 		char version[64];
107 
108 		version[0] = 0;
109 		OF_getprop(node, "version", version, sizeof(version));
110 		version[sizeof(version) - 1] = 0;
111 		printf(": %s", version);
112 	}
113 
114 	len = OF_getproplen(faa->fa_node, "opal-interrupts");
115 	if (len > 0 && (len % sizeof(uint32_t)) != 0) {
116 		printf(": can't parse interrupts\n");
117 		return;
118 	}
119 
120 	printf("\n");
121 
122 	/* There can be only one. */
123 	KASSERT(opal_sc == NULL);
124 	opal_sc = sc;
125 
126 	if (len > 0) {
127 		interrupts = malloc(len, M_TEMP, M_WAITOK);
128 		OF_getpropintarray(faa->fa_node, "opal-interrupts",
129 		    interrupts, len);
130 		sc->sc_nintr = len / sizeof(uint32_t);
131 
132 		sc->sc_intr = mallocarray(sc->sc_nintr,
133 		    sizeof(struct opal_intr), M_DEVBUF, M_WAITOK);
134 
135 		for (i = 0; i < sc->sc_nintr; i++) {
136 			sc->sc_intr[i].oi_sc = sc;
137 			sc->sc_intr[i].oi_isn = interrupts[i];
138 		}
139 
140 		free(interrupts, M_TEMP, len);
141 
142 		config_defer(self, opal_attach_deferred);
143 	}
144 
145 	sc->sc_todr.todr_gettime = opal_gettime;
146 	sc->sc_todr.todr_settime = opal_settime;
147 	todr_attach(&sc->sc_todr);
148 
149 	opalpm_init(sc, OF_getnodebyname(faa->fa_node, "power-mgt"));
150 
151 	node = OF_getnodebyname(faa->fa_node, "consoles");
152 	if (node) {
153 		for (node = OF_child(node); node; node = OF_peer(node))
154 			opal_attach_node(sc, node);
155 	}
156 
157 	node = OF_getnodebyname(faa->fa_node, "sensors");
158 	if (node) {
159 		for (node = OF_child(node); node; node = OF_peer(node))
160 			opal_attach_node(sc, node);
161 	}
162 }
163 
164 int
165 opal_intr(void *arg)
166 {
167 	struct opal_intr *oi = arg;
168 	struct opal_softc *sc = oi->oi_sc;
169 	uint64_t events = 0;
170 	int i;
171 
172 	opal_handle_interrupt(oi->oi_isn, opal_phys(&events));
173 
174 	/* Handle registered events. */
175 	for (i = 0; i < OPAL_NUM_HANDLERS; i++) {
176 		struct intrhand *ih = sc->sc_handler[i];
177 
178 		if (ih == NULL)
179 			continue;
180 		if ((events & ih->ih_events) == 0)
181 			continue;
182 
183 		ih->ih_func(ih->ih_arg);
184 	}
185 
186 	return 1;
187 }
188 
189 void *
190 opal_intr_establish(uint64_t events, int level, int (*func)(void *), void *arg)
191 {
192 	struct opal_softc *sc = opal_sc;
193 	struct intrhand *ih;
194 	int i;
195 
196 	for (i = 0; i < OPAL_NUM_HANDLERS; i++) {
197 		if (sc->sc_handler[i] == NULL)
198 			break;
199 	}
200 	if (i == OPAL_NUM_HANDLERS)
201 		return NULL;
202 
203 	ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK);
204 	ih->ih_events = events;
205 	ih->ih_func = func;
206 	ih->ih_arg = arg;
207 	sc->sc_handler[i] = ih;
208 
209 	return ih;
210 }
211 
212 void
213 opal_attach_deferred(struct device *self)
214 {
215 	struct opal_softc *sc = (struct opal_softc *)self;
216 	int i;
217 
218 	for (i = 0; i < sc->sc_nintr; i++) {
219 		intr_establish(sc->sc_intr[i].oi_isn, IST_LEVEL, IPL_TTY,
220 		    NULL, opal_intr, &sc->sc_intr[i], sc->sc_dev.dv_xname);
221 	}
222 }
223 
224 int
225 opal_print(void *aux, const char *pnp)
226 {
227 	struct fdt_attach_args *faa = aux;
228 	char name[32];
229 
230 	if (!pnp)
231 		return (QUIET);
232 
233 	if (OF_getprop(faa->fa_node, "name", name, sizeof(name)) > 0) {
234 		name[sizeof(name) - 1] = 0;
235 		printf("\"%s\"", name);
236 	} else
237 		printf("node %u", faa->fa_node);
238 
239 	printf(" at %s", pnp);
240 
241 	return (UNCONF);
242 }
243 
244 void
245 opal_attach_node(struct opal_softc *sc, int node)
246 {
247 	struct fdt_attach_args faa;
248 	char buf[32];
249 
250 	if (OF_getproplen(node, "compatible") <= 0)
251 		return;
252 
253 	if (OF_getprop(node, "status", buf, sizeof(buf)) > 0 &&
254 	    strcmp(buf, "disabled") == 0)
255 		return;
256 
257 	memset(&faa, 0, sizeof(faa));
258 	faa.fa_name = "";
259 	faa.fa_node = node;
260 
261 	config_found(&sc->sc_dev, &faa, opal_print);
262 }
263 
264 int
265 opal_gettime(struct todr_chip_handle *ch, struct timeval *tv)
266 {
267 	struct clock_ymdhms dt;
268 	uint64_t time;
269 	uint32_t date;
270 	int64_t error;
271 
272 	do {
273 		error = opal_rtc_read(opal_phys(&date), opal_phys(&time));
274 		if (error == OPAL_BUSY_EVENT)
275 			opal_poll_events(NULL);
276 	} while (error == OPAL_BUSY_EVENT);
277 
278 	if (error != OPAL_SUCCESS)
279 		return EIO;
280 
281 	dt.dt_sec = FROMBCD((time >> 40) & 0xff);
282 	dt.dt_min = FROMBCD((time >> 48) & 0xff);
283 	dt.dt_hour = FROMBCD((time >> 56) & 0xff);
284 	dt.dt_day = FROMBCD((date >> 0) & 0xff);
285 	dt.dt_mon = FROMBCD((date >> 8) & 0xff);
286 	dt.dt_year = FROMBCD((date >> 16) & 0xff);
287 	dt.dt_year += 100 * FROMBCD((date >> 24) & 0xff);
288 
289 	tv->tv_sec = clock_ymdhms_to_secs(&dt);
290 	tv->tv_usec = 0;
291 
292 	return 0;
293 }
294 
295 int
296 opal_settime(struct todr_chip_handle *ch, struct timeval *tv)
297 {
298 	struct clock_ymdhms dt;
299 	uint64_t time = 0;
300 	uint32_t date = 0;
301 	int64_t error;
302 
303 	clock_secs_to_ymdhms(tv->tv_sec, &dt);
304 
305 	time |= (uint64_t)TOBCD(dt.dt_sec) << 40;
306 	time |= (uint64_t)TOBCD(dt.dt_min) << 48;
307 	time |= (uint64_t)TOBCD(dt.dt_hour) << 56;
308 	date |= (uint32_t)TOBCD(dt.dt_day);
309 	date |= (uint32_t)TOBCD(dt.dt_mon) << 8;
310 	date |= (uint32_t)TOBCD(dt.dt_year) << 16;
311 	date |= (uint32_t)TOBCD(dt.dt_year / 100) << 24;
312 
313 	do {
314 		error = opal_rtc_write(date, time);
315 		if (error == OPAL_BUSY_EVENT)
316 			opal_poll_events(NULL);
317 	} while (error == OPAL_BUSY_EVENT);
318 
319 	if (error != OPAL_SUCCESS)
320 		return EIO;
321 
322 	return 0;
323 }
324 
325 void
326 opalpm_init(struct opal_softc *sc, int node)
327 {
328 	int i, len;
329 
330 	if (!node) {
331 		printf("%s: no power-mgt\n", sc->sc_dev.dv_xname);
332 		return;
333 	}
334 	len = OF_getproplen(node, "ibm,pstate-ids");
335 	if (len <= 0 || len % sizeof(int) != 0 ||
336 	    len != OF_getproplen(node, "ibm,pstate-frequencies-mhz")) {
337 		printf("%s: can't parse power-mgt\n", sc->sc_dev.dv_xname);
338 		return;
339 	}
340 	sc->sc_pstate = malloc(len, M_DEVBUF, M_WAITOK);
341 	sc->sc_freq = malloc(len, M_DEVBUF, M_WAITOK);
342 	sc->sc_npstate = len / sizeof(int);
343 	OF_getprop(node, "ibm,pstate-ids", sc->sc_pstate, len);
344 	OF_getprop(node, "ibm,pstate-frequencies-mhz", sc->sc_freq, len);
345 
346 	if ((i = opalpm_find_index(sc)) != -1)
347 		perflevel = (sc->sc_npstate - 1 - i) * 100 / sc->sc_npstate;
348 	cpu_cpuspeed = opalpm_cpuspeed;
349 #ifndef MULTIPROCESSOR
350 	cpu_setperf = opalpm_setperf;
351 #else
352 	ul_setperf = opalpm_setperf;
353 	cpu_setperf = mp_setperf;
354 #endif
355 }
356 
357 int
358 opalpm_find_index(struct opal_softc *sc)
359 {
360 	int i, pstate;
361 
362 	/*
363 	 * POWER9 23.5.8.3 Power Management Status Register (PMSR)
364 	 * 8:15 Local Actual Pstate
365 	 */
366 	pstate = (mfpmsr() >> 48) & 0xff;
367 	for (i = 0; i < sc->sc_npstate; i++) {
368 		if (sc->sc_pstate[i] == pstate)
369 			return i;
370 	}
371 	return -1;
372 }
373 
374 int
375 opalpm_cpuspeed(int *freq)
376 {
377 	struct opal_softc *sc = opal_sc;
378 	int i;
379 
380 	if ((i = opalpm_find_index(sc)) == -1)
381 		return 1;
382 	*freq = sc->sc_freq[i];
383 	return 0;
384 }
385 
386 void
387 opalpm_setperf(int level)
388 {
389 	struct opal_softc *sc = opal_sc;
390 	uint64_t pstate;
391 	int i;
392 
393 	/*
394 	 * Assume that "ibm,pstate-frequencies-mhz" is sorted from
395 	 * fastest to slowest.
396 	 */
397 	i = (100 - level) * (sc->sc_npstate - 1) / 100;
398 	pstate = sc->sc_pstate[i];
399 
400 	/*
401 	 * POWER9 23.5.8.1 Power Management Control Register (PMCR)
402 	 *  0:7  Upper Pstate request
403 	 *  8:15 Lower Pstate request
404 	 * 60:63 Version
405 	 */
406 	mtpmcr((pstate << 56) | (pstate << 48) | 0);
407 }
408