In response to new device tree code in the kernel, OLPC will start using it for probing of certain devices. However, some firmware fixes are needed to put the devicetree into a usable state. Retain compatibility with old firmware by fixing up the device tree at boot-time if it does not contain the new nodes/properties that we need for probing. This is the same approach taken on PPC platforms. Signed-off-by: Daniel Drake <dsd@laptop.org> Link: http://lkml.kernel.org/r/1309019658-1712-2-git-send-email-dsd@laptop.org Acked-by: Grant Likely <grant.likely@secretlab.ca> Acked-by: Andres Salomon <dilinger@queued.net> Cc: devicetree-discuss@lists.ozlabs.org Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
		
			
				
	
	
		
			304 lines
		
	
	
	
		
			6.6 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
	
		
			6.6 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * OLPC-specific OFW device tree support code.
 | 
						|
 *
 | 
						|
 * Paul Mackerras	August 1996.
 | 
						|
 * Copyright (C) 1996-2005 Paul Mackerras.
 | 
						|
 *
 | 
						|
 *  Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
 | 
						|
 *    {engebret|bergner}@us.ibm.com
 | 
						|
 *
 | 
						|
 *  Adapted for sparc by David S. Miller davem@davemloft.net
 | 
						|
 *  Adapted for x86/OLPC by Andres Salomon <dilinger@queued.net>
 | 
						|
 *
 | 
						|
 *      This program is free software; you can redistribute it and/or
 | 
						|
 *      modify it under the terms of the GNU General Public License
 | 
						|
 *      as published by the Free Software Foundation; either version
 | 
						|
 *      2 of the License, or (at your option) any later version.
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/kernel.h>
 | 
						|
#include <linux/bootmem.h>
 | 
						|
#include <linux/of.h>
 | 
						|
#include <linux/of_platform.h>
 | 
						|
#include <linux/of_pdt.h>
 | 
						|
#include <asm/olpc.h>
 | 
						|
#include <asm/olpc_ofw.h>
 | 
						|
 | 
						|
static phandle __init olpc_dt_getsibling(phandle node)
 | 
						|
{
 | 
						|
	const void *args[] = { (void *)node };
 | 
						|
	void *res[] = { &node };
 | 
						|
 | 
						|
	if ((s32)node == -1)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	if (olpc_ofw("peer", args, res) || (s32)node == -1)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	return node;
 | 
						|
}
 | 
						|
 | 
						|
