incubator-nuttx/drivers/devicetree/fdt.c

578 lines
14 KiB
C

/****************************************************************************
* drivers/devicetree/fdt.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <stddef.h>
#include <endian.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#include <nuttx/compiler.h>
#include <nuttx/fdt.h>
#include <libfdt.h>
/****************************************************************************
* Private Data
****************************************************************************/
/* Location of the fdt data for this system. */
static FAR const char *g_fdt_base = NULL;
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: fdt_register
*
* Description:
* Store the pointer to the flattened device tree and verify that it at
* least appears to be valid. This function will not fully parse the FDT.
*
* Return:
* Return -EINVAL if the fdt header does not have the expected magic value.
* otherwise return OK. If OK is not returned the existing entry for FDT
* is not modified.
*
****************************************************************************/
int fdt_register(FAR const char *fdt_base)
{
struct fdt_header_s *fdt_header;
DEBUGASSERT(fdt_base);
fdt_header = (struct fdt_header_s *)fdt_base;
if (fdt_header->magic != be32toh(FDT_MAGIC))
{
return -EINVAL; /* Bad magic byte read */
}
g_fdt_base = fdt_base;
return OK;
}
/****************************************************************************
* Name: fdt_get
*
* Description:
* Return the pointer to a raw FDT. NULL is returned if no FDT has been
* loaded.
*
****************************************************************************/
FAR const char *fdt_get(void)
{
return g_fdt_base;
}
/****************************************************************************
* Name: fdt_get_irq
*
* Description:
* Get the interrupt number of the node
*
****************************************************************************/
int fdt_get_irq(FAR const void *fdt, int nodeoffset,
int offset, int irqbase)
{
FAR const fdt32_t *pv;
int irq = -1;
pv = fdt_getprop(fdt, nodeoffset, "interrupts", NULL);
if (pv != NULL)
{
irq = fdt32_ld(pv + offset) + irqbase;
}
return irq;
}
/****************************************************************************
* Name: fdt_get_irq_by_path
*
* Description:
* Get the interrupt number of the node
*
****************************************************************************/
int fdt_get_irq_by_path(FAR const void *fdt, int offset,
const char *path, int irqbase)
{
return fdt_get_irq(fdt, fdt_path_offset(fdt, path), offset, irqbase);
}
/****************************************************************************
* Name: fdt_get_bankwidth
*
* Description:
* Get the value of bankwidth
*
****************************************************************************/
uint32_t fdt_get_bankwidth(FAR const void *fdt, int offset)
{
FAR const void *reg;
uint32_t bankwidth = 0;
reg = fdt_getprop(fdt, offset, "bank-width", NULL);
if (reg != NULL)
{
bankwidth = fdt32_ld(reg);
}
return bankwidth;
}
/****************************************************************************
* Name: fdt_get_parent_address_cells
*
* Description:
* Get the parent address of the register space
*
****************************************************************************/
int fdt_get_parent_address_cells(FAR const void *fdt, int offset)
{
int parentoff;
parentoff = fdt_parent_offset(fdt, offset);
if (parentoff < 0)
{
return parentoff;
}
return fdt_address_cells(fdt, parentoff);
}
/****************************************************************************
* Name: fdt_get_parent_size_cells
*
* Description:
* Get the parent size of the register space
*
****************************************************************************/
int fdt_get_parent_size_cells(FAR const void *fdt, int offset)
{
int parentoff;
parentoff = fdt_parent_offset(fdt, offset);
if (parentoff < 0)
{
return parentoff;
}
return fdt_size_cells(fdt, parentoff);
}
/****************************************************************************
* Name: fdt_ld_by_cells
*
* Description:
* Load a 32-bit or 64-bit value from a buffer, depending on the number
* of address cells.
*
****************************************************************************/
uintptr_t fdt_ld_by_cells(FAR const void *value, int cells)
{
if (cells == 2)
{
return fdt64_ld(value);
}
else
{
return fdt32_ld(value);
}
}
/****************************************************************************
* Name: fdt_get_reg_count
*
* Description:
* Get the count (in bytes) of the register space
*
****************************************************************************/
uint32_t fdt_get_reg_count(FAR const void *fdt, int offset)
{
FAR const struct fdt_property *reg;
uint32_t count = 0;
reg = fdt_get_property(fdt, offset, "reg", NULL);
if (reg != NULL)
{
count = fdt32_ld(&reg->len);
}
return count;
}
uintptr_t fdt_get_reg_base_by_name(FAR const void *fdt, int offset,
FAR const char *reg_name)
{
uintptr_t addr = 0;
int reg_index
= fdt_stringlist_search(fdt, offset, "reg-names", reg_name);
if (reg_index < 0)
{
return addr;
}
return fdt_get_reg_base(fdt, offset, reg_index);
}
/****************************************************************************
* Name: fdt_get_reg_base
*
* Description:
* Get the base address of the register space
*
****************************************************************************/
uintptr_t fdt_get_reg_base(FAR const void *fdt, int offset, int index)
{
FAR const void *reg;
uintptr_t addr = 0;
int reg_length;
/* Register cells contain a tuple of two values */
index *= 2;
reg = fdt_getprop(fdt, offset, "reg", &reg_length);
if (reg != NULL)
{
if ((index * sizeof(uintptr_t)) > reg_length)
{
return addr;
}
addr = fdt_ld_by_cells(reg + index * sizeof(uintptr_t),
fdt_get_parent_address_cells(fdt, offset));
}
return addr;
}
/****************************************************************************
* Name: fdt_get_reg_base_by_index
*
* Description:
* Get the base address of the register space by index
*
****************************************************************************/
uintptr_t fdt_get_reg_base_by_index(FAR const void *fdt, int offset,
int index)
{
FAR const void *reg;
uintptr_t addr = 0;
reg = fdt_getprop(fdt, offset, "reg", NULL);
if (reg != NULL)
{
int address_cell;
int size_cell;
address_cell = fdt_get_parent_address_cells(fdt, offset);
size_cell = fdt_get_parent_size_cells(fdt, offset);
addr = fdt_ld_by_cells((FAR fdt32_t *)reg +
(address_cell + size_cell) * index,
address_cell);
}
return addr;
}
/****************************************************************************
* Name: fdt_get_reg_size
*
* Description:
* Get the size of the register space
*
****************************************************************************/
size_t fdt_get_reg_size(FAR const void *fdt, int offset)
{
FAR const void *reg;
size_t size = 0;
reg = fdt_getprop(fdt, offset, "reg", NULL);
if (reg != NULL)
{
size = fdt_ld_by_cells((FAR fdt32_t *)reg +
fdt_get_parent_address_cells(fdt, offset),
fdt_get_parent_size_cells(fdt, offset));
}
return size;
}
/****************************************************************************
* Name: fdt_get_reg_size_by_index
*
* Description:
* Get the size of the register space by index
*
****************************************************************************/
size_t fdt_get_reg_size_by_index(FAR const void *fdt, int offset, int index)
{
FAR const void *reg;
size_t size = 0;
reg = fdt_getprop(fdt, offset, "reg", NULL);
if (reg != NULL)
{
int address_cell;
int size_cell;
address_cell = fdt_get_parent_address_cells(fdt, offset);
size_cell = fdt_get_parent_size_cells(fdt, offset);
size = fdt_ld_by_cells((FAR fdt32_t *)reg +
(address_cell + size_cell) * index + address_cell, size_cell);
}
return size;
}
/****************************************************************************
* Name: fdt_get_reg_base_by_path
*
* Description:
* Get the base address of the register space
*
****************************************************************************/
uintptr_t fdt_get_reg_base_by_path(FAR const void *fdt, FAR const char *path)
{
return fdt_get_reg_base(fdt, fdt_path_offset(fdt, path), 0);
}
bool fdt_device_is_available(FAR const void *fdt, int node)
{
FAR const char *status = fdt_getprop(fdt, node, "status", NULL);
if (!status)
{
return true;
}
if (!strcmp(status, "ok") || !strcmp(status, "okay"))
{
return true;
}
return false;
}
FAR const char *fdt_get_node_label(FAR const void *fdt, int node)
{
int symbols_offset;
int property_offset;
int ret;
const char *property_name;
const char *label_name;
char path_buffer[CONFIG_PATH_MAX] =
{
0
};
symbols_offset = fdt_path_offset(fdt, "/__symbols__");
if (symbols_offset < 0)
{
return NULL;
}
ret = fdt_get_path(fdt, node, path_buffer, sizeof(path_buffer));
if (ret < 0)
{
return NULL;
}
fdt_for_each_property_offset(property_offset, fdt, symbols_offset)
{
property_name = fdt_getprop_by_offset(
fdt, property_offset, &label_name, NULL);
/* The symbols section is a list of parameters in the format
* label_name = node_path. So the value of each property needs to be
* checked with the full path found earlier.
*
*/
if (!strncmp(property_name, path_buffer, sizeof(path_buffer)))
{
return label_name;
}
}
return NULL;
}
uintptr_t fdt_get_clock_frequency(FAR const void *fdt, int offset)
{
const void *pv;
uintptr_t clock_frequency = 0;
pv = fdt_getprop(fdt, offset, "clock-frequency", NULL);
if (!pv)
{
return clock_frequency;
}
clock_frequency = fdt_ld_by_cells(pv,
fdt_get_parent_address_cells(fdt,
offset));
return clock_frequency;
}
uintptr_t fdt_get_clock_frequency_from_clocks(FAR const void *fdt,
int offset,
int index)
{
const fdt32_t *pv;
fdt32_t clk_phandle;
int pv_offset;
uintptr_t clock_frequency = 0;
int clk_length;
pv = fdt_getprop(fdt, offset, "clocks", &clk_length);
if (!pv)
{
return clock_frequency;
}
if ((index * sizeof(fdt32_t)) > clk_length)
{
return clock_frequency;
}
clk_phandle = fdt32_ld(pv + index);
pv_offset = fdt_node_offset_by_phandle(fdt, clk_phandle);
if (pv_offset < 0)
{
return clock_frequency;
}
return fdt_get_clock_frequency(fdt, pv_offset);
}
int fdt_node_index_from_label(FAR const char *node_label, int count)
{
int dev_number = 0;
size_t label_length;
if (!node_label)
{
return -ENOENT;
}
label_length = strnlen(node_label, CONFIG_PATH_MAX);
if (count > label_length || count <= 0)
{
return -EINVAL;
}
for (int i = 0; i < count; i++)
{
int number = atoi(&node_label[label_length - i]);
if (number)
{
dev_number = number;
}
}
/* atoi returns 0 on failure, so check that the number isn't actually 0 */
if (!dev_number && !isdigit(node_label[label_length - 1]))
{
return -ENOENT;
}
return dev_number;
}
void fdt_node_from_compat(FAR const void *fdt,
FAR const char **compatible_ids,
FAR void (*driver_callback)(FAR const void *fdt,
int offset))
{
int offset = 0;
DEBUGASSERT(compatible_ids);
DEBUGASSERT(driver_callback);
while (*compatible_ids)
{
while (true)
{
offset
= fdt_node_offset_by_compatible(fdt, offset, *compatible_ids);
if (offset == -FDT_ERR_NOTFOUND)
{
break;
}
if (!fdt_device_is_available(fdt, offset))
{
continue;
}
driver_callback(fdt, offset);
}
compatible_ids++;
}
}
int fdt_load_prop_u32(FAR const void *fdt, int offset,
FAR const char *property, int index,
FAR uint32_t *value)
{
DEBUGASSERT(property);
DEBUGASSERT(value);
int length;
const fdt32_t *pv = fdt_getprop(fdt, offset, property, &length);
if (!pv)
{
return -ENOENT;
}
if (index >= length)
{
return -EINVAL;
}
*value = fdt32_ld(pv + index);
return OK;
}