Introducing device trees

You are almost certainly going to encounter device trees at some point. This section aims to give you a quick overview of what they are and how they work but there are many details that are not discussed.

A device tree is a flexible way to define the hardware components of a computer system. Usually, the device tree is loaded by the bootloader and passed to the kernel, although it is possible to bundle the device tree with the kernel image itself to cater for bootloaders that are not capable of handling them separately.

The format is derived from a Sun Microsystems bootloader known as OpenBoot, which was formalized as the Open Firmware specification, IEEE standard IEEE1275-1994. It was used in PowerPC-based Macintosh computers and so was a logical choice for the PowerPC Linux port. Since then, it has been adapted on a large scale by the many ARM Linux implementations and, to a lesser extent, by MIPS, MicroBlaze, ARC, and other architectures.

I would recommend visiting http://devicetree.org for more information.

Device tree basics

The Linux kernel contains a large number of device tree source files in arch/$ARCH/boot/dts, and this is a good starting point for learning about device trees. There are also a smaller number of sources in the U-boot source code in arch/$ARCH/dts. If you acquired your hardware from a third party, the dts file forms part of a board support package and you should expect to receive one along with the other source files.

The device tree represents a computer system as a collection of components joined together in a hierarchy, like a tree. The device tree begins with a root node, represented by a forward slash, /, which contains subsequent nodes representing the hardware of the system. Each node has a name and contains a number of properties in the form name = "value". Here is a simple example:

/dts-v1/;
/{
  model = "TI AM335x BeagleBone";
  compatible = "ti,am33xx";
  #address-cells = <1>;
  #size-cells = <1>;
  cpus {
    #address-cells = <1>;
    #size-cells = <0>;
    cpu@0 {
      compatible = "arm,cortex-a8";
      device_type = "cpu";
      reg = <0>;
    };
  };
  memory@0x80000000 {
    device_type = "memory";
    reg = <0x80000000 0x20000000>; /* 512 MB */
  };
};

Here we have a root node which contains a cpus node and a memory node. The cpus node contains a single CPU node named cpu@0. It is a common convention that the names of nodes include an @ followed by an address that distinguishes them from any others.

Both the root and CPU nodes have a compatible property. The Linux kernel uses this to match this name against the strings exported by device drivers in a struct of_device_id (more on this in Chapter 8, Introducing Device Drivers). It is a convention that the value is composed of a manufacturer name and a component name to reduce confusion between similar devices made by different manufacturers, hence ti,am33xx and arm,cortex-a8. It is also quite common to have more than one value for compatible where there is more than one driver that can handle this device. They are listed with the most suitable first.

The CPU node and the memory node have a device_type property which describes the class of device. The node name is often derived from the device_type.

The reg property

The memory and CPU nodes have a reg property, which refers to a range of units in a register space. A reg property consists of two values representing the start address and the size (length) of the range. Both are written down as zero or more 32-bit integers, called cells. Hence, the memory node refers to a single bank of memory that begins at 0x80000000 and is 0x20000000 bytes long.

Understanding reg properties becomes more complex when the address or size values cannot be represented in 32 bits. For example, on a device with 64-bit addressing, you need two cells for each:

/ {
  #address-cells = <2>;
  #size-cells = <2>;
  memory@80000000 {
    device_type = "memory";
    reg = <0x00000000 0x80000000 0 0x80000000>;
  };
}

The information about the number of cells required is held in #address-cells and #size_cells declarations in an ancestor node. In other words, to understand a reg property, you have to look backwards down the node hierarchy until you find #address-cells and #size_cells. If there are none, the default values are 1 for each – but it is bad practice for device tree writers to depend on fall-backs.

Now, let's return to the cpu and cpus nodes. CPUs have addresses as well: in a quad core device they might be addressed as 0, 1, 2, and 3. That can be thought of as a one-dimensional array without any depth so the size is zero. Therefore, you can see that we have #address-cells = <1> and #size-cells = <0> in the cpus node, and in the child node, cpu@0, we assign a single value to the reg property: node reg = <0>.

Phandles and interrupts

The structure of the device tree described so far assumes that there is a single hierarchy of components, whereas in fact there are several. As well as the obvious data connection between a component and other parts of the system, it might also be connected to an interrupt controller, to a clock source and to a voltage regulator. To express these connections, we have phandles.

Take an example of a system containing a serial port which can generate interrupts and the interrupt controller:

