The following steps allow to make use of device tree overlay files, a definition of device tree overlay provided by kernel.org is the next:
"A Devicetree’s overlay purpose is to modify the kernel’s live tree, and have the modification affecting the state of the kernel in a way that is reflecting the changes. Since the kernel mainly deals with devices, any new device node that result in an active device should have it created while if the device node is either disabled or removed all together, the affected device should be deregistered."
Knowing that, in this post will be used as an example the baseboard "i.MX 93 EVK" and will be added with device tree overlay an LVDS panel, adding an automatic detection from u-boot, and will be used a host with linux version Ubuntu 20.04.2.
Note: It only works for linux kernel version 6.6.3-nanbield onward.
This section explains all about device tree overlay compilation and building, to create a .dtso file, the equivalent of .dts for overlays, adding some difference between them, using as base the linux-imx repository. It can be downloaded from the following repository:
git clone https://github.com/nxp-imx/linux-imx.git -b <branch version>
Branch version used by this post "lf-6.6.3-1.0.0".
It can be similar to a device tree source (.dts) but it had little difference between them, there are some difference in the next list:
{
/* ignored properties by the overlay */
fragment@0 { /* first child node */
target=<phandle>; /* phandle target of the overlay */
or
target-path="/path"; /* target path of the overlay */
__overlay__ {
property-a; /* add property-a to the target */
node-a { /* add to an existing, or create a node-a */
...
};
};
}
fragment@1 { /* second child node */
...
};
/* more fragments follow */
}
Using as an example the file imx93-11x11-evk-boe-wxga-lvds-panel.dts located in the previous repository file direction <linux-imx path>/arch/arm64/boot/dts/freescale/ using it as a base tree:
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright 2022 NXP
*/
#include "imx93-11x11-evk.dts"
/ {
lvds_backlight: lvds_backlight {
compatible = "pwm-backlight";
pwms = <&adp5585pwm 0 100000 0>;
enable-gpios = <&adp5585gpio 8 GPIO_ACTIVE_HIGH>;
power-supply = <®_vdd_12v>;
status = "okay";
brightness-levels = < 0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99
100>;
default-brightness-level = <80>;
};
...
};
...
&adv7535 {
status = "disabled";
};
...
imx93-11x11-evk-boe-wxga-lvds-panel.dts
Using the previous points and making use of fragments, if we want adapt the node lvds_backlight as fragment, it will be added in the section of overlay, and adding it to a target-path "/":
#include <dt-bindings/interrupt-controller/irq.h>
#include "imx93-pinfunc.h"
#include <dt-bindings/gpio/gpio.h>
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target-path = "/";
__overlay__ {
lvds_backlight: lvds_backlight {
compatible = "pwm-backlight";
pwms = <&adp5585pwm 0 100000 0>;
enable-gpios = <&adp5585gpio 8 GPIO_ACTIVE_HIGH>;
power-supply = <®_vdd_12v>;
status = "okay";
brightness-levels = < 0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99
100>;
default-brightness-level = <80>;
};
};
};
...
};
imx93-11x11-evk-test-lvds-panel.dtso
In the case of adding a property to an existing node, it will look in the following way using as example the node adv7535.
...
/ {
...
fragment@2 {
target = <&adv7535>;
__overlay__ {
status = "disabled";
};
};
...
};
imx93-11x11-evk-boe-wxga-lvds-panel.dts
At the end of this post, will be attach the complete file used for LVDS panel named as imx93-11x11-evk-test-lvds-panel.dtso
To compile the previous .dtso it's necessary to include it to linux-imx repository, linux device tree overlay was included in BSP from version 6.6.3-nanbield onward in Makefile, so it's only necessary adding it as files to be compiled as .dtso, at the end of the post will be a patch file named as linux-imx-makefile.patch to add LVDS-panel to Makefile from branch lf-6.6.3-1.0.0
<overlay without extension>-dtbs := <file to be overlayed>.dtb <overlay>.dtbo
Example of how to add LVDS panel to makefile
imx93-11x11-evk-test-lvds-panel-dtbs := imx93-11x11-evk.dtb imx93-11x11-evk-test-lvds-panel.dtbo
Makefile
$ cd <linux-imx path>/
$ make -j$(nproc --all) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- imx_v8_defconfig$ make -j $(nproc --all) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- freescale/<overlay>.dtbo
as example for LVDS panel
$ make -j $(nproc --all) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- freescale/imx93-11x11-evk-test-lvds-panel.dtbo
It will compile the device tree blob overlay to use.
scp ./<overlay>.dtbo root@<ip>:/run/media/<memory section used>
This section explain the procedure to load a device tree overlay, it will be from u-boot explaining commands used and using the LVDS panel as an example.
Before applying, it's necessary had a device tree loaded so looking around in the process of booting in a i.MX 93 from u-boot, this process is defined by the enviroment variable "bsp_bootcmd" that calls the variable mmcboot, and looking what does these variables, it can be look in the following sentence:
bsp_bootcmd=echo Running BSP bootcmd ...; mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if test ${sec_boot} = yes; then if run loadcntr; then run mmcboot; else run netboot; fi; else if run loadimage; then run mmcboot; else run netboot; fi; fi; fi; fi;
mmcboot=echo Booting from mmc ...; run mmcargs; if test ${sec_boot} = yes; then if run auth_os; then run boot_os; else echo ERR: failed to authenticate; fi; else if test ${boot_fit} = yes || test ${boot_fit} = try; then bootm ${loadaddr}; else if run loadfdt; then run boot_os; else echo WARN: Cannot load the DT; fi; fi;fi;
but reducing it in a normal situation, ignoring if else case and echoes, it can be simplify to:
mmc dev ${mmcdev}; run loadimage; run mmcargs; run loadfdt; run boot_os;
the device tree is load is in the section "run loadfdt" with fatload in his definition:
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr_r} ${fdtfile}
So, it's necessary to applying device tree overlay after "run loadfdt".
To load correctly an overlay it's necessary to following some steps:
The full sentence to apply it, it's the following u-boot command:
u-boot=> setexpr fdtovaddr ${fdt_addr} + 0xF0000; setexpr fdt_buffer 16384; fdt addr ${fdt_addr} && fdt resize ${fdt_buffer}; fatload mmc ${mmcdev}:${mmcpart} ${fdtovaddr} <overlay>.dtbo && fdt apply ${fdtovaddr};
First of all, setexpr it's just to create a new variable, in this case these variable is an integer.
Spliting the previously command we can found the steps to applying it.
Remembering about load overlay needs to be executed after loadfdt, it's possible to save the previous command to a variable and executing it after loadfdt with setexpr, in this case using as example lvds test.
u-boot=> setenv loadoverlay "setexpr fdtovaddr ${fdt_addr} + 0xF0000; setexpr fdt_buffer 16384; fdt addr $\{fdt_addr\} && fdt resize $\{fdt_buffer\}; fatload mmc $\{mmcdev\}:$\{mmcpart\} $\{fdtovaddr\} imx93-11x11-evk-test-lvds-panel.dtbo && fdt apply $\{fdtovaddr\};"
and modifying mmcboot with loadoverlay after loadfdt
u-boot=> setenv mmcboot "run mmcargs; run loadfdt; run loadoverlay; run boot_os;"
to save the environment variables created, it can be saved from u-boot wit the following command.
u-boot=> saveenv
At the end, boot imx93
u-boot=> boot
The LVDS panel should be working using the original dtb (imx93-11x11-evk.dtb) applied the overlay.
This section explain how can be automatize the u-boot load overlay using an LVDS panel, it can vary depending the device to used for, the method used is detecting it in u-boot initialization and if found any device it will generate an environment variable. All the steps was using as a base uboot-imx repository, it can be downloaded from the following repository, at the end of this post will be a patch with the changes.
git clone https://github.com/nxp-imx/uboot-imx.git -b <branch version>
Branch version used "lf-6.6.3-1.0.0".
Knowing more about LVDS Panel used by imx93 it's really hard know more information about registers, so in this example will be limited to detect that is connected the address to a corresponding bus from touch controller.
To know i2c address and bus used by LVDS panel it was used searching it from the original device tree in the next section:
&lpi2c1 {
exc80h60: touch@2a {
compatible = "eeti,exc80h60";
reg = <0x2a>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ctp_int>;
/*
* Need to do hardware rework here:
* remove R131, short R181
*/
interrupt-parent = <&gpio2>;
interrupts = <21 IRQ_TYPE_LEVEL_LOW>;
reset-gpios = <&pcal6524 17 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
imx93-11x11-evk-boe-wxga-lvds-panel.dts
Previous node is related with touch controller from LVDS using lpi2c1, the first channel of i2c corresponding to i2c bus 0, and the register used express the address used to be detected by device tree, in this case was the address 0x2A.
About how it can be detected touch controller from u-boot, this procedure use a function named as "board_late_init", it can be found by his definition from u-boot readme:
Board initialization settings:
------------------------------
During Initialization u-boot calls a number of board specific functions
to allow the preparation of board specific prerequisites, e.g. pin setup
before drivers are initialized. To enable these callbacks the
following configuration macros have to be defined. Currently this is
architecture specific, so please check arch/your_architecture/lib/board.c
typically in board_init_f() and board_init_r().
- CONFIG_BOARD_EARLY_INIT_F: Call board_early_init_f()
- CONFIG_BOARD_EARLY_INIT_R: Call board_early_init_r()
- CONFIG_BOARD_LATE_INIT: Call board_late_init()
u-boot README
In the case of i.MX 93 this function can be found in the next path <u-boot path>/board/freescale/imx93_evk/imx93_evk.c. Using the library included, "uclass.h", it will create a function that, if detect in the bus 0 (LVDS i2c bus) the address 0x2A (i2c LVDS address), it will create an environment variable with the overlay used, it can be set with the function env_set(<String with the name of the variable>, <String with the content of the variable>), the following function can detect and create the environment variable mentioned, creating it with the name "device-tree-overlay" with the content "lvds-panel".
#define LVDS_TOUCH_I2C_BUS 0
#define LVDS_TOUCH_I2C_ADDR 0x2A
static void detect_display_connected(void)
{
struct udevice *bus = NULL;
struct udevice *i2c_dev = NULL;
int ret;
ret = uclass_get_device_by_seq(UCLASS_I2C, LVDS_TOUCH_I2C_BUS, &bus);
if (ret) {
printf("%s: Can't find bus\n", __func__);
}
else
{
ret = dm_i2c_probe(bus, LVDS_TOUCH_I2C_ADDR, 0, &i2c_dev);
if (ret) {
printf("%s: Can't find device id=0x%x\n",
__func__, LVDS_TOUCH_I2C_ADDR);
}
else
{
env_set("device-tree-overlay", "lvds-panel");
}
}
}
imx93_evk.c
At the end, add this function to the previously mention, named as board_late_init, in the section CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG, like the following snipped from code:
int board_late_init(void)
{
#ifdef CONFIG_ENV_IS_IN_MMC
board_late_mmc_env_init();
#endif
env_set("sec_boot", "no");
#ifdef CONFIG_AHAB_BOOT
env_set("sec_boot", "yes");
#endif
#ifdef CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG
env_set("board_name", "11X11_EVK");
env_set("board_rev", "iMX93");
detect_display_connected();
#endif
return 0;
}
imx93_evk.c
Now, when it's starting u-boot after flashing, it will generate the environment variable as trigger if something it's connected with that i2c address, else it doesn't do anything.
As was explained in the section "How to apply device tree overlay", applying the device tree overlay automatically after configure the trigger it's easy, just adding an if/else case for this example, it can be more ways to applying it, even it's possible adding more of one device tree overlay, but in this example will load one.
Using u-boot command "test -e <environment variable>" it will detect if exist this environment variable, adding it to an if/else sentence it can create the event and applying the overlay if was detected or not, for this solution will be added this if/else as input if exists loadoverlay variable with the following structure:
u-boot=> if test -e ${device-tree-overlay}; then <case exists device-tree-overlay variable> else <case doesn't exists device-tree-overlay variable>; fi;
adding it to loadoverlay, it will be written like the following command:
u-boot=> setenv loadoverlay "if test -e ${device-tree-overlay}; then setexpr fdtovaddr ${fdt_addr} + 0xF0000; setexpr fdt_buffer 16384; fdt addr ${fdt_addr} && fdt resize $\{fdt_buffer\}; fatload mmc ${mmcdev}:${mmcpart} $\{fdtovaddr\} imx93-11x11-evk-test-lvds-panel.dtbo; fdt apply $\{fdtovaddr\} ; else echo no overlay; fi;"
A no recommended method it's that it can be saved the environment, and changing mmcboot variable with the following command:
u-boot=> setenv mmcboot "run mmcargs; run loadfdt; run loadoverlay; run boot_os;"; saveenv;
The problem about just saving it, it still necessary compile u-boot to load auto-detection of LVDS panel and flashing, another way to add the event trigger, it's adding it to u-boot as initial environment variable, it can be added in the header file of imx93, it is located in the next path <u-boot path>/include/configs/imx93_evk.h, line number 60, it can be added with the same string but it's recommended follow the same structure, like the following definition:
/* Initial environment variables */
#define CFG_EXTRA_ENV_SETTINGS \
...
"loadoverlay=echo loading overlays from mmc ...; " \
"if test -e ${device-tree-overlay}; then " \
"setexpr fdtovaddr ${fdt_addr} + 0xF0000; " \
"setexpr fdt_buffer 16384; " \
"fdt addr ${fdt_addr} && fdt resize ${fdt_buffer}; " \
"fatload mmc ${mmcdev}:${mmcpart} ${fdtovaddr} imx93-11x11-evk-test-lvds-panel.dtbo && fdt apply ${fdtovaddr}; " \
"else " \
"echo no overlay; " \
"fi;\0" \
...
imx93_evk.h
it also it's necessary to change mmcboot environment variable adding loadoverlay after executing loadfdt.
/* Initial environment variables */
#define CFG_EXTRA_ENV_SETTINGS \
..
"mmcboot=echo Booting from mmc ...; " \
"run mmcargs; " \
"if test ${sec_boot} = yes; then " \
"if run auth_os; then " \
"run run boot_os; " \
"else " \
"echo ERR: failed to authenticate; " \
"fi; " \
"else " \
"if test ${boot_fit} = yes || test ${boot_fit} = try; then " \
"bootm ${loadaddr}; " \
"else " \
"if run loadfdt; then " \
"run loadoverlay; " \
"run boot_os; " \
"else " \
"echo WARN: Cannot load the DT; " \
"fi; " \
"fi;" \
"fi;\0" \
...
imx93_evk.h
To build u-boot, copy the following commands in main path from u-boot
$ cd <u-boot path>
$ make -j $(nproc --all) clean PLAT=imx93 CROSS_COMPILE=aarch64-linux-gnu-
$ make -j $(nproc --all) ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- imx93_11x11_evk_defconfig
$ make -j $(nproc --all) PLAT=imx93 CROSS_COMPILE=aarch64-linux-gnu-
generating the files u-boot.bin and u-boot-spl.bin located in <uboot-imx path>/ and <uboot-imx path>/spl
To build the binary necessary to flash to iMX 93 EVK it's necessary build a file named as flash.bin, it can building using the next repository using the branch used for this example:
$ git clone https://github.com/nxp-imx/imx-mkimage.git -b lf-6.6.3_1.0.0
to build imx-boot image it's necessary adding some files to the path <imx-mkimage path>/iMX93, including 2 generated by u-boot, u-boot.bin and u-boot-spl.bin, move these files to iMX93 directory.
$ cp <uboot-imx path>/u-boot.bin <uboot-imx path>/spl/u-boot-spl.bin <imx-mkimage path>/iMX93/
follow the steps from imx linux users guide section 4.5.13 and imx linux release notes section 1.2 to build flash.bin, as an example of compile, there's the steps to compile for imx93.
$ wget https://www.nxp.com/lgfiles/NMG/MAD/YOCTO/firmware-sentinel-0.11.bin
$ chmod +x firmware-sentinel-0.11.bin
$ ./firmware-sentinel-0.11.bin
$ cp firmware-sentinel-0.11/mx93a1-ahab-container.img <imx-mkimage path>/iMX93/$ wget https://www.nxp.com/lgfiles/NMG/MAD/YOCTO/firmware-imx-8.23.bin
$ chmod +x firmware-imx-8.23.bin
$ ./firmware-imx-8.23.bin
$ cp firmware-imx-8.23/firmware/ddr/synopsys/lpddr4_dmem_1d_v202201.bin firmware-imx-8.23/firmware/ddr/synopsys/lpddr4_dmem_2d_v202201.bin firmware-imx-8.23/firmware/ddr/synopsys/lpddr4_imem_1d_v202201.bin firmware-imx-8.23/firmware/ddr/synopsys/lpddr4_imem_2d_v202201.bin <imx-mkimage path>/iMX93/$ git clone https://github.com/nxp-imx/imx-atf.git -b lf-6.6.3-1.0.0
$ cd imx-atf
$ make -j $(nproc --all) PLAT=imx93 CROSS_COMPILE=aarch64-linux-gnu-
$ cp <imx-atf path>/build/imx93/release/bl31.bin <imx-mkimage path>/iMX93$ cd <imx-mkimage path>/
$ make SOC=iMX9 REV=A1 flash_singlebootit will generate the binary flash.bin located in the path <imx-mkimage path>/iMX93/flash.bin.
Flashing just u-boot image using flash.bin, will be used uuu.exe, it can be downloaded from the his repositroy, try using the most recent version taged as "Latest"
https://github.com/nxp-imx/mfgtools/releases
make sure is using i.MX 93 EVK in boot mode download and connect it to your host from download USB port, using uuu.exe run the next code:
.\uuu.exe -b emmc .\flash.bin
or it can be flashed the full image with flash.bin binary.
.\uuu.exe -b emmc_all .\flash.bin ..\uuu\imx-image-full-imx93evk.wic
after that, starting be will using the created u-boot environment.
Inside u-boot, when it's connected the LVDS panel, it will create the variable named "device-tree-overlay" and will be charged automatically LVDS panel overlay, enabling it, if not it will working normally using DSI as output.
Note: Ensure to have imx93-11x11-evk-test-lvds-panel.dtbo in memory.