Traditionally, the specific hardware configurations are described in so called board-description files and permanently embedded into the kernel during compilation, making it necessary to re-compile the kernel to change pin assignment. The Device Tree is supposed to revolutionize that by reading the hardware configuration from a data structure stored in RAM at startup. In this article we describe how to enhance the Device Tree to support additional hardware.
What the *#+/%§* is a Device Tree?
Embedded systems usually rely on ARM based CPUs, as they provide better power efficiency and modifiability than other architectures. ARM itself just sells licenses for their architectures that allow other manufacturers to modify the chip designs, e.g. by adding hardware interfaces. Unified interfaces such as defined addresses and interrupts are not available.
Accordingly each ARM-platform comes with its own drivers and modules. That’s why it is not possible to run one kernel on different ARM Systems-on-a-Chip (SoC) and development boards.
Traditionally, the specific hardware configurations are described in so called board-description files and permanently embedded into the kernel during compilation. Kernel sources are then expanded according to the number of files needed. Furthermore modern ARM architectures use pins for multiple purposes. With multiplexing, a pin may e.g- work as GPIO or as part of a I2C or a SPI interface. Thus board files have to be added at compilation time and cannot be modified afterwards. For changing the pin assignment it is necessary to re-compile the kernel.
The Device Tree is supposed to revolutionize this. Its idea is to read the hardware configuration from a data structure stored in RAM at startup. So it is possible to run one kernel on multiple development boards, just by swapping the data structure—a technique formerly used for the Power-PC.
With device tree overlays, kernel developers create a mechanism to configure a board’s pins at run time from within the userspace.
Device Tree descriptions are located in arch/arm/boot/dts. While files named .dtsi contain base definitions for a defined SoC, files ending in .dts describe individual boards.
The Device Tree Compiler (DTC) compiles the files to binary blobs called Device Tree Blobs (DTB) with the command make dtbs. There is a specific blob for each device like omap4-panda-es.dtb for a Pandaboard ES with an OMAP4 ARM CPU.
The bootloader, for most embedded devices U-Boot, loads the DTB into the RAM along with the Kernel and the root file system. At startup the kernel gets informed about its hardware configuration by the DTB.
Adding GPIO to a Pandaboard – Hands On
The Device Tree is a quite new mechanism in the kernel. At the moment there isn’t one yet for every SoC and corresponding development board and those who do exist are quite incomplete.
There is one for the Pandaboard ES—which we want to use as an example—, however there are no definitions for the pins of the expansion headers. Below we will show how to add support for the GPIO function of the board. With pin multiplexing it is quite simple to change the mode of the enabled pins, too, e.g. to parts of an UART interface.
We will start with the memory addresses for the pins with GPIO functionality which can be found in the CPU’s Technical Reference Manual (TRM). They are located in the address area 0x4a100000 which is the pinmux core domain. In the device tree structure 0x4a100040 is the base address for this domain. To identify the pins in the structure, offsets are calculated from the core domain address and the representation in the DTS file. For simplicity all relevant information is collected in the table below. At first the original address based on the pinmux core domain is named, then the offset which is used in the device tree.
Next we extend the device tree structure in omap4-panda-es.dts. To add support for GPIOs located in the pinmux core domain, the node &omap4_pmx_core is the right place. For a clean representation each used pin is assigned to its expansion header on the Pandaboard. So &omap4_pmx_core is expanded with two sub-nodes, pinmux_j3_gpios and pinmux_j6_gpios.
Within them, an entry defining a pin starts with the offset to identify the right one. The first constant specifies whether the pin is used as input or output, either of which can be a pull up or a pull down resistor. The second one defines the multiplexing mode. For all considered pins, mode 3 set them to GPIO functionality. The manual lists the functionalities and corresponding modes.
After loading the expanded device tree blob into the kernel, the new added nodes appear in the proc file system under /proc/device-tree/ocp/pinmux@4a100040 and the GPIOs should work.
/* Pinmux for gpios on J3 expansion header */
0x1e (PIN_OUTPUT | MUX_MODE3) /* gpio_39.gpmc_ad15 */
// 0xee (PIN_OUTPUT | MUX_MODE3) /* i2c4_scl.gpio_132 used as i2c4 defined in omap4-panda-commom.dtsi */
// 0xf0 (PIN_OUTPUT | MUX_MODE3) /* i2c4_sda.gpio_133 used as i2c4 defined in omap4-panda-commom.dtsi */
0xf2 (PIN_OUTPUT | MUX_MODE3) /* gpio_134.mcspi1_clk */
0xf4 (PIN_OUTPUT | MUX_MODE3) /* gpio_135.mcspi1_somi */
0xf6 (PIN_OUTPUT | MUX_MODE3) /* gpio_136.mcspi1_simo */
0xf8 (PIN_OUTPUT | MUX_MODE3) /* gpio_137.mcspi1_cs0 */
0xfa (PIN_OUTPUT | MUX_MODE3) /* gpio_138.mcspi1_cs1 */
0xfc (PIN_OUTPUT | MUX_MODE3) /* gpio_139.mcspi1_cs2 */
0xfe (PIN_OUTPUT | MUX_MODE3) /* gpio_140.mcspi1_cs3 */
0x11c (PIN_OUTPUT | MUX_MODE3) /* gpio_155.uart4_rx */
0x11e (PIN_OUTPUT | MUX_MODE3) /* gpio_156.uart4_tx */
/* Pinmux for gpios on J6 expansion header */
pinctrl-single,pins = <
0x10 (PIN_OUTPUT | MUX_MODE3) /* gpio_32.gpmc_ad8 */
0x12 (PIN_OUTPUT | MUX_MODE3) /* gpio_33.gpmc_ad9 */
0x14 (PIN_OUTPUT | MUX_MODE3) /* gpio_34.gpmc_ad10 */
0x16 (PIN_OUTPUT | MUX_MODE3) /* gpio_35.gpmc_ad11 */
0x18 (PIN_OUTPUT | MUX_MODE3) /* gpio_36.gpmc_ad12 */
0x1a (PIN_OUTPUT | MUX_MODE3) /* gpio_37.gpmc_ad13 */
0x1c (PIN_OUTPUT | MUX_MODE3) /* gpio_38.gpmc_ad14 */
0x34 (PIN_OUTPUT | MUX_MODE3) /* gpio_50.gpmc_ncs0 */
0x36 (PIN_OUTPUT | MUX_MODE3) /* gpio_51.gpmc_ncs1 */
0x3c (PIN_OUTPUT | MUX_MODE3) /* gpio_54.gpmc_nwp */
0x3e (PIN_OUTPUT | MUX_MODE3) /* gpio_55.gpmc_clk */
0x40 (PIN_OUTPUT | MUX_MODE3) /* gpio_56.gpmc_nadv_ale */
0x46 (PIN_OUTPUT | MUX_MODE3) /* gpio_59.gpmc_nbe0_cle */
0x4a (PIN_OUTPUT | MUX_MODE3) /* gpio_61.gpmc_wait0 */
// 0xD4 (PIN_OUTPUT | MUX_MODE3) /* gpio_121.h_dmtimer11_pwm */