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>
This commit is contained in:
Michael Pratt 2018-06-11 13:07:09 -06:00 committed by Simon Glass
parent 8c5d4fd0ec
commit 90c08fa038
5 changed files with 224 additions and 1 deletions

View file

@ -0,0 +1,67 @@
* Memory binding
The memory binding for U-Boot is as in the ePAPR with the following additions:
Optional subnodes can be used defining the memory layout for different board
ID masks. To match a set of board ids, a board-id node may define match-mask
and match-value ints to define a mask to apply to the board id, and the value
that the result should have for the match to be considered valid. The mask
defaults to -1, meaning that the value must fully match the board id.
If subnodes are present, then the /memory node must define these properties:
- #address-cells: should be 1.
- #size-cells: should be 0.
Each subnode must define
reg - board ID or mask for this subnode
memory-banks - list of memory banks in the same format as normal
Each subnode may optionally define:
match-mask - A mask to apply to the board id. This must be accompanied by
match-value.
match-value - The required resulting value of the board id mask for the given
node to be considered a match.
auto-size - Indicates that the value given for a bank is the maximum size,
each bank is probed to determine its actual size, which may be
smaller
The board id determination is up to the vendor and is not defined by this
binding.
Example:
memory {
#address-cells = <1>;
#size-cells = <1>;
reg = <0x20000000 0x20000000
0x40000000 0x20000000
0x60000000 0x20000000
0x80000000 0x20000000>;
auto-size;
board-id@0 {
match-value = <17>;
reg = <0x20000000 0x20000000
0x40000000 0x20000000>;
};
board-id@1 {
match-mask = <2>;
match-value = <2>;
reg = <0x20000000 0x20000000
0x40000000 0x20000000
0x60000000 0x20000000
0x80000000 0x20000000
0xa0000000 0x20000000
0xc0000000 0x20000000
0xe0000000 0x20000000>;
};
};
This shows a system with the following properties:
* Default of 2GB of memory, auto-sized, so could be smaller
* 3.5GB of memory (with no auto-size) if (board id & 2) is 2
* 1GB of memory (with no auto-size) if board id is 17.

View file

@ -52,6 +52,7 @@ typedef struct global_data {
unsigned long env_has_init; /* Bitmask of boolean of struct env_location offsets */
int env_load_location;
unsigned long ram_base; /* Base address of RAM used by U-Boot */
unsigned long ram_top; /* Top address of RAM used by U-Boot */
unsigned long relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size; /* RAM size */

View file

@ -283,6 +283,16 @@ int fdt_setup_simplefb_node(void *fdt, int node, u64 base_address, u32 width,
int fdt_overlay_apply_verbose(void *fdt, void *fdto);
/**
* fdt_get_cells_len() - Get the length of a type of cell in top-level nodes
*
* Returns the length of the cell type in bytes (4 or 8).
*
* @blob: Pointer to device tree blob
* @nr_cells_name: Name to lookup, e.g. "#address-cells"
*/
int fdt_get_cells_len(const void *blob, char *nr_cells_name);
#endif /* ifdef CONFIG_OF_LIBFDT */
#ifdef USE_HOSTCC

View file

@ -41,6 +41,8 @@ struct fdt_memory {
fdt_addr_t end;
};
struct bd_info;
#ifdef CONFIG_SPL_BUILD
#define SPL_BUILD 1
#else
@ -993,6 +995,40 @@ int fdtdec_setup(void);
* Called when CONFIG_OF_BOARD is defined, or if CONFIG_OF_SEPARATE is defined
* and the board implements it.
*/
void *board_fdt_blob_setup(void);
/*
* Decode the size of memory
*
* RAM size is normally set in a /memory node and consists of a list of
* (base, size) cells in the 'reg' property. This information is used to
* determine the total available memory as well as the address and size
* of each bank.
*
* Optionally the memory configuration can vary depending on a board id,
* typically read from strapping resistors or an EEPROM on the board.
*
* Finally, memory size can be detected (within certain limits) by probing
* the available memory. It is safe to do so within the limits provides by
* the board's device tree information. This makes it possible to produce
* boards with different memory sizes, where the device tree specifies the
* maximum memory configuration, and the smaller memory configuration is
* probed.
*
* This function decodes that information, returning the memory base address,
* size and bank information. See the memory.txt binding for full
* documentation.
*
* @param blob Device tree blob
* @param area Name of node to check (NULL means "/memory")
* @param board_id Board ID to look up
* @param basep Returns base address of first memory bank (NULL to
* ignore)
* @param sizep Returns total memory size (NULL to ignore)
* @param bd Updated with the memory bank information (NULL to skip)
* @return 0 if OK, -ve on error
*/
int fdtdec_decode_ram_size(const void *blob, const char *area, int board_id,
phys_addr_t *basep, phys_size_t *sizep,
struct bd_info *bd);
#endif

View file

@ -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 @@ int fdtdec_setup(void)
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 */