mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-12 07:57:21 +00:00
269 lines
9.8 KiB
Text
269 lines
9.8 KiB
Text
|
Driver Model Compiled-in Device Tree / Platform Data
|
||
|
====================================================
|
||
|
|
||
|
|
||
|
Introduction
|
||
|
------------
|
||
|
|
||
|
Device tree is the standard configuration method in U-Boot. It is used to
|
||
|
define what devices are in the system and provide configuration information
|
||
|
to these devices.
|
||
|
|
||
|
The overhead of adding device tree access to U-Boot is fairly modest,
|
||
|
approximately 3KB on Thumb 2 (plus the size of the DT itself). This means
|
||
|
that in most cases it is best to use device tree for configuration.
|
||
|
|
||
|
However there are some very constrained environments where U-Boot needs to
|
||
|
work. These include SPL with severe memory limitations. For example, some
|
||
|
SoCs require a 16KB SPL image which must include a full MMC stack. In this
|
||
|
case the overhead of device tree access may be too great.
|
||
|
|
||
|
It is possible to create platform data manually by defining C structures
|
||
|
for it, and referencing that data in a U_BOOT_DEVICE() declaration. This
|
||
|
bypasses the use of device tree completely, but is an available option for
|
||
|
SPL.
|
||
|
|
||
|
As an alternative, a new 'of-platdata' feature is provided. This converts
|
||
|
device tree contents into C code which can be compiled into the SPL binary.
|
||
|
This saves the 3KB of code overhead and perhaps a few hundred more bytes due
|
||
|
to more efficient storage of the data.
|
||
|
|
||
|
|
||
|
Caveats
|
||
|
-------
|
||
|
|
||
|
There are many problems with this features. It should only be used when
|
||
|
stricly necessary. Notable problems include:
|
||
|
|
||
|
- Device tree does not describe data types but the C code must define a
|
||
|
type for each property. Thesee are guessed using heuristics which
|
||
|
are wrong in several fairly common cases. For example an 8-byte value
|
||
|
is considered to be a 2-item integer array, and is byte-swapped. A
|
||
|
boolean value that is not present means 'false', but cannot be
|
||
|
included in the structures since there is generally no mention of it
|
||
|
in the device tree file.
|
||
|
|
||
|
- Naming of nodes and properties is automatic. This means that they follow
|
||
|
the naming in the device tree, which may result in C identifiers that
|
||
|
look a bit strange
|
||
|
|
||
|
- It is not possible to find a value given a property name. Code must use
|
||
|
the associated C member variable directly in the code. This makes
|
||
|
the code less robust in the face of device-tree changes. It also
|
||
|
makes it very unlikely that your driver code will be useful for more
|
||
|
than one SoC. Even if the code is common, each SoC will end up with
|
||
|
a different C struct and format for the platform data.
|
||
|
|
||
|
- The platform data is provided to drivers as a C structure. The driver
|
||
|
must use the same structure to access the data. Since a driver
|
||
|
normally also supports device tree it must use #ifdef to separate
|
||
|
out this code, since the structures are only available in SPL.
|
||
|
|
||
|
|
||
|
How it works
|
||
|
------------
|
||
|
|
||
|
The feature is enabled by CONFIG SPL_OF_PLATDATA. This is only available
|
||
|
in SPL and should be tested with:
|
||
|
|
||
|
#if CONFIG_IS_ENABLED(SPL_OF_PLATDATA)
|
||
|
|
||
|
A new tool called 'dtoc' converts a device tree file either into a set of
|
||
|
struct declarations, one for each compatible node, or a set of
|
||
|
U_BOOT_DEVICE() declarations along with the actual platform data for each
|
||
|
device. As an example, consider this MMC node:
|
||
|
|
||
|
sdmmc: dwmmc@ff0c0000 {
|
||
|
compatible = "rockchip,rk3288-dw-mshc";
|
||
|
clock-freq-min-max = <400000 150000000>;
|
||
|
clocks = <&cru HCLK_SDMMC>, <&cru SCLK_SDMMC>,
|
||
|
<&cru SCLK_SDMMC_DRV>, <&cru SCLK_SDMMC_SAMPLE>;
|
||
|
clock-names = "biu", "ciu", "ciu_drv", "ciu_sample";
|
||
|
fifo-depth = <0x100>;
|
||
|
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
|
||
|
reg = <0xff0c0000 0x4000>;
|
||
|
bus-width = <4>;
|
||
|
cap-mmc-highspeed;
|
||
|
cap-sd-highspeed;
|
||
|
card-detect-delay = <200>;
|
||
|
disable-wp;
|
||
|
num-slots = <1>;
|
||
|
pinctrl-names = "default";
|
||
|
pinctrl-0 = <&sdmmc_clk>, <&sdmmc_cmd>, <&sdmmc_cd>, <&sdmmc_bus4>;
|
||
|
vmmc-supply = <&vcc_sd>;
|
||
|
status = "okay";
|
||
|
u-boot,dm-pre-reloc;
|
||
|
};
|
||
|
|
||
|
|
||
|
Some of these properties are dropped by U-Boot under control of the
|
||
|
CONFIG_OF_SPL_REMOVE_PROPS option. The rest are processed. This will produce
|
||
|
the following C struct declaration:
|
||
|
|
||
|
struct dtd_rockchip_rk3288_dw_mshc {
|
||
|
fdt32_t bus_width;
|
||
|
bool cap_mmc_highspeed;
|
||
|
bool cap_sd_highspeed;
|
||
|
fdt32_t card_detect_delay;
|
||
|
fdt32_t clock_freq_min_max[2];
|
||
|
struct phandle_2_cell clocks[4];
|
||
|
bool disable_wp;
|
||
|
fdt32_t fifo_depth;
|
||
|
fdt32_t interrupts[3];
|
||
|
fdt32_t num_slots;
|
||
|
fdt32_t reg[2];
|
||
|
bool u_boot_dm_pre_reloc;
|
||
|
fdt32_t vmmc_supply;
|
||
|
};
|
||
|
|
||
|
and the following device declaration:
|
||
|
|
||
|
static struct dtd_rockchip_rk3288_dw_mshc dtv_dwmmc_at_ff0c0000 = {
|
||
|
.fifo_depth = 0x100,
|
||
|
.cap_sd_highspeed = true,
|
||
|
.interrupts = {0x0, 0x20, 0x4},
|
||
|
.clock_freq_min_max = {0x61a80, 0x8f0d180},
|
||
|
.vmmc_supply = 0xb,
|
||
|
.num_slots = 0x1,
|
||
|
.clocks = {{&dtv_clock_controller_at_ff760000, 456}, {&dtv_clock_controller_at_ff760000, 68}, {&dtv_clock_controller_at_ff760000, 114}, {&dtv_clock_controller_at_ff760000, 118}},
|
||
|
.cap_mmc_highspeed = true,
|
||
|
.disable_wp = true,
|
||
|
.bus_width = 0x4,
|
||
|
.u_boot_dm_pre_reloc = true,
|
||
|
.reg = {0xff0c0000, 0x4000},
|
||
|
.card_detect_delay = 0xc8,
|
||
|
};
|
||
|
U_BOOT_DEVICE(dwmmc_at_ff0c0000) = {
|
||
|
.name = "rockchip_rk3288_dw_mshc",
|
||
|
.platdata = &dtv_dwmmc_at_ff0c0000,
|
||
|
};
|
||
|
|
||
|
The device is then instantiated at run-time and the platform data can be
|
||
|
accessed using:
|
||
|
|
||
|
struct udevice *dev;
|
||
|
struct dtd_rockchip_rk3288_dw_mshc *plat = dev_get_platdata(dev);
|
||
|
|
||
|
This avoids the code overhead of converting the device tree data to
|
||
|
platform data in the driver. The ofdata_to_platdata() method should
|
||
|
therefore do nothing in such a driver.
|
||
|
|
||
|
|
||
|
How to structure your driver
|
||
|
----------------------------
|
||
|
|
||
|
Drivers should always support device tree as an option. The of-platdata
|
||
|
feature is intended as a add-on to existing drivers.
|
||
|
|
||
|
Your driver should directly access the platdata struct in its probe()
|
||
|
method. The existing device tree decoding logic should be kept in the
|
||
|
ofdata_to_platdata() and wrapped with #ifdef.
|
||
|
|
||
|
For example:
|
||
|
|
||
|
#include <dt-structs.h>
|
||
|
|
||
|
struct mmc_platdata {
|
||
|
#if CONFIG_IS_ENABLED(SPL_OF_PLATDATA)
|
||
|
/* Put this first */
|
||
|
struct dtd_mmc dtplat;
|
||
|
#endif
|
||
|
/*
|
||
|
* Other fields can go here, to be filled in by decoding from
|
||
|
* the device tree. They will point to random memory in the
|
||
|
* of-plat case.
|
||
|
*/
|
||
|
int fifo_depth;
|
||
|
};
|
||
|
|
||
|
static int mmc_ofdata_to_platdata(struct udevice *dev)
|
||
|
{
|
||
|
#if !CONFIG_IS_ENABLED(SPL_OF_PLATDATA)
|
||
|
struct mmc_platdata *plat = dev_get_platdata(dev);
|
||
|
const void *blob = gd->fdt_blob;
|
||
|
int node = dev->of_offset;
|
||
|
|
||
|
plat->fifo_depth = fdtdec_get_int(blob, node, "fifo-depth", 0);
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mmc_probe(struct udevice *dev)
|
||
|
{
|
||
|
struct mmc_platdata *plat = dev_get_platdata(dev);
|
||
|
#if CONFIG_IS_ENABLED(SPL_OF_PLATDATA)
|
||
|
struct dtd_mmc *dtplat = &plat->dtplat;
|
||
|
|
||
|
/* Set up the device from the dtplat data */
|
||
|
writel(dtplat->fifo_depth, ...)
|
||
|
#else
|
||
|
/* Set up the device from the plat data */
|
||
|
writel(plat->fifo_depth, ...)
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static const struct udevice_id mmc_ids[] = {
|
||
|
{ .compatible = "vendor,mmc" },
|
||
|
{ }
|
||
|
};
|
||
|
|
||
|
U_BOOT_DRIVER(mmc_drv) = {
|
||
|
.name = "mmc",
|
||
|
.id = UCLASS_MMC,
|
||
|
.of_match = mmc_ids,
|
||
|
.ofdata_to_platdata = mmc_ofdata_to_platdata,
|
||
|
.probe = mmc_probe,
|
||
|
.priv_auto_alloc_size = sizeof(struct mmc_priv),
|
||
|
.platdata_auto_alloc_size = sizeof(struct mmc_platdata),
|
||
|
};
|
||
|
|
||
|
|
||
|
In the case where SPL_OF_PLATDATA is enabled, platdata_auto_alloc_size is
|
||
|
ignored, and the platform data points to the C structure data. In the case
|
||
|
where device tree is used, the platform data is allocated, and starts
|
||
|
zeroed. In this case the ofdata_to_platdata() method should set up the
|
||
|
platform data.
|
||
|
|
||
|
SPL must use either of-platdata or device tree. Drivers cannot use both.
|
||
|
The device tree becomes in accessible when CONFIG_SPL_OF_PLATDATA is enabled,
|
||
|
since the device-tree access code is not compiled in.
|
||
|
|
||
|
|
||
|
Internals
|
||
|
---------
|
||
|
|
||
|
The dt-structs.h file includes the generated file
|
||
|
(include/generated//dt-structs.h) if CONFIG_SPL_OF_PLATDATA is enabled.
|
||
|
Otherwise (such as in U-Boot proper) these structs are not available. This
|
||
|
prevents them being used inadvertently.
|
||
|
|
||
|
The dt-platdata.c file contains the device declarations and is is built in
|
||
|
spl/dt-platdata.c.
|
||
|
|
||
|
Some phandles (thsoe that are recognised as such) are converted into
|
||
|
points to platform data. This pointer can potentially be used to access the
|
||
|
referenced device (by searching for the pointer value). This feature is not
|
||
|
yet implemented, however.
|
||
|
|
||
|
The beginnings of a libfdt Python module are provided. So far this only
|
||
|
implements a subset of the features.
|
||
|
|
||
|
The 'swig' tool is needed to build the libfdt Python module.
|
||
|
|
||
|
|
||
|
Future work
|
||
|
-----------
|
||
|
- Add unit tests
|
||
|
- Add a sandbox_spl functional test
|
||
|
- Consider programmatically reading binding files instead of device tree
|
||
|
contents
|
||
|
- Drop the device tree data from the SPL image
|
||
|
- Complete the phandle feature
|
||
|
- Get this running on a Rockchip board
|
||
|
- Move to using a full Python libfdt module
|
||
|
|
||
|
--
|
||
|
Simon Glass <sjg@chromium.org>
|
||
|
6/6/16
|