Linux: Introduction to device trees

From Compulab Mediawiki
Revision as of 09:34, 8 February 2016 by Nikita (talk | contribs) (Cross referencing with SoC manual)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

What are device trees?

A device tree is a data structure that describes the System-on-Module (SoM) hardware: its various hardware components, and their relationship with one another. In the past, such information was hardcoded into the kernel for each SoM, and device trees were invented to curb this practice by providing a standard way to pass hardware description to the kernel. Much like source code, device trees can exist as human readable source files (.dts, .dtsi), or as compiled blobs (.dtb). Before a device tree is passed to the kernel, its source must be compiled into a blob. This compilation happens automatically as part of building the kernel.

How are device trees structured?

Device trees contain segments called "nodes". Each node contains information about a hardware component. The contents of each node are dictated by the driver that is responsible for handling the hardware component. The various nodes are organized in a tree-like hierarchy, which reflects the relationship between the hardware components. For example, if we have an i2c bus with multiple devices, the nodes that represent these devices will be sub nodes of the i2c bus node. (NOTE: device nodes can also make direct references to other nodes in the system, so in practice the device tree is more like a device graph).

Device trees can include other device trees. This facilitates modularity of the hardware representation. A common pattern in the ARM world is for the SoC vendor (e.g. TI) to define a device tree that describes the SoC (e.g. AM437x), and the SoM vendor (e.g. CompuLab) would include it in the device tree of a product that is based on the SoC (e.g. CM-T43). The inclusion works in a cascading fashion: if device tree B includes device tree A, the result will have the content of both device trees. However, if both A and B happen to define the same property, the value in device tree B will override the value provided by device tree A. In the opposite case (device tree A includes device tree B), the value from A woulde override the value from B.

  • For more information about device tree syntax, visit the official Device Tree WiKi pages and make an overview of a Device Tree tutorial
  • For information about what content (bindings) should appear in various nodes, see Documentation/devicetree/bindings/ folder in the Linux kernel source code.

Device tree software support

In order to use device tree blobs, the kernel must have device tree support compiled in, and the bootloader must have support for passing device tree blobs to the kernel. In modern Linux kernels, device tree support is ubiquitous. When configuring the kernel for the ARM architecture, the relevant CONFIG options (specifically CONFIG_USE_OF) is selected automatically by the kernel config system. Device tree support in U-Boot, the bootloader of choice for CompuLab SoMs, can be enabled by defining the CONFIG option CONFIG_OF_LIBFDT in the board config file (usually include/configs/board_name.h).

Passing device tree blobs to the kernel

Passing device tree blobs to the kernel is done using the U-Boot commands bootz (for zImages) and bootm (for uImages). For example:

U-Boot# help bootz
bootz - boot Linux zImage image from memory

Usage:
bootz [addr [initrd[:size]] [fdt]]
    - boot Linux zImage stored in memory
	The argument 'initrd' is optional and specifies the address
	of the initrd in memory. The optional argument ':size' allows
	specifying the size of RAW initrd.
	When booting a Linux kernel which requires a flat device-tree
	a third argument is required which is the address of the
	device-tree blob. To boot that kernel without an initrd image,
	use a '-' for the second argument. If you do not pass a third
	a bd_info struct will be passed instead

Modifying device tree blobs

Sometimes, a device tree cannot simultaneously represent different versions of the same hardware product. In that case, a choice is made to represent one of the hardware variations, and the bootloader is expected to adjust the device tree before passing it on to the kernel. U-Boot has dedicated commands for modifying the device tree:

U-Boot# help fdt
fdt - flattened device tree utility commands

Usage:
fdt addr [-c]  <addr> [<length>]   - Set the [control] fdt location to <addr>
fdt move   <fdt> <newaddr> <length> - Copy the fdt to <addr> and make it active
fdt resize                          - Resize fdt to size + padding to 4k addr
fdt print  <path> [<prop>]          - Recursive print starting at <path>
fdt list   <path> [<prop>]          - Print one level starting at <path>
fdt get value <var> <path> <prop>   - Get <property> and store in <var>
fdt get name <var> <path> <index>   - Get name of node <index> and store in <var>
fdt get addr <var> <path> <prop>    - Get start address of <property> and store in <var>
fdt get size <var> <path> [<prop>]  - Get size of [<property>] or num nodes and store in <var>
fdt set    <path> <prop> [<val>]    - Set <property> [to <val>]
fdt mknode <path> <node>            - Create a new node after <path>
fdt rm     <path> [<prop>]          - Delete the node or <property>
fdt header                          - Display header info
fdt bootcpu <id>                    - Set boot cpuid
fdt memory <addr> <size>            - Add/Update memory node
fdt rsvmem print                    - Show current mem reserves
fdt rsvmem add <addr> <size>        - Add a mem reserve
fdt rsvmem delete <index>           - Delete a mem reserves
fdt chosen [<start> <end>]          - Add/update the /chosen branch in the tree
                                        <start>/<end> - initrd start/end addr
