xref: /freebsd/sys/powerpc/powermac/platform_powermac.c (revision 422c8719eab2b8a01b49f748a88dd372db25f888)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2008 Marcel Moolenaar
5  * Copyright (c) 2009 Nathan Whitehorn
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/param.h>
31 #include <sys/systm.h>
32 #include <sys/kernel.h>
33 #include <sys/bus.h>
34 #include <sys/pcpu.h>
35 #include <sys/proc.h>
36 #include <sys/smp.h>
37 #include <vm/vm.h>
38 #include <vm/pmap.h>
39 
40 #include <machine/altivec.h>	/* For save_vec() */
41 #include <machine/bus.h>
42 #include <machine/cpu.h>
43 #include <machine/fpu.h>	/* For save_fpu() */
44 #include <machine/hid.h>
45 #include <machine/platformvar.h>
46 #include <machine/setjmp.h>
47 #include <machine/smp.h>
48 #include <machine/spr.h>
49 
50 #include <dev/ofw/openfirm.h>
51 #include <machine/ofw_machdep.h>
52 
53 #include <powerpc/powermac/platform_powermac.h>
54 
55 #include "platform_if.h"
56 
57 extern volatile void *ap_pcpu;
58 
59 static void dummy_timebase(device_t, bool);
60 static device_t powermac_tb_dev;
61 static void (*freeze_timebase)(device_t, bool) = dummy_timebase;
62 
63 static int powermac_probe(platform_t);
64 static int powermac_attach(platform_t);
65 void powermac_mem_regions(platform_t, struct mem_region *phys, int *physsz,
66     struct mem_region *avail, int *availsz);
67 static u_long powermac_timebase_freq(platform_t, struct cpuref *cpuref);
68 static int powermac_smp_first_cpu(platform_t, struct cpuref *cpuref);
69 static int powermac_smp_next_cpu(platform_t, struct cpuref *cpuref);
70 static int powermac_smp_get_bsp(platform_t, struct cpuref *cpuref);
71 static int powermac_smp_start_cpu(platform_t, struct pcpu *cpu);
72 static void powermac_smp_timebase_sync(platform_t, u_long tb, int ap);
73 static void powermac_reset(platform_t);
74 #ifndef __powerpc64__
75 static void powermac_sleep(platform_t);
76 #endif
77 
78 static platform_method_t powermac_methods[] = {
79 	PLATFORMMETHOD(platform_probe, 		powermac_probe),
80 	PLATFORMMETHOD(platform_attach,		powermac_attach),
81 	PLATFORMMETHOD(platform_mem_regions,	powermac_mem_regions),
82 	PLATFORMMETHOD(platform_timebase_freq,	powermac_timebase_freq),
83 
84 	PLATFORMMETHOD(platform_smp_first_cpu,	powermac_smp_first_cpu),
85 	PLATFORMMETHOD(platform_smp_next_cpu,	powermac_smp_next_cpu),
86 	PLATFORMMETHOD(platform_smp_get_bsp,	powermac_smp_get_bsp),
87 	PLATFORMMETHOD(platform_smp_start_cpu,	powermac_smp_start_cpu),
88 	PLATFORMMETHOD(platform_smp_timebase_sync, powermac_smp_timebase_sync),
89 
90 	PLATFORMMETHOD(platform_reset,		powermac_reset),
91 #ifndef __powerpc64__
92 	PLATFORMMETHOD(platform_sleep,		powermac_sleep),
93 #endif
94 
95 	PLATFORMMETHOD_END
96 };
97 
98 static platform_def_t powermac_platform = {
99 	"powermac",
100 	powermac_methods,
101 	0
102 };
103 
104 PLATFORM_DEF(powermac_platform);
105 
106 static int
powermac_probe(platform_t plat)107 powermac_probe(platform_t plat)
108 {
109 	char compat[255];
110 	ssize_t compatlen;
111 	char *curstr;
112 	phandle_t root;
113 
114 	root = OF_peer(0);
115 	if (root == 0)
116 		return (ENXIO);
117 
118 	compatlen = OF_getprop(root, "compatible", compat, sizeof(compat));
119 
120 	for (curstr = compat; curstr < compat + compatlen;
121 	    curstr += strlen(curstr) + 1) {
122 		if (strncmp(curstr, "MacRISC", 7) == 0)
123 			return (BUS_PROBE_SPECIFIC);
124 	}
125 
126 	return (ENXIO);
127 }
128 
129 void
powermac_mem_regions(platform_t plat,struct mem_region * phys,int * physsz,struct mem_region * avail,int * availsz)130 powermac_mem_regions(platform_t plat, struct mem_region *phys, int *physsz,
131     struct mem_region *avail, int *availsz)
132 {
133 	phandle_t memory;
134 	cell_t memoryprop[PHYS_AVAIL_SZ * 2];
135 	ssize_t propsize, i, j;
136 	int physacells = 1;
137 
138 	memory = OF_finddevice("/memory");
139 	if (memory == -1)
140 		memory = OF_finddevice("/memory@0");
141 
142 	/* "reg" has variable #address-cells, but #size-cells is always 1 */
143 	OF_getprop(OF_parent(memory), "#address-cells", &physacells,
144 	    sizeof(physacells));
145 
146 	propsize = OF_getprop(memory, "reg", memoryprop, sizeof(memoryprop));
147 	propsize /= sizeof(cell_t);
148 	for (i = 0, j = 0; i < propsize; i += physacells+1, j++) {
149 		phys[j].mr_start = memoryprop[i];
150 		if (physacells == 2) {
151 #ifndef __powerpc64__
152 			/* On 32-bit PPC, ignore regions starting above 4 GB */
153 			if (memoryprop[i] != 0) {
154 				j--;
155 				continue;
156 			}
157 #else
158 			phys[j].mr_start <<= 32;
159 #endif
160 			phys[j].mr_start |= memoryprop[i+1];
161 		}
162 		phys[j].mr_size = memoryprop[i + physacells];
163 	}
164 	*physsz = j;
165 
166 	/* "available" always has #address-cells = 1 */
167 	propsize = OF_getprop(memory, "available", memoryprop,
168 	    sizeof(memoryprop));
169 	if (propsize <= 0) {
170 		for (i = 0; i < *physsz; i++) {
171 			avail[i].mr_start = phys[i].mr_start;
172 			avail[i].mr_size = phys[i].mr_size;
173 		}
174 
175 		*availsz = *physsz;
176 	} else {
177 		propsize /= sizeof(cell_t);
178 		for (i = 0, j = 0; i < propsize; i += 2, j++) {
179 			avail[j].mr_start = memoryprop[i];
180 			avail[j].mr_size = memoryprop[i + 1];
181 		}
182 
183 #ifdef __powerpc64__
184 		/* Add in regions above 4 GB to the available list */
185 		for (i = 0; i < *physsz; i++) {
186 			if (phys[i].mr_start > BUS_SPACE_MAXADDR_32BIT) {
187 				avail[j].mr_start = phys[i].mr_start;
188 				avail[j].mr_size = phys[i].mr_size;
189 				j++;
190 			}
191 		}
192 #endif
193 		*availsz = j;
194 	}
195 }
196 
197 static int
powermac_attach(platform_t plat)198 powermac_attach(platform_t plat)
199 {
200 	phandle_t rootnode;
201 	char model[32];
202 
203 	/*
204 	 * Quiesce Open Firmware on PowerMac11,2 and 12,1. It is
205 	 * necessary there to shut down a background thread doing fan
206 	 * management, and is harmful on other machines (it will make OF
207 	 * shut off power to various system components it had turned on).
208 	 *
209 	 * Note: we don't need to worry about which OF module we are
210 	 * using since this is called only from very early boot, within
211 	 * OF's boot context.
212 	 */
213 
214 	rootnode = OF_finddevice("/");
215 	if (OF_getprop(rootnode, "model", model, sizeof(model)) > 0) {
216 		if (strcmp(model, "PowerMac11,2") == 0 ||
217 		    strcmp(model, "PowerMac12,1") == 0) {
218 			ofw_quiesce();
219 		}
220 	}
221 
222 	return (0);
223 }
224 
225 static u_long
powermac_timebase_freq(platform_t plat,struct cpuref * cpuref)226 powermac_timebase_freq(platform_t plat, struct cpuref *cpuref)
227 {
228 	phandle_t phandle;
229 	int32_t ticks = -1;
230 
231 	phandle = cpuref->cr_hwref;
232 
233 	OF_getprop(phandle, "timebase-frequency", &ticks, sizeof(ticks));
234 
235 	if (ticks <= 0)
236 		panic("Unable to determine timebase frequency!");
237 
238 	return (ticks);
239 }
240 
241 static int
powermac_smp_fill_cpuref(struct cpuref * cpuref,phandle_t cpu)242 powermac_smp_fill_cpuref(struct cpuref *cpuref, phandle_t cpu)
243 {
244 	cell_t cpuid;
245 	int res;
246 
247 	cpuref->cr_hwref = cpu;
248 	res = OF_getprop(cpu, "reg", &cpuid, sizeof(cpuid));
249 
250 	/*
251 	 * psim doesn't have a reg property, so assume 0 as for the
252 	 * uniprocessor case in the CHRP spec.
253 	 */
254 	if (res < 0) {
255 		cpuid = 0;
256 	}
257 
258 	cpuref->cr_cpuid = cpuid & 0xff;
259 	return (0);
260 }
261 
262 static int
powermac_smp_first_cpu(platform_t plat,struct cpuref * cpuref)263 powermac_smp_first_cpu(platform_t plat, struct cpuref *cpuref)
264 {
265 	char buf[8];
266 	phandle_t cpu, dev, root;
267 	int res;
268 
269 	root = OF_peer(0);
270 
271 	dev = OF_child(root);
272 	while (dev != 0) {
273 		res = OF_getprop(dev, "name", buf, sizeof(buf));
274 		if (res > 0 && strcmp(buf, "cpus") == 0)
275 			break;
276 		dev = OF_peer(dev);
277 	}
278 	if (dev == 0) {
279 		/*
280 		 * psim doesn't have a name property on the /cpus node,
281 		 * but it can be found directly
282 		 */
283 		dev = OF_finddevice("/cpus");
284 		if (dev == -1)
285 			return (ENOENT);
286 	}
287 
288 	cpu = OF_child(dev);
289 
290 	while (cpu != 0) {
291 		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
292 		if (res > 0 && strcmp(buf, "cpu") == 0)
293 			break;
294 		cpu = OF_peer(cpu);
295 	}
296 	if (cpu == 0)
297 		return (ENOENT);
298 
299 	return (powermac_smp_fill_cpuref(cpuref, cpu));
300 }
301 
302 static int
powermac_smp_next_cpu(platform_t plat,struct cpuref * cpuref)303 powermac_smp_next_cpu(platform_t plat, struct cpuref *cpuref)
304 {
305 	char buf[8];
306 	phandle_t cpu;
307 	int res;
308 
309 	cpu = OF_peer(cpuref->cr_hwref);
310 	while (cpu != 0) {
311 		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
312 		if (res > 0 && strcmp(buf, "cpu") == 0)
313 			break;
314 		cpu = OF_peer(cpu);
315 	}
316 	if (cpu == 0)
317 		return (ENOENT);
318 
319 	return (powermac_smp_fill_cpuref(cpuref, cpu));
320 }
321 
322 static int
powermac_smp_get_bsp(platform_t plat,struct cpuref * cpuref)323 powermac_smp_get_bsp(platform_t plat, struct cpuref *cpuref)
324 {
325 	ihandle_t inst;
326 	phandle_t bsp, chosen;
327 	int res;
328 
329 	chosen = OF_finddevice("/chosen");
330 	if (chosen == -1)
331 		return (ENXIO);
332 
333 	res = OF_getprop(chosen, "cpu", &inst, sizeof(inst));
334 	if (res < 0)
335 		return (ENXIO);
336 
337 	bsp = OF_instance_to_package(inst);
338 	return (powermac_smp_fill_cpuref(cpuref, bsp));
339 }
340 
341 static int
powermac_smp_start_cpu(platform_t plat,struct pcpu * pc)342 powermac_smp_start_cpu(platform_t plat, struct pcpu *pc)
343 {
344 #ifdef SMP
345 	phandle_t cpu;
346 	volatile uint8_t *rstvec;
347 	static volatile uint8_t *rstvec_virtbase = NULL;
348 	int res, reset, timeout;
349 
350 	cpu = pc->pc_hwref;
351 	res = OF_getprop(cpu, "soft-reset", &reset, sizeof(reset));
352 	if (res < 0) {
353 		reset = 0x58;
354 
355 		switch (pc->pc_cpuid) {
356 		case 0:
357 			reset += 0x03;
358 			break;
359 		case 1:
360 			reset += 0x04;
361 			break;
362 		case 2:
363 			reset += 0x0f;
364 			break;
365 		case 3:
366 			reset += 0x10;
367 			break;
368 		default:
369 			return (ENXIO);
370 		}
371 	}
372 
373 	ap_pcpu = pc;
374 
375 	if (rstvec_virtbase == NULL)
376 		rstvec_virtbase = pmap_mapdev(0x80000000, PAGE_SIZE);
377 
378 	rstvec = rstvec_virtbase + reset;
379 
380 	*rstvec = 4;
381 	powerpc_sync();
382 	(void)(*rstvec);
383 	powerpc_sync();
384 	DELAY(1);
385 	*rstvec = 0;
386 	powerpc_sync();
387 	(void)(*rstvec);
388 	powerpc_sync();
389 
390 	timeout = 10000;
391 	while (!pc->pc_awake && timeout--)
392 		DELAY(100);
393 
394 	return ((pc->pc_awake) ? 0 : EBUSY);
395 #else
396 	/* No SMP support */
397 	return (ENXIO);
398 #endif
399 }
400 
401 void
powermac_register_timebase(device_t dev,powermac_tb_disable_t cb)402 powermac_register_timebase(device_t dev, powermac_tb_disable_t cb)
403 {
404 	powermac_tb_dev = dev;
405 	freeze_timebase = cb;
406 }
407 
408 /**
409  * @brief Implement a default platform AP/BSP SMP timebase synchronisation
410  *
411  * Some powermac platforms don't have a freeze/unfreeze method.
412  * Here just try our best to force synchronisation.
413  */
414 static void
powermac_smp_timebase_sync_fallback(platform_t plat,u_long tb,int ap)415 powermac_smp_timebase_sync_fallback(platform_t plat, u_long tb, int ap)
416 {
417 	static volatile bool tb_ready = false;
418 	static volatile int cpu_done;
419 
420 	if (bootverbose)
421 		printf("[%d] %s: called, AP tb=0x%lx tb=0x%lx\n",
422 		    ap, __func__, tb, mftb());
423 
424 	/* Do initial timebase sync */
425 	mttb(tb);
426 
427 	if (ap) {
428 		/*
429 		 * APs - wait until the BSP signals its ready to sync,
430 		 * then wait for all CPUs to be ready.
431 		 */
432 		critical_enter();
433 		while (!tb_ready)
434 			atomic_thread_fence_seq_cst();
435 		atomic_add_int(&cpu_done, 1);
436 		do {
437 			atomic_thread_fence_seq_cst();
438 		} while (cpu_done < mp_ncpus);
439 		mttb(tb);
440 		critical_exit();
441 	} else {
442 		/*
443 		 * BSP - signify that the timebase sync is about to start,
444 		 * then wait for other CPUs to be ready.
445 		 */
446 		critical_enter();
447 		/* Ensure cpu_done is zeroed so we can resync at runtime */
448 		atomic_store_int(&cpu_done, 0);
449 		tb_ready = true;
450 		atomic_add_int(&cpu_done, 1);
451 		do {
452 			atomic_thread_fence_seq_cst();
453 		} while (cpu_done < mp_ncpus);
454 		mttb(tb);
455 		/* Reset tb_ready so we can resync at runtime */
456 		tb_ready = false;
457 		critical_exit();
458 	}
459 	if (bootverbose)
460 		printf("[%d] %s: finished; AP tb=0x%lx called tb=0x%lx\n",
461 		    ap, __func__, tb, mftb());
462 }
463 
464 /**
465  * @brief Implement freeze/unfreeze AP/BSP SMP timebase synchronisation
466  *
467  * This implements SMP timebase synchronisation for hardware that
468  * implements freezing a shared timebase clock source.
469  *
470  * The BSP will freeze the timebase and signal the APs to program their
471  * local timebase with the shared timebase value.  The BSP will then
472  * unfreeze the timebase clock, allowing all CPUs to march forward
473  * from the same base timebase value.
474  */
475 static void
powermac_smp_timebase_sync_freeze(platform_t plat,u_long tb,int ap)476 powermac_smp_timebase_sync_freeze(platform_t plat, u_long tb, int ap)
477 {
478 	static volatile bool tb_ready = false;
479 	static volatile int cpu_done;
480 
481 	if (bootverbose)
482 		printf("[%d] %s: called, AP tb=0x%lx tb=0x%lx\n",
483 		    ap, __func__, tb, mftb());
484 
485 	/*
486 	 * This needs to be replaced with a cpu-to-cpu software sync
487 	 * protocol, because this is not a consistent way to sync timebase.
488 	 */
489 	mttb(tb);
490 
491 	if (ap) {
492 		/* APs.  Hold off until we get a stable timebase. */
493 		critical_enter();
494 		while (!tb_ready)
495 			atomic_thread_fence_seq_cst();
496 		mttb(tb);
497 		atomic_add_int(&cpu_done, 1);
498 		do {
499 			atomic_thread_fence_seq_cst();
500 		} while (cpu_done < mp_ncpus);
501 		critical_exit();
502 	} else {
503 		/* BSP */
504 		critical_enter();
505 		/* Ensure cpu_done is zeroed so we can resync at runtime */
506 		atomic_store_int(&cpu_done, 0);
507 		freeze_timebase(powermac_tb_dev, true);
508 		tb_ready = true;
509 		mttb(tb);
510 		atomic_add_int(&cpu_done, 1);
511 		do {
512 			atomic_thread_fence_seq_cst();
513 		} while (cpu_done < mp_ncpus);
514 		freeze_timebase(powermac_tb_dev, false);
515 		/* Reset tb_ready so we can resync at runtime */
516 		tb_ready = false;
517 		critical_exit();
518 	}
519 	if (bootverbose)
520 		printf("[%d] %s: finished; AP tb=0x%lx called tb=0x%lx\n",
521 		    ap, __func__, tb, mftb());
522 }
523 
524 static void
powermac_smp_timebase_sync(platform_t plat,u_long tb,int ap)525 powermac_smp_timebase_sync(platform_t plat, u_long tb, int ap)
526 {
527 	if (freeze_timebase == dummy_timebase)
528 		powermac_smp_timebase_sync_fallback(plat, tb, ap);
529 	else
530 		powermac_smp_timebase_sync_freeze(plat, tb, ap);
531 }
532 
533 /* Fallback freeze. In case no real handler is found in the device tree. */
534 static void
dummy_timebase(device_t dev,bool freeze)535 dummy_timebase(device_t dev, bool freeze)
536 {
537 	/* Nothing to do here, move along. */
538 }
539 
540 static void
powermac_reset(platform_t platform)541 powermac_reset(platform_t platform)
542 {
543 	OF_reboot();
544 }
545 
546 #ifndef __powerpc64__
547 void
powermac_sleep(platform_t platform)548 powermac_sleep(platform_t platform)
549 {
550 	/* Only supports MPC745x for now. */
551 	if (!MPC745X_P(mfspr(SPR_PVR) >> 16)) {
552 		printf("sleep only supported for G4 PowerMac hardware.\n");
553 		return;
554 	}
555 
556 	*(unsigned long *)0x80 = 0x100;
557 	mpc745x_sleep();
558 }
559 #endif
560