How to Write Device Tree Overlays
Introduction
This article aims to guide you through the process of writing and deploying Device Tree Overlays suitable for your customized hardware and peripherals.
Prerequisites
- Understand the Device Tree structure and conventions
- Follow the First Steps with Device Tree Overlays
How to Write an Overlay
Pin Definitions Header
Each SoC has its own pin definitions file, as you can see on the related Pinmuxing Guide. This file lists each pin with all of its possible muxing options. It contains definitions in a pattern comprising the ball name prefix and the muxing option suffix.
As an example, MX6QDL_PAD_SD2_DAT1__SD2_DATA1
, comprises:
- The ball name:
MX6QDL_PAD_SD2_DAT1
- The pin mux option:
SD2_DATA1
Pin Control Node
Here we have the actual pin control node that will add the chosen pins as GPIOs. In the template file, we defined the node my_muxgrp
and its label as pinctrl_my_pins
. Keep in mind that these are arbitrary names, and you can choose any name according to your preference. But be careful: not all characters are allowed, as it's explained at Device Tree Specifications.
&iomuxc {
...
pinctrl_my_pins: my_muxgrp {
fsl,pins = <
PIN_NAME_PIN_MUX PAD_CTRL // PINS
>;
};
};
Depending on the SoM, you might have to put the pin control node inside another node named after the SoM, like apalis-imx8qm
. Check how pin control nodes are declared inside iomuxc
in the unaltered Device Tree and use that as reference.
&iomuxc {
...
name-of-som {
pinctrl_my_pins: my_muxgrp {
fsl,pins = <
PIN_NAME_PIN_MUX PAD_CTRL // PINS
>;
};
};
};
Adding the Pins
Finally, we come to the pin functions declaration themselves. The PIN_NAME_PIN_MUX
field should be in the format <name prefix>_<muxing option suffix>
or <name prefix>__<muxing option suffix>
(depending on the SoC) as explained earlier, and that will directly reference the speficif header file. The PAD_CTRL
is a hexadecimal value to set the Pad Control register of the SoC, which you can find related information on the Pinmuxing Guides
Referencing the Pin Control Node
A pin control node/group alone has limited functionality. However, other nodes can make use of its pins by referencing it, which effectively applies functions to the pins. When referencing a pin group, there are typically two available options:
IOMUXC node: Setting the value of
pinctrl-0
iniomuxc
with a pin control node indicates that the pins inside it will be available in userspace, making it possible to use them as GPIOs.<&pinctrl-originally-in-device-tree>
represents one or more pin control nodes that were referenced in the unaltered device tree, and it's usually good to keep them in the overlay, as they would be otherwise dereferenced....
pinctrl-names = "default";
pinctrl-0 = <&pinctrl-originally-in-device-tree>, <&pinctrl_my_pins>;
};Peripherals: Similarly, setting
pinctrl-0
in a peripheral node with a pin control group makes the pins in it available to the peripheral. While you can reference the same pin control group on multiple nodes, be sure to only have one of them enabled in the device tree to avoid pin conflicts.&genericPeripheral {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_my_pins>; // Pin group used in genericPeripheral
status = "okay";
Case Oriented Example: LED Blink
This section aims to guide you on how to use a device tree overlay to modify the device tree of a Verdin iMX8M Mini with a Verdin Development Board, multiplexing pins to allow a LED blinking. The custom-overlay.dts
will be written from scratch, and each step will be carefully explained.
Device tree overlays are built standalone, without any connection to the device tree they will modify. For this reason, they must include headers and specifiers just like a regular device tree file.
custom-overlay.dts/* Custom Device Tree Overlay for LED blinking */
/dts-v1/;
/plugin/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
#include "imx8mm-pinfunc.h"As it can be seen, we have tree includes:
dt-bindings/gpio/gpio.h
: Define macros for GPIO bindings.dt-bindings/leds/common.h
: Define macros for the common LEDs device tree bindings.imx8mm-pinfunc.h
: Define macros for pins and its possible muxings according to SoC's Technical Reference Manual through IOMUXC driver.
Define the hardware compatibility using the
compatible
property. This will make the overlay modify the right device tree source file.custom-overlay.dts/ {
compatible = "toradex,verdin-imx8mm";
};Define which nodes you want to enable or disable through the property
status
. In this case, as we are going to disable the PWM interfaces.custom-overlay.dts&pwm1 {
status = "disabled";
};
&pwm2 {
status = "disabled";
};Overlay, modify or create the desired node with custom configuration. In this case, we will create and add the
leds
node to the root node (&{/}
) with custom properties inpinctrl-0
, later defined by pinmuxing operations.custom-overlay.dts&{/} {
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_my_led>;
myled_sodimm_19 {
label = "my_led_sodimm_19";
gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "on";
};
myled_sodimm_55 {
label = "my_led_sodimm_55";
gpios = <&gpio5 16 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "on";
};
};
};It can be seen that this overlay contains two child nodes
myled_sodimm_19
andmyled_sodimm_55
, with custom properties.To perform pinmuxing operations, we need to overlay the
iomuxc
node and customize the multiplexing operations throughfsl,pins
property.custom-overlay.dts&iomuxc {
pinctrl_my_led: myledgrp {
fsl,pins =
<MX8MM_IOMUXC_GPIO1_IO01_GPIO1_IO1 0x104>, // SODIMM 19 // pull-down enabled and drive strength X2
<MX8MM_IOMUXC_I2C2_SCL_GPIO5_IO16 0x104>; // SODIMM 55 // pull-down enabled and drive strength X2
};
};
After all the previous steps, the custom-overlay.dts
should look like this:
View custom-overlay.dts
/dts-v1/;
/plugin/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
#include "imx8mm-pinfunc.h"
/ {
compatible = "toradex,verdin-imx8mm";
};
&pwm1 {
status = "disabled";
};
&pwm2 {
status = "disabled";
};
&{/} {
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_my_led>;
myled_sodimm_19 {
label = "my_led_sodimm_19";
gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "on";
};
myled_sodimm_55 {
label = "my_led_sodimm_55";
gpios = <&gpio5 16 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "on";
};
};
};
&iomuxc {
/*pinctrl-0 = <&pinctrl_gpio1>, <&pinctrl_gpio2>,
<&pinctrl_gpio3>, <&pinctrl_gpio4>,
<&pinctrl_gpio7>, <&pinctrl_gpio8>,
<&pinctrl_gpio_hog1>, <&pinctrl_gpio_hog2>, <&pinctrl_gpio_hog3>,
<&pinctrl_pmic_tpm_ena>, <&pinctrl_my_led>;*/
pinctrl_my_led: myledgrp {
fsl,pins =
<MX8MM_IOMUXC_GPIO1_IO01_GPIO1_IO1 0x104>, // SODIMM 19 // pull-down enabled and drive strength X2
<MX8MM_IOMUXC_I2C2_SCL_GPIO5_IO16 0x104>; // SODIMM 55 // pull-down enabled and drive strength X2
};
};