NOTE: Dereference aliases by omiting the leading '/', e.g. fdt print ethernet0.

Configuring pinmux settings in device tree

One of the most common tasks in embedded development is configuring the pinmux settings for the SoC. This task requires cross referencing the SoM manual with the SoC manual, and editing the pinmux section of the SoM device tree. The following instructions describe the process step-by-step for TI SoCs. Adjustments may be needed when dealing with SoCs manufactured by other companies, but the general process is similar.

Selecting pins for use

Compulab provides a detailed reference manual for its SoMs. One of the chapters in each such manual describes the pins which are exported via the SoM connector, listing all of their possible functions, as well as availability (some pins are not usable when certain manufacturing options are selected for the SoM). For example, on the CM-T43 reference manual, this information is detailed in chapter 5.5 "Signal Multiplexing Characteristics".

Cross referencing with SoC manual

Having chosen the pins to configure, the next step is to find the address of the register that controls the pins. This is necessary because the way we reference the pin in the kernel is with the address of the register that controls it. On TI SoCs, each pin is named after its mode 0 function. To find the register that controls it, open the SoC TRM, and search for a register called CTRL_CONF_<mode_0_function_name>. The page that describes the register will also list its offset relative to the base address of the control module. This value will be used in the device tree to refer to the pin control register.

Modifying the device tree

The device tree segment that controls the pin mux will look something like this:

&am43xx_pinmux {
	pinctrl-names = "default";
	pinctrl-0 = <&cm_t43_led_pins>;

	spi0_pins: pinmux_spi0_pins {
		pinctrl-single,pins = <
			AM4372_IOPAD(0x950, PIN_INPUT | MUX_MODE0) /* spi0_sclk.spi0_sclk */
			AM4372_IOPAD(0x954, PIN_INPUT | MUX_MODE0) /* spi0_d0.spi0_d0 */
			AM4372_IOPAD(0x958, PIN_OUTPUT | MUX_MODE0) /* spi0_d1.spi0_d1 */
			AM4372_IOPAD(0x95C, PIN_OUTPUT | MUX_MODE0) /* spi0_cs0.spi0_cs0 */
		>;
	};
...
}

Add a new node for your custom pinmux configuration, and populate it with pinmux settings. For example:

&am43xx_pinmux {
	pinctrl-names = "default";
	pinctrl-0 = <&cm_t43_led_pins>;

	spi0_pins: pinmux_spi0_pins {
		pinctrl-single,pins = <
			AM4372_IOPAD(0x950, PIN_INPUT | MUX_MODE0) /* spi0_sclk.spi0_sclk */
			AM4372_IOPAD(0x954, PIN_INPUT | MUX_MODE0) /* spi0_d0.spi0_d0 */
			AM4372_IOPAD(0x958, PIN_OUTPUT | MUX_MODE0) /* spi0_d1.spi0_d1 */
			AM4372_IOPAD(0x95C, PIN_OUTPUT | MUX_MODE0) /* spi0_cs0.spi0_cs0 */
		>;
	};


	custom_pins: pinmux_custom_pins {
		pinctrl-single,pins = <
			/* some pin settings */
			/* some other pin settings */
		>;
	};

...
}

Specifying pinmux settings

The general format for specifying pinmux settings is that each line starts with the pin register offset, and ends with the pin settings. The syntax for specifying this information changes somewhat between SoCs, and even between kernel versions. Because of these differences, the best practice for determining how to specify a custom pinmux configuration is to look at existing examples, such as the pinmux segment in the device tree for your chosen Compulab SoM. The following example describes the pinmux settings format for cm-t43 on a v4.4 kernel, and points out the possible differences that may exist in other SoMs.

The pinmux setting:

AM4372_IOPAD(0x950, PIN_INPUT | MUX_MODE0)

This line consists of the following sections:

  • AM4372_IOPAD() macro. As the name suggests, this macro is specific to AM4372 SoCs. Other SoCs will have a differently named macro. If a SoC is fairly new, there may not be a macro for it yet, and then the pin configuration will be just the offset and pinmux settings, like so:
0x950 (PIN_OUTPUT_PULLDOWN | MUX_MODE2)
  • The pin register offset (0x950). This offset may or may not correspond to the register offset described in the SoC TRM. For example, before the AM4372_IOPAD() macro was defined for the AM437x SoC, it was necessary to subtract 0x800 from the offset listed in the TRM in order to refer to the pin register. For example, if the TRM listed the offset as 0x950, the device tree line controlling the pin actually looked like this:
0x150 (PIN_OUTPUT_PULLDOWN | MUX_MODE2)

Once the AM4372_IOPAD() macro was defined, this subtraction was no longer necessary, and the value from the TRM had to be used, like so:

AM4372_IOPAD(0x950, PIN_OUTPUT_PULLDOWN | MUX_MODE2)
  • Pin settings (PIN_OUTPUT_PULLDOWN | MUX_MODE2). These macros may be named differently in device trees for non-TI SoCs.