xref: /freebsd/sys/powerpc/powermac/platform_powermac.c (revision a03411e84728e9b267056fd31c7d1d9d1dc1b01e)
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
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
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
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
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
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
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
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
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
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
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 static void
409 powermac_smp_timebase_sync(platform_t plat, u_long tb, int ap)
410 {
411 	static volatile bool tb_ready;
412 	static volatile int cpu_done;
413 
414 	/*
415 	 * XXX Temporary fallback for platforms we don't know how to freeze.
416 	 *
417 	 * This needs to be replaced with a cpu-to-cpu software sync
418 	 * protocol, because this is not a consistent way to sync timebase.
419 	 */
420 	mttb(tb);
421 	if (freeze_timebase == dummy_timebase)
422 		return;
423 
424 	if (ap) {
425 		/* APs.  Hold off until we get a stable timebase. */
426 		critical_enter();
427 		while (!tb_ready)
428 			atomic_thread_fence_seq_cst();
429 		mttb(tb);
430 		atomic_add_int(&cpu_done, 1);
431 		while (cpu_done < mp_ncpus)
432 			atomic_thread_fence_seq_cst();
433 		critical_exit();
434 	} else {
435 		/* BSP */
436 		critical_enter();
437 		/* Ensure cpu_done is zeroed so we can resync at runtime */
438 		atomic_set_int(&cpu_done, 0);
439 		freeze_timebase(powermac_tb_dev, true);
440 		tb_ready = true;
441 		mttb(tb);
442 		atomic_add_int(&cpu_done, 1);
443 		while (cpu_done < mp_ncpus)
444 			atomic_thread_fence_seq_cst();
445 		freeze_timebase(powermac_tb_dev, false);
446 		/* Reset tb_ready so we can resync at runtime */
447 		tb_ready = false;
448 		critical_exit();
449 	}
450 }
451 
452 /* Fallback freeze. In case no real handler is found in the device tree. */
453 static void
454 dummy_timebase(device_t dev, bool freeze)
455 {
456 	/* Nothing to do here, move along. */
457 }
458 
459 static void
460 powermac_reset(platform_t platform)
461 {
462 	OF_reboot();
463 }
464 
465 #ifndef __powerpc64__
466 void
467 powermac_sleep(platform_t platform)
468 {
469 	/* Only supports MPC745x for now. */
470 	if (!MPC745X_P(mfspr(SPR_PVR) >> 16)) {
471 		printf("sleep only supported for G4 PowerMac hardware.\n");
472 		return;
473 	}
474 
475 	*(unsigned long *)0x80 = 0x100;
476 	mpc745x_sleep();
477 }
478 #endif
479