/dts-v1/;
{
  intc: interrupt-controller@48200000 {
    compatible = "ti,am33xx-intc";
    interrupt-controller;
    #interrupt-cells = <1>;
    reg = <0x48200000 0x1000>;
  };
  serial@44e09000 {
    compatible = "ti,omap3-uart";
    ti,hwmods = "uart1";
    clock-frequency = <48000000>;
    reg = <0x44e09000 0x2000>;
    interrupt-parent = <&intc>;
    interrupts = <72>;
  };
};

We have an interrupt-controller node which has the special property #interrupt-cells, which tells us how many 4-byte values are needed to represent an interrupt line. In this case, it is just one giving the IRQ number, but it is quite common to use additional values to characterize the interrupt, for example 1 = low-to-high edge triggered, 2 = high-to-low edge triggered, and so on.

Looking at the serial node, it has an interrupt-parent property which references the interrupt-controller it is connected to by using its label. This is the phandle. The actual IRQ line is given by the interrupts property, 72 in this case.

The serial node has other properties that we have not seen before: clock-frequency and ti,hwmods. These are part of the bindings for this particular type of device, in other words, the kernel device driver will read these properties to manage the device. The bindings can be found in the Linux kernel source, in directory Documentation/devicetree/bindings/.

Device tree include files

A lot of hardware is common between SoCs of the same family and between boards using the same SoC. This is reflected in the device tree by splitting out common sections into include files, usually with the extension .dtsi. The Open Firmware standard defines /include/ as the mechanism to be used, as in this snippet from vexpress-v2p-ca9.dts:

/include/ "vexpress-v2m.dtsi"

Look through the .dts files in the kernel, though, and you will find an alternative include statement that is borrowed from C, for example in am335x-boneblack.dts:

#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"

Here is another example from am33xx.dtsi:

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/am33xx.h>

Lastly, include/dt-bindings/pinctrl/am33xx.h contains normal C macros:

#define PULL_DISABLE (1 << 3)
#define INPUT_EN (1 << 5)
#define SLEWCTRL_SLOW (1 << 6)
#define SLEWCTRL_FAST 0

All of this is resolved if the device tree sources are built using kernel kbuild, which first runs them through the C pre-processor, cpp, where the #include and #define statements are processed into plain text that is suitable for the device tree compiler. The motivation is shown in the previous example: it means that device tree sources can use the same constant definitions as the kernel code.

When we include files in this way the nodes are overlaid on top of one another to create a composite tree in which the outer layers extend or modify the inner ones. For example, am33xx.dtsi, which is general to all am33xx SoCs, defines the first MMC controller interface like this:

mmc1: mmc@48060000 {
  compatible = "ti,omap4-hsmmc";
  ti,hwmods = "mmc1";
  ti,dual-volt;
  ti,needs-special-reset;
  ti,needs-special-hs-handling;
  dmas = <&edma 24  &edma 25>;
  dma-names = "tx", "rx";
  interrupts = <64>;
  interrupt-parent = <&intc>;
  reg = <0x48060000 0x1000>;
  status = "disabled";
};

Note

Note that the status is disabled, meaning that no device driver should be bound to it, and also that it has the label mmc1.

In am335x-bone-common.dtsi, which is included with both BeagleBone and BeagleBone Black, the same node is referenced by its phandle:

&mmc1 {
  status = "okay";
  bus-width = <0x4>;
  pinctrl-names = "default";
  pinctrl-0 = <&mmc1_pins>;
  cd-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
  cd-inverted;
};

Here, mmc1 is enabled (status="okay") because both variants have a physical MMC1 device, and the pinctrl is established. Then, in am335x-boneblack.dts, you will see another reference to mmc1 which associates it with a voltage regulator:

&mmc1 {
  vmmc-supply = <&vmmcsd_fixed>;
};

So, layering source files like this gives flexibility and reduces the need for duplicated code.

Compiling a device tree

The bootloader and kernel require a binary representation of the device tree, so it has to be compiled using the device tree compiler, dtc. The result is a file ending with .dtb, which is referred to as a device tree binary or a device tree blob.

There is a copy of dtc in the Linux source, in scripts/dtc/dtc, and it is also available as a package on many Linux distributions. You can use it to compile a simple device tree (one that does not use #include) like this:

$ dtc simpledts-1.dts -o simpledts-1.dtb
DTC: dts->dts on file "simpledts-1.dts"

Be wary of the fact that dtc does not give helpful error messages and it makes no checks other than on the basic syntax of the language, which means that debugging a typing error in a source file can be a lengthy business.

To build more complex examples, you will have to use the kernel kbuild, as shown in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.217.198