Version 18 (modified by 2 months ago) ( diff ) | ,
---|
Device Tree
The modern linux kernel as well as the modern U-Boot bootloader use a system called 'devicetree' to describe hardware in a consistent fashion to avoid needing custom 'board support' files (compiled code) for boards.
For a device-tree controlled U-Boot (newport/venice) the Secondary Program Loader (SPL) is in charge of choosing a dtb (flattened device tree binary) to pass to U-Boot.
For a device-tree controlled Linux kernel the bootloader will load a dtb, modify it where necessary, and pass it to Linux.
Because board specific details such as memory size, MAC addresses and serial numbers are relied to the kernel via device-tree the bootloader will modify the device-tree with these details before passing it to the kernel.
Device Tree Terminology
The following terminology is commonly used:
- dt - device tree
- fdt - Flattened device tree (binary image)
- dtc - device tree compiler (the tool used to compile and de-compile device-tree)
The following file extensions are typically used:
- dts - Board level definitions
- dtsi - SoC level definitions
- dtb - Binary blob of device tree loaded by bootloader
- dtbo - Binary blob of device tree overlay (aka fragment) loaded and applied by bootloader
Example Device Tree File
Device tree files exist in:
- Linux kernel tree:
- arch/arm/boot/dts for 32bit ARM
- arch/arm64/boot/dts for 64bit ARM
- U-Boot bootloader:
- arch/arm/dts for ARM
For Gateworks Kernels:
- Venice: arch/arm64/boot/dts/freescale imx8*-venice-*.dts*
- Newport: dts-newport gw*.dts* (Note Newport dt source is not in the Mainline Linux kernel)
- Ventana: arch/arm/boot/dts imx6*-gw-*.dts*
Accessing devicetree from the Bootloader
The U-Boot bootloader is responsible for Loading a device-tree, making any adjustments necessary, and passing it to the kernel.
If desired during this process you can use the ftd
U-boot command to access/modify the devicetree before the kernel is booted. How and where to do this would depend greatly on the U-Boot bootscript used to boot your OS.
Examples:
- Venice: disable PCI:
setenv fsload 'load mmc 2:1' run loadfdt && fdt addr $fdt_addr_r fdt resize fdt set /soc@0/pcie@33800000 status disabled
- Ventana: display the fdt on a NAND flash board:
setenv fsload 'ubifsload' ubi part rootfs && ubifsmount ubi0:rootfs run loadfdt && fdt addr ${fdt_addr} && fdt boardsetup fdt print
- Ventana: disable PCI:
setenv fsload 'ubifsload' ubi part rootfs && ubifsmount ubi0:rootfs run loadfdt && fdt addr ${fdt_addr} && fdt boardsetup fdt resize fdt set /soc/pcie@0x01000000 status disabled
Important notes regarding the 'fdt set' command 'value' parameter:
- numbers can be represented as decimal or as hex with a '0x' prefix
- an array of cells is specified as a list of numbers enclosed in < and >, ie <0x00112233 4 05>
- a byte stream can be represented as a list of numbers enclosed in [ and ], ie [00 11 22 .. nn]
- a 32bit integer is a 4-byte MSB stream so a hex value of 0x1 would be represented as [00 00 00 01]
- if the value does not start with a < or a [ it is treated as a string (quotes are stripped if present)
fixfdt script
If using the Gateworks BSP's the fixfdt
env variable if set will be run as a script during the process as a feature of the Gateworks boot scripts.
Examples:
- Venice: The
loadfdt
script will load the dt to the address specified in thefdt_addr_r
env var- disable PCI via
fixfdt
script:setenv fixfdt 'fdt addr $fdt_addr_r && fdt resize && fdt set /soc@0/pcie@33800000 status disabled' saveenv
- limit PCI link to Gen 1 (default is Gen2 for Venice)
setenv fixfdt 'fdt addr $fdt_addr_r && fdt resize && fdt set /soc@0/pcie@33800000 fsl,max-link-speed [00 00 00 01]' saveenv
- change the pinmux registers for sdhc3:
setenv fixfdt 'fdt addr $fdt_addr_r && fdt resize && fdt set /soc@0/bus@30000000/pinctrl@30330000/usdhc3-200mhzgrp fsl,pins <0x00000138 0x000003a0 0x00000000 0x00000012 0x00000000 0x00000182 0x0000013c 0x000003a4 0x00000000 0x00000002 0x00000000 0x000001d6 0x0000011c 0x00000384 0x00000000 0x00000002 0x00000000 0x000001d6 0x00000120 0x00000388 0x00000000 0x00000002 0x00000000 0x000001d6 0x00000124 0x0000038c 0x00000000 0x00000002 0x00000000 0x000001d6 0x00000128 0x00000390 0x00000000 0x00000002 0x00000000 0x000001d6 0x00000130 0x00000398 0x00000000 0x00000002 0x00000000 0x000001d6 0x00000100 0x00000368 0x00000000 0x00000002 0x00000000 0x000001d6 0x00000104 0x0000036c 0x00000000 0x00000002 0x00000000 0x000001d6 0x00000108 0x00000370 0x00000000 0x00000002 0x00000000 0x000001d6 0x000000fc 0x00000364 0x00000000 0x00000002 0x00000000 0x00000196>
- disable PCI via
- Newport: U-Boot uses a live dt which is also passed to the kernel (dtb is not loaded) so you can alter the dt before running your normal bootscript
- Disable PEM2 (PEM0=pci@87e0c0000000, PEM1=pci@87e0c1000000, PEM2=pci@87e0c2000000)
setenv bootscript 'fdt /soc@0/pci@87e0c2000000 status disabled; run distro_bootcmd' saveenv
- Disable PEM2 (PEM0=pci@87e0c0000000, PEM1=pci@87e0c1000000, PEM2=pci@87e0c2000000)
- Ventana: The
loadfdt
script will load the dt to the address specified in thefdt_addr
env var- disable PCI via
fixfdt
script:setenv fixfdt 'fdt addr ${fdt_addr} && fdt resize && fdt set /soc/pcie@0x01000000 status disabled' saveenv
- disable PCI via
You can verify your changes took effect by checking the values in Linux via /proc/device-tree
or by using dtc
to de-compile the dt from /sys/firmware/devicetree/base
:
# de-compile dt via dtc dtc -I fs -O dts /sys/firmware/devicetree/base # hexdump prop from /proc/device-tree hexdump -C /proc/device-tree/soc\@0/pcie\@33800000/fsl\,max-link-speed
dt overlay
Note also that some bootloader environments such as Venice offer dt overlay support where the bootloader can apply overlays to a dt to alter it before passing it to the kernel. In the case of venice you can set the fdt_overlay
env variable to a list of files to load and apply as an overlay to modify the base fdt which is typically used for adding features from an add-on board like a display or camera.
See below
Accessing devicetree from Linux
If the kernel is configured with CONFIG_PROC_DEVICETREE and procfs is enabled (both are enabled on Gateworks default kernels), you can access the devicetree via /proc/device-tree
. This can be useful to obtain information about the board that the bootloader configured, such as board model and serial number.
Additionally the /sys/firmware/devicetree/base
sysfs file represents the binary devicetree which you can use with the device tree compiler.
Examples:
- show board model:
echo $(cat /proc/device-tree/board)
- show board serialnumber
echo $(cat /proc/device-tree/serial-number)
- show devicetree compatible node (this describes which device-tree was used as there is one per base-board design):
echo $(cat /proc/device-tree/compatible)
- show chosen bootargs (the bootargs passed in by the bootloader, same as /proc/cmdline):
echo $(cat /proc/device-tree/chosen/bootargs)
Device Tree Overlays (aka Fragments)
The device-tree compiler and the U-Boot bootloader support the concept of applying changes to a base device-tree to make it easy to implement add-on boards such as a touchscreen display or camera that require dt bindings.
Some specific requirements:
- requires '/plugin/' specified
- requires '&{/} { compatible };' node that specifies the board this fragment is compatible with
- requires using handles of nodes that are defined in the base devicetree
- requires compiling with the-@ flag
- requires device-tree syntax (so if you are using C defines/includes make sure you run through the C preprocessor before compiling)
Examples:
- add a SPI NOR flash device on an off-board SPI connector. Such a device has a Linux kernel driver and requires using device-tree bindings:
/dts-v1/; /plugin/; &{/} { compatible = "gw,imx8mm-gw73xx-0x", "fsl,imx8mm"; }; /* off-board header */ &ecspi2 { flash@0 { compatible = "spansion,m25p128", "jedec,spi-nor"; spi-max-frequency = <30000000>; reg = <0>; #address-cells = <1>; #size-cells = <1>; m25p,fast-read; partition@0 { label = "data"; reg = <0x0 0x1000000>; }; }; };
- Note the compatible string specifies the board this is compatible with
- Note the board in question's base dt specifies the ecspi2 handle which we modify here
- To compile this fragment:
dtc -@ -I dts -O dtb -o imx8mm-gw73xx-0x-spinor.dtbo imx8mm-gw73xx-0x-spinor.dts
- And to use it on a Venice imx8mm-gw73xx-0x make sure the dtbo exits alongside the kernel in the directory the bootscript resides (ie /boot)
setenv fdt_overlays "imx8mm-gw73xx-0x-spinor.dtbo" saveenv
- repurpose the I2C3 and SPI2 pins on a GW730x to GPIO's to be used in userspace via gpiod/sysfs:
- the dts framgment here has C style includes required for the pinmux definitions and the gpio definitions so we use cpp to pre-process it before compiling
- the includes here are from the Linux kernel so you must have the linux kernel source to compile this, place the file in the proper directory per the includes (imx8mm-pinfunc.h is in arch/arm64/boot/dts/freescale so that is where we put the file in our example), and compile from the correct directory such that the includes can be found
# get the kernel source git clone https://github.com/Gateworks/linux-venice.git cd linux-venice cat << EOF > arch/arm64/boot/dts/freescale/imx8mm-venice-gw73xx-0x-gpio.dts /* * GW73xx GPIO: * - repurpose ecspi2 CLK/MISO/MOSI/CS0 as GPIO * - repurpose i2c3 CLK/DATA as GPIO */ #include <dt-bindings/gpio/gpio.h> #include "imx8mm-pinfunc.h" /dts-v1/; /plugin/; &{/} { compatible = "gw,imx8mm-gw73xx-0x"; }; &gpio5 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpio5_hog>; /* gpio-hog nodes allow you to give gpios a name and a default state: * define one of 'input', 'output-low', or 'output-high' properties */ gpio5_io10 { gpio-hog; gpios = <10 GPIO_ACTIVE_HIGH>; input; line-name = "gpio5_io10"; }; gpio5_io11 { gpio-hog; gpios = <11 GPIO_ACTIVE_HIGH>; input; line-name = "gpio5_io11"; }; gpio5_io12 { gpio-hog; gpios = <12 GPIO_ACTIVE_HIGH>; input; line-name = "gpio5_io12"; }; gpio5_io13 { gpio-hog; gpios = <13 GPIO_ACTIVE_HIGH>; input; line-name = "gpio5_io13"; }; gpio5_io18 { gpio-hog; gpios = <18 GPIO_ACTIVE_HIGH>; input; line-name = "gpio5_io18"; }; gpio5_io19 { gpio-hog; gpios = <19 GPIO_ACTIVE_HIGH>; input; line-name = "gpio5_io19"; }; }; &i2c3 { status = "disabled"; }; &ecspi2 { status = "disabled"; }; &iomuxc { /* pinmux: refer to IMX8M Reference Manuals for the bit configuration * - the first definition comes from imx8mm-pinfunc.h and sets the * pins NUX_CTL register and input select * - the second definition sets the PAD_CTL register that configures * things like drive strength, slew rate, pull-up, pull-down, and hysteresis. * If you want to be able to read back the pin state you need to set the SION * bit by setting bit 30 (as is done below) */ pinctrl_gpio5_hog: gpio5hoggrp { fsl,pins = < MX8MM_IOMUXC_ECSPI2_SCLK_GPIO5_IO10 0x40000146 /* SION, pull-up, 6x drive str */ MX8MM_IOMUXC_ECSPI2_MOSI_GPIO5_IO11 0x40000106 /* SION, pull-down, 6x drive str */ MX8MM_IOMUXC_ECSPI2_MISO_GPIO5_IO12 0x40000006 /* SION, 6x drive-str */ MX8MM_IOMUXC_ECSPI2_SS0_GPIO5_IO13 0x40000146 MX8MM_IOMUXC_I2C3_SCL_GPIO5_IO18 0x40000146 MX8MM_IOMUXC_I2C3_SDA_GPIO5_IO19 0x40000146 >; }; }; EOF # run through the C pre-processor to handle the includes/macros/defines cpp -nostdinc -I include -I arch -undef -x assembler-with-cpp \ arch/arm64/boot/dts/freescale/imx8mm-venice-gw73xx-0x-gpio.dts \ imx8mm-venice-gw73xx-0x-gpio.dts.tmp # compile with Device Tree Compiler dtc -@ -i include/ -I dts -O dtb -o imx8mm-venice-gw73xx-0x-gpio.dtbo \ imx8mm-venice-gw73xx-0x-gpio.dts.tmp
- And to use it on a Venice imx8mm-gw73xx-0x make sure the dtbo exits alongside the kernel in the directory the bootscript resides (ie /boot)
setenv fdt_overlays "imx8mm-venice-gw73xx-0x-gpio.dtbo" saveenv
- And to use it on a Venice imx8mm-gw73xx-0x make sure the dtbo exits alongside the kernel in the directory the bootscript resides (ie /boot)
- repurpose the I2C3 and SPI2 pins on a GW730x to GPIO's (like above) but bind 4 of them to the gpio-keys driver to use as up/down/right/left buttons and bind 2 of them to the gpio-led driver to use as leds via /sys/class/led
- the dts framgment here has C style includes required for the pinmux definitions and the gpio definitions so we use cpp to pre-process it before compiling
- we use gpio-keys1 and led-controller1 node names so as not to collide with gpio-keys and led-controller nodes already defined in the gw73xx-0x device-tree
- the includes here are from the Linux kernel so you must have the linux kernel source to compile this, place the file in the proper directory per the includes (imx8mm-pinfunc.h is in arch/arm64/boot/dts/freescale so that is where we put the file in our example), and compile from the correct directory such that the includes can be found
# get the kernel source git clone https://github.com/Gateworks/linux-venice.git cd linux-venice cat << EOF > arch/arm64/boot/dts/freescale/imx8mm-venice-gw73xx-0x-gpio.dts /* * GW73xx GPIO: * - repurpose ecspi2 CLK/MISO/MOSI/CS0 as GPIO buttons tied to linux input events * - repurpose i2c3 CLK/DATA as GPIO controlled LED's tied to led class */ #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/input/linux-event-codes.h> #include <dt-bindings/leds/common.h> #include "imx8mm-pinfunc.h" /dts-v1/; /plugin/; &{/} { compatible = "gw,imx8mm-gw73xx-0x"; gpio-keys1 { compatible = "gpio-keys"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpio_keys1>; key-left { label = "left"; gpios = <&gpio5 10 GPIO_ACTIVE_LOW>; linux,code = <KEY_LEFT>; }; key-right { label = "right"; gpios = <&gpio5 11 GPIO_ACTIVE_LOW>; linux,code = <KEY_RIGHT>; }; key-up { label = "up"; gpios = <&gpio5 12 GPIO_ACTIVE_LOW>; linux,code = <KEY_UP>; }; key-down { label = "down"; gpios = <&gpio5 13 GPIO_ACTIVE_LOW>; linux,code = <KEY_DOWN>; }; }; led-controller1 { compatible = "gpio-leds"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpio_leds1>; led-0 { function = LED_FUNCTION_STATUS; color = <LED_COLOR_ID_GREEN>; gpios = <&gpio5 18 GPIO_ACTIVE_HIGH>; default-state = "on"; linux,default-trigger = "heartbeat"; }; led-1 { function = LED_FUNCTION_STATUS; color = <LED_COLOR_ID_RED>; gpios = <&gpio5 19 GPIO_ACTIVE_HIGH>; default-state = "off"; }; }; }; &i2c3 { status = "disabled"; }; &ecspi2 { status = "disabled"; }; &iomuxc { /* pinmux: refer to IMX8M Reference Manuals for the bit configuration * - the first definition comes from imx8mm-pinfunc.h and sets the * pins NUX_CTL register and input select * - the second definition sets the PAD_CTL register that configures * things like drive strength, slew rate, pull-up, pull-down, and hys teresis. * If you want to be able to read back the pin state you need to set the SION * bit by setting bit 30 (as is done below) * below the pins are configured with internal pull-up and 6x drive str */ pinctrl_gpio_keys1: gpiokeys1grp { fsl,pins = < MX8MM_IOMUXC_ECSPI2_SCLK_GPIO5_IO10 0x146 MX8MM_IOMUXC_ECSPI2_MOSI_GPIO5_IO11 0x146 MX8MM_IOMUXC_ECSPI2_MISO_GPIO5_IO12 0x146 MX8MM_IOMUXC_ECSPI2_SS0_GPIO5_IO13 0x146 >; }; pinctrl_gpio_leds1: gpioleds1grp { fsl,pins = < MX8MM_IOMUXC_I2C3_SCL_GPIO5_IO18 0x146 MX8MM_IOMUXC_I2C3_SDA_GPIO5_IO19 0x146 >; }; }; EOF # run through the C pre-processor to handle the includes/macros/defines cpp -nostdinc -I include -I arch -undef -x assembler-with-cpp \ arch/arm64/boot/dts/freescale/imx8mm-venice-gw73xx-0x-gpio.dts \ imx8mm-venice-gw73xx-0x-gpio.dts.tmp # compile with Device Tree Compiler dtc -@ -i include/ -I dts -O dtb -o imx8mm-venice-gw73xx-0x-gpio.dtbo \ imx8mm-venice-gw73xx-0x-gpio.dts.tmp
- And to use it on a Venice imx8mm-gw73xx-0x make sure the dtbo exits alongside the kernel in the directory the bootscript resides (ie /boot)
setenv fdt_overlays "imx8mm-venice-gw73xx-0x-gpio.dtbo" saveenv
- And to use it on a Venice imx8mm-gw73xx-0x make sure the dtbo exits alongside the kernel in the directory the bootscript resides (ie /boot)
Notes:
- Support for compiling dtbo files appeared in the Linux kernel somewhere between 5.10 and 5.15
- Gateworks uses dtbo's to provide support for adding a RaspberryPi v2.0 camera module and a RaspberryPi DFROBOT touchscreen display to a gw72xx/gw73xx baseboard for example.
Gateworks Product family notes
Venice
For Venice:
- U-Boot is controlled by device-tree, the DTB used is decided upon by the SPL based on EEPROM contents describing the board model, exists in a FIT image along with the ARM Trusted Firmware (ATF) and U-Boot proper and is specified by a 'DTB' banner before the SPL boots U-Boot proper.
- The Linux device-tree comes from the kernel source tree (arch/arm64/boot/dts/freescale) and is loaded and modified by the bootloader. The
loadfdt
script is used to load the device-tree blob (dtb) and will attempt to load several board dtb files starting with the most specific names possible including board PCB revisions and ending with a generic file represending the baseboard. - A list of optional files specified by
fdt_overlays
will be loaded and applied as overlays and if definedfixfdt
will be run as well.
Newport
For Newport:
- U-Boot is controlled by device-tree, the dtb used is loaded by BDK from the embedded FATFS based on the model from the board EEPROM.
- DTS source files are in the dts-newport repo and built by the BSP Makefile and populated in the FATFS
- The bootscript uses the U-Boot 'live' device-tree used by U-Boot for Linux as well (by passing the bootm command '$fdtcontroladdr'). This is not required and if needed you could load your own dtb and pass the address instead however note that the DTS files for CN80xx do not exist in the kernel tree.
Ventana
For Ventana:
- The latest Gateworks gateworks_v2017.05 U-Boot is not controlled by device tree and instead board support in U-Boot is handled by code based off the board model from the EEPROM
- DTS source files are in the Linux kernel in the arch/arm/boot/dts directory and placed with the kernel uImage in the same directory as the bootscript in the root filesystem (ie /boot)
- The bootloader
loadfdt
script is used to load the device-tree blob (dtb) in all standard boot cases for the Gateworks bootloader. This script attempts to load a dtb three times, each time with a more generic filename (from thefdt_file
,fdt_file1
, andfdt_file2
) env variable which if not set (overridden) will get set per the board model defined in the Gateworks EEPROM. For example a GW5400-C would have the following:Ventana > print fdt_file fdt_file=imx6q-gw5400-c.dtb Ventana > print fdt_file1 fdt_file1=imx6q-gw5400.dtb Ventana > print fdt_file2 fdt_file2=imx6q-gw54xx.dtb
Adding New Devices to the Device Tree
For customers interested in adding a new device to a board's device-tree (for example something connected to an off-board header) see the following examples:
Device Tree Compiler
Compiling the Device Tree
If you need to change the device-tree you can easily compile it on a Linux system using the dtc app from the device-tree-compiler package:
apt-get install device-tree-compiler dtc -O dtb -o imx6dl-gw51xx.dtb imqx6dl-gw51xx.dts
De-Compiling the Device Tree
You can also de-compile a dtb back to a dts:
dtc -I dtb -O dts imx6dl-gw51xx.dtb > imx6dl-gw51xx.dts
You can also decompile the device tree of a running system:
dtc -I fs -O dts /sys/firmware/devicetree/base > MySBC.dts