1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/kernel.h>
36#include <sys/bus.h>
37#include <sys/pcpu.h>
38#include <sys/proc.h>
39#include <sys/smp.h>
40#include <vm/vm.h>
41#include <vm/pmap.h>
42
43#include <machine/altivec.h>	/* For save_vec() */
44#include <machine/bus.h>
45#include <machine/cpu.h>
46#include <machine/fpu.h>	/* For save_fpu() */
47#include <machine/hid.h>
48#include <machine/platformvar.h>
49#include <machine/setjmp.h>
50#include <machine/smp.h>
51#include <machine/spr.h>
52
53#include <dev/ofw/openfirm.h>
54#include <machine/ofw_machdep.h>
55
56#include "platform_if.h"
57
58extern void *ap_pcpu;
59
60static int powermac_probe(platform_t);
61static int powermac_attach(platform_t);
62void powermac_mem_regions(platform_t, struct mem_region *phys, int *physsz,
63    struct mem_region *avail, int *availsz);
64static u_long powermac_timebase_freq(platform_t, struct cpuref *cpuref);
65static int powermac_smp_first_cpu(platform_t, struct cpuref *cpuref);
66static int powermac_smp_next_cpu(platform_t, struct cpuref *cpuref);
67static int powermac_smp_get_bsp(platform_t, struct cpuref *cpuref);
68static int powermac_smp_start_cpu(platform_t, struct pcpu *cpu);
69static void powermac_smp_timebase_sync(platform_t, u_long tb, int ap);
70static void powermac_reset(platform_t);
71#ifndef __powerpc64__
72static void powermac_sleep(platform_t);
73#endif
74
75static platform_method_t powermac_methods[] = {
76	PLATFORMMETHOD(platform_probe, 		powermac_probe),
77	PLATFORMMETHOD(platform_attach,		powermac_attach),
78	PLATFORMMETHOD(platform_mem_regions,	powermac_mem_regions),
79	PLATFORMMETHOD(platform_timebase_freq,	powermac_timebase_freq),
80
81	PLATFORMMETHOD(platform_smp_first_cpu,	powermac_smp_first_cpu),
82	PLATFORMMETHOD(platform_smp_next_cpu,	powermac_smp_next_cpu),
83	PLATFORMMETHOD(platform_smp_get_bsp,	powermac_smp_get_bsp),
84	PLATFORMMETHOD(platform_smp_start_cpu,	powermac_smp_start_cpu),
85	PLATFORMMETHOD(platform_smp_timebase_sync, powermac_smp_timebase_sync),
86
87	PLATFORMMETHOD(platform_reset,		powermac_reset),
88#ifndef __powerpc64__
89	PLATFORMMETHOD(platform_sleep,		powermac_sleep),
90#endif
91
92	PLATFORMMETHOD_END
93};
94
95static platform_def_t powermac_platform = {
96	"powermac",
97	powermac_methods,
98	0
99};
100
101PLATFORM_DEF(powermac_platform);
102
103static int
104powermac_probe(platform_t plat)
105{
106	char compat[255];
107	ssize_t compatlen;
108	char *curstr;
109	phandle_t root;
110
111	root = OF_peer(0);
112	if (root == 0)
113		return (ENXIO);
114
115	compatlen = OF_getprop(root, "compatible", compat, sizeof(compat));
116
117	for (curstr = compat; curstr < compat + compatlen;
118	    curstr += strlen(curstr) + 1) {
119		if (strncmp(curstr, "MacRISC", 7) == 0)
120			return (BUS_PROBE_SPECIFIC);
121	}
122
123	return (ENXIO);
124}
125
126void
127powermac_mem_regions(platform_t plat, struct mem_region *phys, int *physsz,
128    struct mem_region *avail, int *availsz)
129{
130	phandle_t memory;
131	cell_t memoryprop[PHYS_AVAIL_SZ * 2];
132	ssize_t propsize, i, j;
133	int physacells = 1;
134
135	memory = OF_finddevice("/memory");
136	if (memory == -1)
137		memory = OF_finddevice("/memory@0");
138
139	/* "reg" has variable #address-cells, but #size-cells is always 1 */
140	OF_getprop(OF_parent(memory), "#address-cells", &physacells,
141	    sizeof(physacells));
142
143	propsize = OF_getprop(memory, "reg", memoryprop, sizeof(memoryprop));
144	propsize /= sizeof(cell_t);
145	for (i = 0, j = 0; i < propsize; i += physacells+1, j++) {
146		phys[j].mr_start = memoryprop[i];
147		if (physacells == 2) {
148#ifndef __powerpc64__
149			/* On 32-bit PPC, ignore regions starting above 4 GB */
150			if (memoryprop[i] != 0) {
151				j--;
152				continue;
153			}
154#else
155			phys[j].mr_start <<= 32;
156#endif
157			phys[j].mr_start |= memoryprop[i+1];
158		}
159		phys[j].mr_size = memoryprop[i + physacells];
160	}
161	*physsz = j;
162
163	/* "available" always has #address-cells = 1 */
164	propsize = OF_getprop(memory, "available", memoryprop,
165	    sizeof(memoryprop));
166	if (propsize <= 0) {
167		for (i = 0; i < *physsz; i++) {
168			avail[i].mr_start = phys[i].mr_start;
169			avail[i].mr_size = phys[i].mr_size;
170		}
171
172		*availsz = *physsz;
173	} else {
174		propsize /= sizeof(cell_t);
175		for (i = 0, j = 0; i < propsize; i += 2, j++) {
176			avail[j].mr_start = memoryprop[i];
177			avail[j].mr_size = memoryprop[i + 1];
178		}
179
180#ifdef __powerpc64__
181		/* Add in regions above 4 GB to the available list */
182		for (i = 0; i < *physsz; i++) {
183			if (phys[i].mr_start > BUS_SPACE_MAXADDR_32BIT) {
184				avail[j].mr_start = phys[i].mr_start;
185				avail[j].mr_size = phys[i].mr_size;
186				j++;
187			}
188		}
189#endif
190		*availsz = j;
191	}
192}
193
194static int
195powermac_attach(platform_t plat)
196{
197	phandle_t rootnode;
198	char model[32];
199
200	/*
201	 * Quiesce Open Firmware on PowerMac11,2 and 12,1. It is
202	 * necessary there to shut down a background thread doing fan
203	 * management, and is harmful on other machines (it will make OF
204	 * shut off power to various system components it had turned on).
205	 *
206	 * Note: we don't need to worry about which OF module we are
207	 * using since this is called only from very early boot, within
208	 * OF's boot context.
209	 */
210
211	rootnode = OF_finddevice("/");
212	if (OF_getprop(rootnode, "model", model, sizeof(model)) > 0) {
213		if (strcmp(model, "PowerMac11,2") == 0 ||
214		    strcmp(model, "PowerMac12,1") == 0) {
215			ofw_quiesce();
216		}
217	}
218
219	return (0);
220}
221
222static u_long
223powermac_timebase_freq(platform_t plat, struct cpuref *cpuref)
224{
225	phandle_t phandle;
226	int32_t ticks = -1;
227
228	phandle = cpuref->cr_hwref;
229
230	OF_getprop(phandle, "timebase-frequency", &ticks, sizeof(ticks));
231
232	if (ticks <= 0)
233		panic("Unable to determine timebase frequency!");
234
235	return (ticks);
236}
237
238static int
239powermac_smp_fill_cpuref(struct cpuref *cpuref, phandle_t cpu)
240{
241	cell_t cpuid;
242	int res;
243
244	cpuref->cr_hwref = cpu;
245	res = OF_getprop(cpu, "reg", &cpuid, sizeof(cpuid));
246
247	/*
248	 * psim doesn't have a reg property, so assume 0 as for the
249	 * uniprocessor case in the CHRP spec.
250	 */
251	if (res < 0) {
252		cpuid = 0;
253	}
254
255	cpuref->cr_cpuid = cpuid & 0xff;
256	return (0);
257}
258
259static int
260powermac_smp_first_cpu(platform_t plat, struct cpuref *cpuref)
261{
262	char buf[8];
263	phandle_t cpu, dev, root;
264	int res;
265
266	root = OF_peer(0);
267
268	dev = OF_child(root);
269	while (dev != 0) {
270		res = OF_getprop(dev, "name", buf, sizeof(buf));
271		if (res > 0 && strcmp(buf, "cpus") == 0)
272			break;
273		dev = OF_peer(dev);
274	}
275	if (dev == 0) {
276		/*
277		 * psim doesn't have a name property on the /cpus node,
278		 * but it can be found directly
279		 */
280		dev = OF_finddevice("/cpus");
281		if (dev == -1)
282			return (ENOENT);
283	}
284
285	cpu = OF_child(dev);
286
287	while (cpu != 0) {
288		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
289		if (res > 0 && strcmp(buf, "cpu") == 0)
290			break;
291		cpu = OF_peer(cpu);
292	}
293	if (cpu == 0)
294		return (ENOENT);
295
296	return (powermac_smp_fill_cpuref(cpuref, cpu));
297}
298
299static int
300powermac_smp_next_cpu(platform_t plat, struct cpuref *cpuref)
301{
302	char buf[8];
303	phandle_t cpu;
304	int res;
305
306	cpu = OF_peer(cpuref->cr_hwref);
307	while (cpu != 0) {
308		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
309		if (res > 0 && strcmp(buf, "cpu") == 0)
310			break;
311		cpu = OF_peer(cpu);
312	}
313	if (cpu == 0)
314		return (ENOENT);
315
316	return (powermac_smp_fill_cpuref(cpuref, cpu));
317}
318
319static int
320powermac_smp_get_bsp(platform_t plat, struct cpuref *cpuref)
321{
322	ihandle_t inst;
323	phandle_t bsp, chosen;
324	int res;
325
326	chosen = OF_finddevice("/chosen");
327	if (chosen == -1)
328		return (ENXIO);
329
330	res = OF_getprop(chosen, "cpu", &inst, sizeof(inst));
331	if (res < 0)
332		return (ENXIO);
333
334	bsp = OF_instance_to_package(inst);
335	return (powermac_smp_fill_cpuref(cpuref, bsp));
336}
337
338static int
339powermac_smp_start_cpu(platform_t plat, struct pcpu *pc)
340{
341#ifdef SMP
342	phandle_t cpu;
343	volatile uint8_t *rstvec;
344	static volatile uint8_t *rstvec_virtbase = NULL;
345	int res, reset, timeout;
346
347	cpu = pc->pc_hwref;
348	res = OF_getprop(cpu, "soft-reset", &reset, sizeof(reset));
349	if (res < 0) {
350		reset = 0x58;
351
352		switch (pc->pc_cpuid) {
353		case 0:
354			reset += 0x03;
355			break;
356		case 1:
357			reset += 0x04;
358			break;
359		case 2:
360			reset += 0x0f;
361			break;
362		case 3:
363			reset += 0x10;
364			break;
365		default:
366			return (ENXIO);
367		}
368	}
369
370	ap_pcpu = pc;
371
372	if (rstvec_virtbase == NULL)
373		rstvec_virtbase = pmap_mapdev(0x80000000, PAGE_SIZE);
374
375	rstvec = rstvec_virtbase + reset;
376
377	*rstvec = 4;
378	powerpc_sync();
379	(void)(*rstvec);
380	powerpc_sync();
381	DELAY(1);
382	*rstvec = 0;
383	powerpc_sync();
384	(void)(*rstvec);
385	powerpc_sync();
386
387	timeout = 10000;
388	while (!pc->pc_awake && timeout--)
389		DELAY(100);
390
391	return ((pc->pc_awake) ? 0 : EBUSY);
392#else
393	/* No SMP support */
394	return (ENXIO);
395#endif
396}
397
398static void
399powermac_smp_timebase_sync(platform_t plat, u_long tb, int ap)
400{
401
402	mttb(tb);
403}
404
405static void
406powermac_reset(platform_t platform)
407{
408	OF_reboot();
409}
410
411#ifndef __powerpc64__
412void
413powermac_sleep(platform_t platform)
414{
415	/* Only supports MPC745x for now. */
416	if (!MPC745X_P(mfspr(SPR_PVR) >> 16)) {
417		printf("sleep only supported for G4 PowerMac hardware.\n");
418		return;
419	}
420
421	*(unsigned long *)0x80 = 0x100;
422	mpc745x_sleep();
423}
424#endif
425