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