static phandle __init olpc_dt_getchild(phandle node)
 | 
						|
{
 | 
						|
	const void *args[] = { (void *)node };
 | 
						|
	void *res[] = { &node };
 | 
						|
 | 
						|
	if ((s32)node == -1)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	if (olpc_ofw("child", args, res) || (s32)node == -1) {
 | 
						|
		pr_err("PROM: %s: fetching child failed!\n", __func__);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	return node;
 | 
						|
}
 | 
						|
 | 
						|
static int __init olpc_dt_getproplen(phandle node, const char *prop)
 | 
						|
{
 | 
						|
	const void *args[] = { (void *)node, prop };
 | 
						|
	int len;
 | 
						|
	void *res[] = { &len };
 | 
						|
 | 
						|
	if ((s32)node == -1)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (olpc_ofw("getproplen", args, res)) {
 | 
						|
		pr_err("PROM: %s: getproplen failed!\n", __func__);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return len;
 | 
						|
}
 | 
						|
 | 
						|
static int __init olpc_dt_getproperty(phandle node, const char *prop,
 | 
						|
		char *buf, int bufsize)
 | 
						|
{
 | 
						|
	int plen;
 | 
						|
 | 
						|
	plen = olpc_dt_getproplen(node, prop);
 | 
						|
	if (plen > bufsize || plen < 1) {
 | 
						|
		return -1;
 | 
						|
	} else {
 | 
						|
		const void *args[] = { (void *)node, prop, buf, (void *)plen };
 | 
						|
		void *res[] = { &plen };
 | 
						|
 | 
						|
		if (olpc_ofw("getprop", args, res)) {
 | 
						|
			pr_err("PROM: %s: getprop failed!\n", __func__);
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return plen;
 | 
						|
}
 | 
						|
 | 
						|
static int __init olpc_dt_nextprop(phandle node, char *prev, char *buf)
 | 
						|
{
 | 
						|
	const void *args[] = { (void *)node, prev, buf };
 | 
						|
	int success;
 | 
						|
	void *res[] = { &success };
 | 
						|
 | 
						|
	buf[0] = '\0';
 | 
						|
 | 
						|
	if ((s32)node == -1)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (olpc_ofw("nextprop", args, res) || success != 1)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int __init olpc_dt_pkg2path(phandle node, char *buf,
 | 
						|
		const int buflen, int *len)
 | 
						|
{
 | 
						|
	const void *args[] = { (void *)node, buf, (void *)buflen };
 | 
						|
	void *res[] = { len };
 | 
						|
 | 
						|
	if ((s32)node == -1)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (olpc_ofw("package-to-path", args, res) || *len < 1)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static unsigned int prom_early_allocated __initdata;
 | 
						|
 | 
						|
void * __init prom_early_alloc(unsigned long size)
 | 
						|
{
 | 
						|
	static u8 *mem;
 | 
						|
	static size_t free_mem;
 | 
						|
	void *res;
 | 
						|
 | 
						|
	if (free_mem < size) {
 | 
						|
		const size_t chunk_size = max(PAGE_SIZE, size);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * To mimimize the number of allocations, grab at least
 | 
						|
		 * PAGE_SIZE of memory (that's an arbitrary choice that's
 | 
						|
		 * fast enough on the platforms we care about while minimizing
 | 
						|
		 * wasted bootmem) and hand off chunks of it to callers.
 | 
						|
		 */
 | 
						|
		res = alloc_bootmem(chunk_size);
 | 
						|
		BUG_ON(!res);
 | 
						|
		prom_early_allocated += chunk_size;
 | 
						|
		memset(res, 0, chunk_size);
 | 
						|
		free_mem = chunk_size;
 | 
						|
		mem = res;
 | 
						|
	}
 | 
						|
 | 
						|
	/* allocate from the local cache */
 | 
						|
	free_mem -= size;
 | 
						|
	res = mem;
 | 
						|
	mem += size;
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
static struct of_pdt_ops prom_olpc_ops __initdata = {
 | 
						|
	.nextprop = olpc_dt_nextprop,
 | 
						|
	.getproplen = olpc_dt_getproplen,
 | 
						|
	.getproperty = olpc_dt_getproperty,
 | 
						|
	.getchild = olpc_dt_getchild,
 | 
						|
	.getsibling = olpc_dt_getsibling,
 | 
						|
	.pkg2path = olpc_dt_pkg2path,
 | 
						|
};
 | 
						|
 | 
						|
static phandle __init olpc_dt_finddevice(const char *path)
 | 
						|
{
 | 
						|
	phandle node;
 | 
						|
	const void *args[] = { path };
 | 
						|
	void *res[] = { &node };
 | 
						|
 | 
						|
	if (olpc_ofw("finddevice", args, res)) {
 | 
						|
		pr_err("olpc_dt: finddevice failed!\n");
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((s32) node == -1)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	return node;
 | 
						|
}
 | 
						|
 | 
						|
static int __init olpc_dt_interpret(const char *words)
 | 
						|
{
 | 
						|
	int result;
 | 
						|
	const void *args[] = { words };
 | 
						|
	void *res[] = { &result };
 | 
						|
 | 
						|
	if (olpc_ofw("interpret", args, res)) {
 | 
						|
		pr_err("olpc_dt: interpret failed!\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Extract board revision directly from OFW device tree.
 | 
						|
 * We can't use olpc_platform_info because that hasn't been set up yet.
 | 
						|
 */
 | 
						|
static u32 __init olpc_dt_get_board_revision(void)
 | 
						|
{
 | 
						|
	phandle node;
 | 
						|
	__be32 rev;
 | 
						|
	int r;
 | 
						|
 | 
						|
	node = olpc_dt_finddevice("/");
 | 
						|
	if (!node)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	r = olpc_dt_getproperty(node, "board-revision-int",
 | 
						|
				(char *) &rev, sizeof(rev));
 | 
						|
	if (r < 0)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	return be32_to_cpu(rev);
 | 
						|
}
 | 
						|
 | 
						|
void __init olpc_dt_fixup(void)
 | 
						|
{
 | 
						|
	int r;
 | 
						|
	char buf[64];
 | 
						|
	phandle node;
 | 
						|
	u32 board_rev;
 | 
						|
 | 
						|
	node = olpc_dt_finddevice("/battery@0");
 | 
						|
	if (!node)
 | 
						|
		return;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If the battery node has a compatible property, we are running a new
 | 
						|
	 * enough firmware and don't have fixups to make.
 | 
						|
	 */
 | 
						|
	r = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf));
 | 
						|
	if (r > 0)
 | 
						|
		return;
 | 
						|
 | 
						|
	pr_info("PROM DT: Old firmware detected, applying fixes\n");
 | 
						|
 | 
						|
	/* Add olpc,xo1-battery compatible marker to battery node */
 | 
						|
	olpc_dt_interpret("\" /battery@0\" find-device"
 | 
						|
		" \" olpc,xo1-battery\" +compatible"
 | 
						|
		" device-end");
 | 
						|
 | 
						|
	board_rev = olpc_dt_get_board_revision();
 | 
						|
	if (!board_rev)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (board_rev >= olpc_board_pre(0xd0)) {
 | 
						|
		/* XO-1.5: add dcon device */
 | 
						|
		olpc_dt_interpret("\" /pci/display@1\" find-device"
 | 
						|
			" new-device"
 | 
						|
			" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible"
 | 
						|
			" finish-device device-end");
 | 
						|
	} else {
 | 
						|
		/* XO-1: add dcon device, mark RTC as olpc,xo1-rtc */
 | 
						|
		olpc_dt_interpret("\" /pci/display@1,1\" find-device"
 | 
						|
			" new-device"
 | 
						|
			" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible"
 | 
						|
			" finish-device device-end"
 | 
						|
			" \" /rtc\" find-device"
 | 
						|
			" \" olpc,xo1-rtc\" +compatible"
 | 
						|
			" device-end");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void __init olpc_dt_build_devicetree(void)
 | 
						|
{
 | 
						|
	phandle root;
 | 
						|
 | 
						|
	if (!olpc_ofw_is_installed())
 | 
						|
		return;
 | 
						|
 | 
						|
	olpc_dt_fixup();
 | 
						|
 | 
						|
	root = olpc_dt_getsibling(0);
 | 
						|
	if (!root) {
 | 
						|
		pr_err("PROM: unable to get root node from OFW!\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	of_pdt_build_devicetree(root, &prom_olpc_ops);
 | 
						|
 | 
						|
	pr_info("PROM DT: Built device tree with %u bytes of memory.\n",
 | 
						|
			prom_early_allocated);
 | 
						|
}
 | 
						|
 | 
						|
/* A list of DT node/bus matches that we want to expose as platform devices */
 | 
						|
static struct of_device_id __initdata of_ids[] = {
 | 
						|
	{ .compatible = "olpc,xo1-battery" },
 | 
						|
	{ .compatible = "olpc,xo1-dcon" },
 | 
						|
	{ .compatible = "olpc,xo1-rtc" },
 | 
						|
	{},
 | 
						|
};
 | 
						|
 | 
						|
static int __init olpc_create_platform_devices(void)
 | 
						|
{
 | 
						|
	if (machine_is_olpc())
 | 
						|
		return of_platform_bus_probe(NULL, of_ids, NULL);
 | 
						|
	else
 | 
						|
		return 0;
 | 
						|
}
 | 
						|
device_initcall(olpc_create_platform_devices);
 |