162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * OLPC-specific OFW device tree support code.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Paul Mackerras	August 1996.
662306a36Sopenharmony_ci * Copyright (C) 1996-2005 Paul Mackerras.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci *  Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
962306a36Sopenharmony_ci *    {engebret|bergner}@us.ibm.com
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci *  Adapted for sparc by David S. Miller davem@davemloft.net
1262306a36Sopenharmony_ci *  Adapted for x86/OLPC by Andres Salomon <dilinger@queued.net>
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/kernel.h>
1662306a36Sopenharmony_ci#include <linux/memblock.h>
1762306a36Sopenharmony_ci#include <linux/of.h>
1862306a36Sopenharmony_ci#include <linux/of_pdt.h>
1962306a36Sopenharmony_ci#include <asm/olpc.h>
2062306a36Sopenharmony_ci#include <asm/olpc_ofw.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic phandle __init olpc_dt_getsibling(phandle node)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	const void *args[] = { (void *)node };
2562306a36Sopenharmony_ci	void *res[] = { &node };
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if ((s32)node == -1)
2862306a36Sopenharmony_ci		return 0;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	if (olpc_ofw("peer", args, res) || (s32)node == -1)
3162306a36Sopenharmony_ci		return 0;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	return node;
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic phandle __init olpc_dt_getchild(phandle node)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	const void *args[] = { (void *)node };
3962306a36Sopenharmony_ci	void *res[] = { &node };
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if ((s32)node == -1)
4262306a36Sopenharmony_ci		return 0;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	if (olpc_ofw("child", args, res) || (s32)node == -1) {
4562306a36Sopenharmony_ci		pr_err("PROM: %s: fetching child failed!\n", __func__);
4662306a36Sopenharmony_ci		return 0;
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	return node;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int __init olpc_dt_getproplen(phandle node, const char *prop)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	const void *args[] = { (void *)node, prop };
5562306a36Sopenharmony_ci	int len;
5662306a36Sopenharmony_ci	void *res[] = { &len };
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if ((s32)node == -1)
5962306a36Sopenharmony_ci		return -1;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	if (olpc_ofw("getproplen", args, res)) {
6262306a36Sopenharmony_ci		pr_err("PROM: %s: getproplen failed!\n", __func__);
6362306a36Sopenharmony_ci		return -1;
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return len;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic int __init olpc_dt_getproperty(phandle node, const char *prop,
7062306a36Sopenharmony_ci		char *buf, int bufsize)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	int plen;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	plen = olpc_dt_getproplen(node, prop);
7562306a36Sopenharmony_ci	if (plen > bufsize || plen < 1) {
7662306a36Sopenharmony_ci		return -1;
7762306a36Sopenharmony_ci	} else {
7862306a36Sopenharmony_ci		const void *args[] = { (void *)node, prop, buf, (void *)plen };
7962306a36Sopenharmony_ci		void *res[] = { &plen };
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci		if (olpc_ofw("getprop", args, res)) {
8262306a36Sopenharmony_ci			pr_err("PROM: %s: getprop failed!\n", __func__);
8362306a36Sopenharmony_ci			return -1;
8462306a36Sopenharmony_ci		}
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return plen;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int __init olpc_dt_nextprop(phandle node, char *prev, char *buf)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	const void *args[] = { (void *)node, prev, buf };
9362306a36Sopenharmony_ci	int success;
9462306a36Sopenharmony_ci	void *res[] = { &success };
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	buf[0] = '\0';
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if ((s32)node == -1)
9962306a36Sopenharmony_ci		return -1;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (olpc_ofw("nextprop", args, res) || success != 1)
10262306a36Sopenharmony_ci		return -1;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return 0;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int __init olpc_dt_pkg2path(phandle node, char *buf,
10862306a36Sopenharmony_ci		const int buflen, int *len)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	const void *args[] = { (void *)node, buf, (void *)buflen };
11162306a36Sopenharmony_ci	void *res[] = { len };
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if ((s32)node == -1)
11462306a36Sopenharmony_ci		return -1;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (olpc_ofw("package-to-path", args, res) || *len < 1)
11762306a36Sopenharmony_ci		return -1;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	return 0;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic unsigned int prom_early_allocated __initdata;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_civoid * __init prom_early_alloc(unsigned long size)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	static u8 *mem;
12762306a36Sopenharmony_ci	static size_t free_mem;
12862306a36Sopenharmony_ci	void *res;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (free_mem < size) {
13162306a36Sopenharmony_ci		const size_t chunk_size = max(PAGE_SIZE, size);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci		/*
13462306a36Sopenharmony_ci		 * To minimize the number of allocations, grab at least
13562306a36Sopenharmony_ci		 * PAGE_SIZE of memory (that's an arbitrary choice that's
13662306a36Sopenharmony_ci		 * fast enough on the platforms we care about while minimizing
13762306a36Sopenharmony_ci		 * wasted bootmem) and hand off chunks of it to callers.
13862306a36Sopenharmony_ci		 */
13962306a36Sopenharmony_ci		res = memblock_alloc(chunk_size, SMP_CACHE_BYTES);
14062306a36Sopenharmony_ci		if (!res)
14162306a36Sopenharmony_ci			panic("%s: Failed to allocate %zu bytes\n", __func__,
14262306a36Sopenharmony_ci			      chunk_size);
14362306a36Sopenharmony_ci		BUG_ON(!res);
14462306a36Sopenharmony_ci		prom_early_allocated += chunk_size;
14562306a36Sopenharmony_ci		memset(res, 0, chunk_size);
14662306a36Sopenharmony_ci		free_mem = chunk_size;
14762306a36Sopenharmony_ci		mem = res;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	/* allocate from the local cache */
15162306a36Sopenharmony_ci	free_mem -= size;
15262306a36Sopenharmony_ci	res = mem;
15362306a36Sopenharmony_ci	mem += size;
15462306a36Sopenharmony_ci	return res;
15562306a36Sopenharmony_ci}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic struct of_pdt_ops prom_olpc_ops __initdata = {
15862306a36Sopenharmony_ci	.nextprop = olpc_dt_nextprop,
15962306a36Sopenharmony_ci	.getproplen = olpc_dt_getproplen,
16062306a36Sopenharmony_ci	.getproperty = olpc_dt_getproperty,
16162306a36Sopenharmony_ci	.getchild = olpc_dt_getchild,
16262306a36Sopenharmony_ci	.getsibling = olpc_dt_getsibling,
16362306a36Sopenharmony_ci	.pkg2path = olpc_dt_pkg2path,
16462306a36Sopenharmony_ci};
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic phandle __init olpc_dt_finddevice(const char *path)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	phandle node;
16962306a36Sopenharmony_ci	const void *args[] = { path };
17062306a36Sopenharmony_ci	void *res[] = { &node };
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	if (olpc_ofw("finddevice", args, res)) {
17362306a36Sopenharmony_ci		pr_err("olpc_dt: finddevice failed!\n");
17462306a36Sopenharmony_ci		return 0;
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if ((s32) node == -1)
17862306a36Sopenharmony_ci		return 0;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	return node;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int __init olpc_dt_interpret(const char *words)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	int result;
18662306a36Sopenharmony_ci	const void *args[] = { words };
18762306a36Sopenharmony_ci	void *res[] = { &result };
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (olpc_ofw("interpret", args, res)) {
19062306a36Sopenharmony_ci		pr_err("olpc_dt: interpret failed!\n");
19162306a36Sopenharmony_ci		return -1;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	return result;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci/*
19862306a36Sopenharmony_ci * Extract board revision directly from OFW device tree.
19962306a36Sopenharmony_ci * We can't use olpc_platform_info because that hasn't been set up yet.
20062306a36Sopenharmony_ci */
20162306a36Sopenharmony_cistatic u32 __init olpc_dt_get_board_revision(void)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	phandle node;
20462306a36Sopenharmony_ci	__be32 rev;
20562306a36Sopenharmony_ci	int r;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	node = olpc_dt_finddevice("/");
20862306a36Sopenharmony_ci	if (!node)
20962306a36Sopenharmony_ci		return 0;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	r = olpc_dt_getproperty(node, "board-revision-int",
21262306a36Sopenharmony_ci				(char *) &rev, sizeof(rev));
21362306a36Sopenharmony_ci	if (r < 0)
21462306a36Sopenharmony_ci		return 0;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	return be32_to_cpu(rev);
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic int __init olpc_dt_compatible_match(phandle node, const char *compat)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	char buf[64], *p;
22262306a36Sopenharmony_ci	int plen, len;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	plen = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf));
22562306a36Sopenharmony_ci	if (plen <= 0)
22662306a36Sopenharmony_ci		return 0;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	len = strlen(compat);
22962306a36Sopenharmony_ci	for (p = buf; p < buf + plen; p += strlen(p) + 1) {
23062306a36Sopenharmony_ci		if (strcmp(p, compat) == 0)
23162306a36Sopenharmony_ci			return 1;
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	return 0;
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic void __init olpc_dt_fixup(void)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	phandle node;
24062306a36Sopenharmony_ci	u32 board_rev;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	node = olpc_dt_finddevice("/battery@0");
24362306a36Sopenharmony_ci	if (!node)
24462306a36Sopenharmony_ci		return;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	board_rev = olpc_dt_get_board_revision();
24762306a36Sopenharmony_ci	if (!board_rev)
24862306a36Sopenharmony_ci		return;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	if (board_rev >= olpc_board_pre(0xd0)) {
25162306a36Sopenharmony_ci		/* XO-1.5 */
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci		if (olpc_dt_compatible_match(node, "olpc,xo1.5-battery"))
25462306a36Sopenharmony_ci			return;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci		/* Add olpc,xo1.5-battery compatible marker to battery node */
25762306a36Sopenharmony_ci		olpc_dt_interpret("\" /battery@0\" find-device");
25862306a36Sopenharmony_ci		olpc_dt_interpret("  \" olpc,xo1.5-battery\" +compatible");
25962306a36Sopenharmony_ci		olpc_dt_interpret("device-end");
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci		if (olpc_dt_compatible_match(node, "olpc,xo1-battery")) {
26262306a36Sopenharmony_ci			/*
26362306a36Sopenharmony_ci			 * If we have a olpc,xo1-battery compatible, then we're
26462306a36Sopenharmony_ci			 * running a new enough firmware that already has
26562306a36Sopenharmony_ci			 * the dcon node.
26662306a36Sopenharmony_ci			 */
26762306a36Sopenharmony_ci			return;
26862306a36Sopenharmony_ci		}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci		/* Add dcon device */
27162306a36Sopenharmony_ci		olpc_dt_interpret("\" /pci/display@1\" find-device");
27262306a36Sopenharmony_ci		olpc_dt_interpret("  new-device");
27362306a36Sopenharmony_ci		olpc_dt_interpret("    \" dcon\" device-name");
27462306a36Sopenharmony_ci		olpc_dt_interpret("    \" olpc,xo1-dcon\" +compatible");
27562306a36Sopenharmony_ci		olpc_dt_interpret("  finish-device");
27662306a36Sopenharmony_ci		olpc_dt_interpret("device-end");
27762306a36Sopenharmony_ci	} else {
27862306a36Sopenharmony_ci		/* XO-1 */
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci		if (olpc_dt_compatible_match(node, "olpc,xo1-battery")) {
28162306a36Sopenharmony_ci			/*
28262306a36Sopenharmony_ci			 * If we have a olpc,xo1-battery compatible, then we're
28362306a36Sopenharmony_ci			 * running a new enough firmware that already has
28462306a36Sopenharmony_ci			 * the dcon and RTC nodes.
28562306a36Sopenharmony_ci			 */
28662306a36Sopenharmony_ci			return;
28762306a36Sopenharmony_ci		}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		/* Add dcon device, mark RTC as olpc,xo1-rtc */
29062306a36Sopenharmony_ci		olpc_dt_interpret("\" /pci/display@1,1\" find-device");
29162306a36Sopenharmony_ci		olpc_dt_interpret("  new-device");
29262306a36Sopenharmony_ci		olpc_dt_interpret("    \" dcon\" device-name");
29362306a36Sopenharmony_ci		olpc_dt_interpret("    \" olpc,xo1-dcon\" +compatible");
29462306a36Sopenharmony_ci		olpc_dt_interpret("  finish-device");
29562306a36Sopenharmony_ci		olpc_dt_interpret("device-end");
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci		olpc_dt_interpret("\" /rtc\" find-device");
29862306a36Sopenharmony_ci		olpc_dt_interpret(" \" olpc,xo1-rtc\" +compatible");
29962306a36Sopenharmony_ci		olpc_dt_interpret("device-end");
30062306a36Sopenharmony_ci	}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	/* Add olpc,xo1-battery compatible marker to battery node */
30362306a36Sopenharmony_ci	olpc_dt_interpret("\" /battery@0\" find-device");
30462306a36Sopenharmony_ci	olpc_dt_interpret("  \" olpc,xo1-battery\" +compatible");
30562306a36Sopenharmony_ci	olpc_dt_interpret("device-end");
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_civoid __init olpc_dt_build_devicetree(void)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	phandle root;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (!olpc_ofw_is_installed())
31362306a36Sopenharmony_ci		return;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	olpc_dt_fixup();
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	root = olpc_dt_getsibling(0);
31862306a36Sopenharmony_ci	if (!root) {
31962306a36Sopenharmony_ci		pr_err("PROM: unable to get root node from OFW!\n");
32062306a36Sopenharmony_ci		return;
32162306a36Sopenharmony_ci	}
32262306a36Sopenharmony_ci	of_pdt_build_devicetree(root, &prom_olpc_ops);
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	pr_info("PROM DT: Built device tree with %u bytes of memory.\n",
32562306a36Sopenharmony_ci			prom_early_allocated);
32662306a36Sopenharmony_ci}
327