fdt: Add device tree memory bindings

Support a default memory bank, specified in reg, as well as
board-specific memory banks in subtree board-id nodes.

This allows memory information to be provided in the device tree,
rather than hard-coded in, which will make it simpler to handle
similar devices with different memory banks, as the board-id values
or masks can be used to match devices.

Signed-off-by: Michael Pratt <mpratt@chromium.org>
Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
diff --git a/lib/fdtdec.c b/lib/fdtdec.c
index f4e8dbf..fc92082 100644
--- a/lib/fdtdec.c
+++ b/lib/fdtdec.c
@@ -11,6 +11,7 @@
 #include <errno.h>
 #include <fdtdec.h>
 #include <fdt_support.h>
+#include <inttypes.h>
 #include <linux/libfdt.h>
 #include <serial.h>
 #include <asm/sections.h>
@@ -1350,4 +1351,112 @@
 	return fdtdec_prepare_fdt();
 }
 
+#ifdef CONFIG_NR_DRAM_BANKS
+int fdtdec_decode_ram_size(const void *blob, const char *area, int board_id,
+			   phys_addr_t *basep, phys_size_t *sizep, bd_t *bd)
+{
+	int addr_cells, size_cells;
+	const u32 *cell, *end;
+	u64 total_size, size, addr;
+	int node, child;
+	bool auto_size;
+	int bank;
+	int len;
+
+	debug("%s: board_id=%d\n", __func__, board_id);
+	if (!area)
+		area = "/memory";
+	node = fdt_path_offset(blob, area);
+	if (node < 0) {
+		debug("No %s node found\n", area);
+		return -ENOENT;
+	}
+
+	cell = fdt_getprop(blob, node, "reg", &len);
+	if (!cell) {
+		debug("No reg property found\n");
+		return -ENOENT;
+	}
+
+	addr_cells = fdt_address_cells(blob, node);
+	size_cells = fdt_size_cells(blob, node);
+
+	/* Check the board id and mask */
+	for (child = fdt_first_subnode(blob, node);
+	     child >= 0;
+	     child = fdt_next_subnode(blob, child)) {
+		int match_mask, match_value;
+
+		match_mask = fdtdec_get_int(blob, child, "match-mask", -1);
+		match_value = fdtdec_get_int(blob, child, "match-value", -1);
+
+		if (match_value >= 0 &&
+		    ((board_id & match_mask) == match_value)) {
+			/* Found matching mask */
+			debug("Found matching mask %d\n", match_mask);
+			node = child;
+			cell = fdt_getprop(blob, node, "reg", &len);
+			if (!cell) {
+				debug("No memory-banks property found\n");
+				return -EINVAL;
+			}
+			break;
+		}
+	}
+	/* Note: if no matching subnode was found we use the parent node */
+
+	if (bd) {
+		memset(bd->bi_dram, '\0', sizeof(bd->bi_dram[0]) *
+						CONFIG_NR_DRAM_BANKS);
+	}
+
+	auto_size = fdtdec_get_bool(blob, node, "auto-size");
+
+	total_size = 0;
+	end = cell + len / 4 - addr_cells - size_cells;
+	debug("cell at %p, end %p\n", cell, end);
+	for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) {
+		if (cell > end)
+			break;
+		addr = 0;
+		if (addr_cells == 2)
+			addr += (u64)fdt32_to_cpu(*cell++) << 32UL;
+		addr += fdt32_to_cpu(*cell++);
+		if (bd)
+			bd->bi_dram[bank].start = addr;
+		if (basep && !bank)
+			*basep = (phys_addr_t)addr;
+
+		size = 0;
+		if (size_cells == 2)
+			size += (u64)fdt32_to_cpu(*cell++) << 32UL;
+		size += fdt32_to_cpu(*cell++);
+
+		if (auto_size) {
+			u64 new_size;
+
+			debug("Auto-sizing %" PRIx64 ", size %" PRIx64 ": ",
+			      addr, size);
+			new_size = get_ram_size((long *)(uintptr_t)addr, size);
+			if (new_size == size) {
+				debug("OK\n");
+			} else {
+				debug("sized to %" PRIx64 "\n", new_size);
+				size = new_size;
+			}
+		}
+
+		if (bd)
+			bd->bi_dram[bank].size = size;
+		total_size += size;
+	}
+
+	debug("Memory size %" PRIu64 "\n", total_size);
+	if (sizep)
+		*sizep = (phys_size_t)total_size;
+
+	return 0;
+}
+#endif /* CONFIG_NR_DRAM_BANKS */
+
 #endif /* !USE_HOSTCC */