diff options
Diffstat (limited to 'components/drivers/ofw/io.c')
| -rwxr-xr-x | components/drivers/ofw/io.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/components/drivers/ofw/io.c b/components/drivers/ofw/io.c new file mode 100755 index 0000000000..b5d6b58425 --- /dev/null +++ b/components/drivers/ofw/io.c @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-08-25 GuEe-GUI first version + */ + +#include <rtthread.h> + +#include <ioremap.h> +#include <drivers/ofw.h> +#include <drivers/ofw_io.h> +#include <drivers/ofw_fdt.h> + +#define DBG_TAG "rtdm.ofw" +#define DBG_LVL DBG_INFO +#include <rtdbg.h> + +#include "ofw_internal.h" + +static int ofw_bus_addr_cells(struct rt_ofw_node *np) +{ + int res = OFW_ROOT_NODE_ADDR_CELLS_DEFAULT; + + for (rt_uint32_t cells; np; np = np->parent) + { + if (!rt_ofw_prop_read_u32(np, "#address-cells", &cells)) + { + res = cells; + break; + } + } + + return res; +} + +static int ofw_bus_size_cells(struct rt_ofw_node *np) +{ + int res = OFW_ROOT_NODE_SIZE_CELLS_DEFAULT; + + for (rt_uint32_t cells; np; np = np->parent) + { + if (!rt_ofw_prop_read_u32(np, "#size-cells", &cells)) + { + res = cells; + break; + } + } + + return res; +} + +int rt_ofw_bus_addr_cells(struct rt_ofw_node *np) +{ + return np ? ofw_bus_addr_cells(np) : -RT_EINVAL; +} + +int rt_ofw_bus_size_cells(struct rt_ofw_node *np) +{ + return np ? ofw_bus_size_cells(np) : -RT_EINVAL; +} + +int rt_ofw_io_addr_cells(struct rt_ofw_node *np) +{ + return np ? ofw_bus_addr_cells(np->parent ? np->parent : np) : -RT_EINVAL; +} + +int rt_ofw_io_size_cells(struct rt_ofw_node *np) +{ + return np ? ofw_bus_size_cells(np->parent ? np->parent : np) : -RT_EINVAL; +} + +int rt_ofw_get_address_count(struct rt_ofw_node *np) +{ + int count; + + if (np) + { + rt_ssize_t len; + + count = 0; + + if (rt_ofw_get_prop(np, "reg", &len)) + { + count = len / (sizeof(fdt32_t) * (rt_ofw_io_addr_cells(np) + rt_ofw_io_size_cells(np))); + } + } + else + { + count = -RT_EINVAL; + } + + return count; +} + +static rt_err_t ofw_get_address(struct rt_ofw_node *np, int index, rt_uint64_t *out_address, rt_uint64_t *out_size) +{ + rt_ssize_t len; + rt_err_t err = RT_EOK; + int addr_cells = rt_ofw_io_addr_cells(np); + int size_cells = rt_ofw_io_size_cells(np); + int skip_cells = (addr_cells + size_cells) * index; + const fdt32_t *cell = rt_ofw_prop_read_raw(np, "reg", &len); + + if (cell && skip_cells < (len / sizeof(*cell))) + { + cell += skip_cells; + *out_address = rt_fdt_next_cell(&cell, addr_cells); + *out_address = rt_ofw_translate_address(np, RT_NULL, *out_address); + *out_size = rt_fdt_read_number(cell, size_cells); + } + else + { + err = -RT_EINVAL; + } + + return err; +} + +rt_err_t rt_ofw_get_address(struct rt_ofw_node *np, int index, rt_uint64_t *out_address, rt_uint64_t *out_size) +{ + rt_err_t err; + + if (np && index >= 0 && (out_address || out_size)) + { + rt_uint64_t address, size; + + err = ofw_get_address(np, index, &address, &size); + + if (!err) + { + if (out_address) + { + *out_address = address; + } + if (out_size) + { + *out_size = size; + } + } + } + else + { + err = -RT_EINVAL; + } + + return err; +} + +static rt_err_t ofw_get_address_by_name(struct rt_ofw_node *np, const char *name, + rt_uint64_t *out_address, rt_uint64_t *out_size) + +{ + int index = 0; + rt_err_t err = RT_EOK; + const char *reg_name; + struct rt_ofw_prop *prop; + + rt_ofw_foreach_prop_string(np, "reg-names", prop, reg_name) + { + if (!rt_strcmp(name, reg_name)) + { + err = rt_ofw_get_address(np, index, out_address, out_size); + + break; + } + + ++index; + } + + return err; +} + +rt_err_t rt_ofw_get_address_by_name(struct rt_ofw_node *np, const char *name, + rt_uint64_t *out_address, rt_uint64_t *out_size) +{ + rt_err_t err; + + if (np && name && (out_address || out_size)) + { + rt_uint64_t address, size; + + err = ofw_get_address_by_name(np, name, &address, &size); + + if (!err) + { + if (out_address) + { + *out_address = address; + } + if (out_size) + { + *out_size = size; + } + } + } + else + { + err = -RT_EINVAL; + } + + return err; +} + +int rt_ofw_get_address_array(struct rt_ofw_node *np, int nr, rt_uint64_t *out_regs) +{ + int count; + + if (np && nr > 0 && out_regs) + { + rt_ssize_t len; + int max_nr; + int addr_cells = rt_ofw_io_addr_cells(np); + int size_cells = rt_ofw_io_size_cells(np); + const fdt32_t *cell = rt_ofw_prop_read_raw(np, "reg", &len); + + max_nr = len / (sizeof(*cell) * (addr_cells + size_cells)); + + if (nr > max_nr) + { + nr = max_nr; + } + + count = nr; + + while (nr --> 0) + { + *out_regs = rt_fdt_next_cell(&cell, addr_cells); + *out_regs = rt_ofw_translate_address(np, RT_NULL, *out_regs); + ++out_regs; + + *out_regs = rt_fdt_next_cell(&cell, size_cells); + ++out_regs; + } + } + else + { + count = -RT_EINVAL; + } + + return count; +} + +static struct bus_ranges *ofw_bus_ranges(struct rt_ofw_node *np, struct rt_ofw_prop *prop) +{ + const fdt32_t *cell; + struct bus_ranges *ranges = RT_NULL; + int child_address_cells, child_size_cells, parent_address_cells, groups; + rt_uint64_t *child_addr, *parent_addr, *child_size; + + /* + * Address Translation Example: + * + * / { + * #address-cells = <1>; + * #size-cells = <1>; + * + * soc { + * compatible = "simple-bus"; + * #address-cells = <1>; + * #size-cells = <1>; + * ranges = <0x0 0xe0000000 0x00100000>; + * + * serial@4600 { + * device_type = "serial"; + * reg = <0x4600 0x100>; + * clock-frequency = <0>; + * }; + * }; + * } + * + * The soc node specifies a ranges property of <0x0 0xe0000000 0x00100000>; + * This property value specifies that for a 1024 KB range of address space, a + * child node addressed at physical 0x0 maps to a parent address of physical + * 0xe0000000. With this mapping, the serial device node can be addressed by a + * load or store at address 0xe0004600, an offset of 0x4600 (specified in reg) + * plus the 0xe0000000 mapping specified in ranges: + * + * bus-address = parent-bus-address + (reg-address - child-bus-address) + */ + + do { + child_address_cells = rt_ofw_bus_addr_cells(np); + child_size_cells = rt_ofw_bus_size_cells(np); + parent_address_cells = rt_ofw_io_addr_cells(np); + + if (child_address_cells < 0 || child_size_cells < 0 || parent_address_cells < 0) + { + LOG_D("%s read address/size cells fail: child[%d, %d] parent[%d]", + np->full_name, child_address_cells, child_size_cells, parent_address_cells); + + break; + } + + groups = prop->length / sizeof(*cell); + groups /= child_address_cells + child_size_cells + parent_address_cells; + + ranges = rt_malloc(sizeof(*ranges) + sizeof(rt_uint64_t) * 3 * groups); + + if (!ranges) + { + break; + } + + ranges->nr = groups; + ranges->child_addr = (void *)ranges + sizeof(*ranges); + ranges->parent_addr = &ranges->child_addr[groups]; + ranges->child_size = &ranges->parent_addr[groups]; + + cell = prop->value; + + child_addr = ranges->child_addr; + parent_addr = ranges->parent_addr; + child_size = ranges->child_size; + + while (groups --> 0) + { + *child_addr++ = rt_fdt_next_cell(&cell, child_address_cells); + *parent_addr++ = rt_fdt_next_cell(&cell, parent_address_cells); + *child_size++ = rt_fdt_next_cell(&cell, child_size_cells); + } + + rt_ofw_data(np) = ranges; + } while (0); + + return ranges; +} + +rt_uint64_t rt_ofw_translate_address(struct rt_ofw_node *np, const char *range_type, rt_uint64_t address) +{ + rt_uint64_t cpu_addr = address; + + if (!range_type) + { + range_type = "ranges"; + } + + rt_ofw_foreach_parent_node(np) + { + rt_ssize_t len; + struct rt_ofw_prop *prop; + struct bus_ranges *ranges; + + prop = rt_ofw_get_prop(np, range_type, &len); + + if (!prop || !len) + { + continue; + } + + ranges = rt_ofw_data(np); + + if (!ranges) + { + ranges = ofw_bus_ranges(np, prop); + } + + if (ranges) + { + for (int i = 0; i < ranges->nr; ++i) + { + rt_uint64_t child_addr = ranges->child_addr[i]; + rt_uint64_t child_size = ranges->child_size[i]; + + if (address >= child_addr && address < child_addr + child_size) + { + cpu_addr = address + (ranges->parent_addr[i] - child_addr); + + break; + } + } + } + else + { + cpu_addr = ~0ULL; + } + + rt_ofw_node_put(np); + + break; + } + + return cpu_addr; +} + +void *rt_ofw_iomap(struct rt_ofw_node *np, int index) +{ + void *iomem = RT_NULL; + + if (np) + { + rt_uint64_t regs[2]; + + if (!ofw_get_address(np, index, ®s[0], ®s[1])) + { + iomem = rt_ioremap((void *)regs[0], (size_t)regs[1]); + } + } + + return iomem; +} + +void *rt_ofw_iomap_by_name(struct rt_ofw_node *np, const char *name) +{ + void *iomem = RT_NULL; + + if (np) + { + rt_uint64_t regs[2]; + + if (!ofw_get_address_by_name(np, name, ®s[0], ®s[1])) + { + iomem = rt_ioremap((void *)regs[0], (size_t)regs[1]); + } + } + + return iomem; +} |
