The latest development version of this page may be more current than this released 2.1.0 version.

Devicetree

Zephyr uses the devicetree data structure to describe the hardware available on a board, as well as its initial configuration in an application. Note that “devicetree” – without spaces – is preferred to “devicetree”. The Devicetree specification fully defines this data structure and its source and binary representations.

Introduction

This figure shows how devicetree fits into the Zephyr build system:

../../_images/zephyr_dt_build_flow.png

Devicetree build flow

Zephyr’s build system can use a devicetree to generate C language code. This code generation uses rules in additional files called devicetree bindings to control how devicetree data is converted to C definitions. The generated code can be included by Zephyr device drivers and other C sources. The C macros generated by this process all begin with DT_.

This differs significantly from how devicetree is used on Linux. The Linux kernel would instead read the entire devicetree data structure in its binary form, parsing it at runtime in order to load and initialize device drivers. Zephyr does not work this way because the size of the devicetree binary and associated handling code would be too large to fit comfortably on the relatively constrained devices Zephyr supports.

As the name indicates, a devicetree is a tree. The human-readable text format for this tree is called DTS (for devicetree source), and is defined in the Devicetree Specification. Here is an example DTS file:

/dts-v1/;

/ {
        a-node {
                subnode_label: a-sub-node {
                        foo = <3>;
                };
        };
};

This example has three nodes:

  1. A root node

  2. A node named a-node, which is a child of the root node

  3. A node named a-sub-node, which is a child of a-node

Nodes can be given labels, which are unique shorthands that can be used to refer to the labeled node elsewhere in the devicetree. Above, a-sub-node has label subnode_label.

Devicetree nodes have paths identifying their locations in the tree. Like Unix file system paths, devicetree paths are strings separated by slashes (/), and the root node’s path is a single slash: /. Otherwise, each node’s path is formed by concatenating the node’s ancestors’ names with the node’s own name, separated by slashes. For example, the full path to a-sub-node is /a-node/a-sub-node.

Devicetree nodes can also have properties. Properties are name/value pairs. The values are simple byte arrays. Node a-sub-node has a property named foo, whose value is a 32-bit big-endian unsigned integer with value 3. The size and type of foo’s value are implied by the enclosing angle brackets (< and >) in the DTS. Refer to the Devicetree Specification for a complete list of ways to write a property value in a DTS file.

In practice, devicetree nodes correspond to some hardware, and the node hierarchy reflects the hardware’s physical layout. For example, let’s consider a board with three I2C peripherals connected to an I2C bus master on an SoC, like this:

representation of a board with three I2C peripherals

Nodes corresponding to the I2C bus master and each I2C peripheral would be present in this board’s devicetree. Reflecting the hardware layout, the devicetree’s peripheral nodes would be children of the bus master node. Similar conventions exist for representing other types of hardware in devicetree.

The corresponding DTS would look something like this:

/ {
        soc {
                i2c-bus-master {
                        i2c-peripheral-1 {
                        };
                        i2c-peripheral-2 {
                        };
                        i2c-peripheral-3 {
                        };
                };
        };
};

Properties are used in practice to describe or configure the hardware the node represents. For example, an I2C peripheral’s node has a property whose value is the peripheral’s address on the bus.

Here’s a tree representing the same example, but with real-world node names and properties you might see when working with I2C devices.

../../_images/zephyr_dt_i2c_example.png

I2C devicetree example with real-world names and properties

Above, node names – like i2c@40003000 – are at the top of each node, with a gray background, except for the root node, which is shown using its path /. Properties are shown as name=value pairs below the node names.

Some important properties are:

  • compatible: this says what “kind” of device the node represents. Its value is a null-terminated string in the format “vendor,device”, like "avago,apds9960", or a sequence of these, like "ti,hdc", "ti,hdc1010". The build system uses the compatible property to find the right bindings for the node.

  • label: the device’s name according to Zephyr’s Device Driver Model. The value can be passed to device_get_binding() to retrieve the corresponding driver-level struct device*. This pointer can then be passed to the correct driver API by application code to interact with the device. For example, calling device_get_binding("I2C_0") would return a pointer to a device structure which could be passed to I2C API functions like i2c_transfer(). The generated C header will also contain a macro which expands to this string.

  • reg: information used to address the device. This could be a memory-mapped I/O address range (as with i2c@40003000’s reg property), an I2C bus address (as with apds9960@39 and its devicetree siblings), a SPI chip select line, or some other value depending on the kind of device the node represents.

This tree has the following DTS.

/ {
        soc {
                i2c@40003000 {
                        compatible = "nordic,nrf-twim";
                        label = "I2C_0";
                        reg = <0x40003000 0x1000>;

                        apds9960@39 {
                                compatible = "avago,apds9960";
                                label = "APDS9960";
                                reg = <0x39>;
                        };
                        ti_hdc@43 {
                                compatible = "ti,hdc", "ti,hdc1010";
                                label = "HDC1010;
                                reg = <0x43>;
                        };
                        mma8652fc@1d {
                                compatible = "nxp,fxos8700", "nxp,mma8652fc";
                                label = "MMA8652FC";
                                reg = <0x1d>;
                        };
                };
        };
};

Input and output files

The first figure in the Introduction shows how devicetree fits into the Zephyr build system. This section describes the input and output files in more detail.

../../_images/zephyr_dt_inputs_outputs.png

Devicetree input (green) and output (yellow) files

DTS files usually have .dts or .dtsi (for Devicetree Source Include) extensions. Zephyr’s build system looks for a file named BOARD.dts in the board definition directory; this file contains the board’s base devicetree. See Example: FRDM-K64F and Hexiwear K64 for real-world examples.

The build system combines the board’s DTS with additional input files called overlays to produce a final devicetree source file. Overlays are also written in the DTS format, but have a .overlay extension to make it clear that they’re overlays. You can specify the overlay files to use at build time using the DTC_OVERLAY_FILE CMake variable described in Important Build System Variables. The build system also looks for devicetree overlays in several locations by default; see Devicetree Overlays for the list.

Overlays can be used to add or delete nodes from the tree, or to modify node properties and their values. Along with Kconfig, devicetree overlays let you reconfigure the kernel and device drivers without modifying their source code.

Before they are combined, the C preprocessor is run on BOARD.dts and any overlays. This allows these files to use C macros and include directives.

The combined devicetree is written to a DTS file named BOARD.dts_compiled in the application build directory. This file contains the final devicetree.

This devicetree and the set of Devicetree Bindings are then used to generate C definitions using scripts in scripts/dts/. These definitions can be included via the generated_dts_board.h header file, which the build system places on the C preprocessor include path. This file is not generated; it is in include/generated_dts_board.h. (Its name was chosen for backwards compatibility.)

Do not include the generated C headers in the build directory directly. Use generated_dts_board.h instead.

Zephyr device drivers typically use information from generated_dts_board.h to statically allocate and initialize struct device instances. Property values from generated_dts_board.h are usually stored in ROM in the value pointed to by a device->config->config_info field. For example, a struct device corresponding to an I2C peripheral would store the peripheral address in its reg property there.

Application source code with a pointer to the struct device can then pass it to driver APIs in include/drivers/. These API functions usually take a struct device* as their first argument. This allows the driver API to use information from devicetree to interact with the device hardware.

Temporary “fixup” files are currently required for devicetree support on most devices. These fixup files by default reside in the board and soc directories and are named dts_fixup.h. These fixup files map the generated include information to the current driver/source usage. They exist for historical reasons; Zephyr is moving away from needing or using these files.

Example: FRDM-K64F and Hexiwear K64

The FRDM-K64F and Hexiwear K64 board devicetrees are defined in frdm_k64fs.dts and hexiwear_k64.dts respectively. Both boards have NXP SoCs from the same Kinetis SoC family, the K6X.

Common devicetree definitions for K6X are stored in nxp_k6x.dtsi, which is included by both board .dts files. nxp_k6x.dtsi in turn includes armv7-m.dtsi, which has common definitions for Arm v7-M cores.

Since nxp_k6x.dtsi is meant to be generic across K6X-based boards, it leaves many devices disabled by default using status properties. For example, there is a CAN controller defined as follows (with unimportant parts skipped):

can0: can@40024000 {
     ...
     status = "disabled";
     ...
};

It is up to the board .dts or application overlay files to enable these devices as desired, by setting status = "okay". The board .dts files are also responsible for any board-specific configuration of the device, such as adding nodes for on-board sensors, LEDs, buttons, etc.

For example, FRDM-K64 (but not Hexiwear K64) .dts enables the CAN controller and sets the bus speed:

&can0 {
     status = "okay";
     bus-speed = <125000>;
};

The &can0 { ... }; syntax adds/overrides properties on the node with label can0, i.e. the can@4002400 node defined in the .dtsi file.

Other examples of board-specific customization is pointing properties in aliases and chosen to the right nodes (see aliases and chosen nodes), and making GPIO/pinmux assignments.

Devicetree vs Kconfig

Along with devicetree, Zephyr also uses the Kconfig language to configure the source code. Whether to use devicetree or Kconfig for a particular purpose can sometimes be confusing. This section should help you decide which one to use.

In short:

  • Use devicetree to describe hardware and its boot-time configuration. Examples include peripherals on a board, boot-time clock frequencies, interrupt lines, etc.

  • Use Kconfig to configure software support to build into the final image. Examples include whether to add networking support, which drivers are needed by the application, etc.

In other words, devicetree mainly deals with hardware, and Kconfig with software.

For example, consider a board containing a SoC with 2 UART, or serial port, instances.

  • The fact that the board has this UART hardware is described with two UART nodes in the devicetree. These provide the UART type (via the compatible property) and certain settings such as the address range of the hardware peripheral registers in memory (via the reg property).

  • Additionally, the UART boot-time configuration is also described with devicetree. This could include configuration such as the RX IRQ line’s priority and the UART baud rate. These may be modifiable at runtime, but their boot-time configuration is described in devicetree.

  • Whether or not to include software support for UART in the build is controlled via Kconfig. Applications which do not need to use the UARTs can remove the driver source code from the build using Kconfig, even though the board’s devicetree still includes UART nodes.

As another example, consider a device with a 2.4GHz, multi-protocol radio supporting both the Bluetooth Low Energy and 802.15.4 wireless technologies.

  • Devicetree should be used to describe the presence of the radio hardware, what driver or drivers it’s compatible with, etc.

  • Boot-time configuration for the radio, such as TX power in dBm, should also be specified using devicetree.

  • Kconfig should determine which software features should be built for the radio, such as selecting a BLE or 802.15.4 protocol stack.

There are two noteworthy exceptions to these rules:

  • Devicetree’s chosen keyword, which allows the user to select a specific instance of a hardware device to be used for a particular purpose. An example of this is selecting a particular UART for use as the system’s console.

  • Devicetree’s status keyword, which allows the user to enable or disable a particular instance of a hardware device. This takes precedence over related Kconfig options which serve a similar purpose.

Currently supported boards

Devicetree is currently supported on all embedded targets except posix (boards/posix).

Adding support for a board

Adding devicetree support for a given board requires adding a number of files. These files will contain the DTS information that describes a platform, the bindings in YAML format, and any fixup files required to support the platform.

It is best practice to separate common peripheral information that could be used across multiple cores, SoC families, or boards in .dtsi files, reserving the .dts suffix for the primary DTS file for a given board.

Devicetree Source File Template

A board’s .dts file contains at least a version line, optional includes, and a root node definition with model and compatible properties. These property values denote the particular board.

/dts-v1/;

#include <vendor/soc.dtsi>

/ {
        model = "Human readable board name";
        compatible = "vendor,soc-on-your-board's-mcu";
        /* rest of file */
};

You can use other board .dts files as a starting point.

The following is a more precise list of required files:

  • Base architecture support

    • Add architecture-specific DTS directory, if not already present. Example: dts/arm for Arm.

    • Add target specific devicetree files for base SoC. These should be .dtsi files to be included in the board-specific devicetree files.

    • Add target specific YAML binding files in the dts/bindings/ directory. Create the yaml directory if not present.

  • SoC family support

    • Add one or more SoC family .dtsi files that describe the hardware for a set of devices. The file should contain all the relevant nodes and base configuration that would be applicable to all boards utilizing that SoC family.

    • Add SoC family YAML binding files that describe the nodes present in the .dtsi file.

  • Board specific support

    • Add a board level .dts file that includes the SoC family .dtsi files and enables the nodes required for that specific board.

    • Board .dts file should specify the SRAM and FLASH devices, if present.

      • Flash device node might specify flash partitions. For more details see Flash Partitions

    • Add board-specific YAML binding files, if required. This would occur if the board has additional hardware that is not covered by the SoC family .dtsi/.yaml files.

  • Fixup files

    • Fixup files contain mappings from existing Kconfig options to the actual underlying DTS derived configuration #defines. Fixup files are temporary artifacts until additional DTS changes are made to make them unnecessary.

  • Overlay Files (optional)

    • Overlay files contain tweaks or changes to the SoC and Board support files described above. They can be used to modify devicetree configurations without having to change the SoC and Board files. See Devicetree Overlays for more information on overlay files and the Zephyr build system.

aliases and chosen nodes

Using an alias with a common name for a particular node makes it easier for you to write board-independent source code. Devicetree aliases nodes are used for this purpose, by mapping certain generic, commonly used names to specific hardware resources:

aliases {
   led0 = &led0;
   sw0 = &button0;
   sw1 = &button1;
   uart-0 = &uart0;
   uart-1 = &uart1;
};

Certain software subsystems require a specific hardware resource to bind to in order to function properly. Some of those subsystems are used with many different boards, which makes using the devicetree chosen nodes very convenient. By doing, so the software subsystem can rely on having the specific hardware peripheral assigned to it. In the following example we bind the shell to uart1 in this board:

chosen {
   zephyr,shell-uart = &uart1;
};

The full set of Zephyr-specific chosen nodes follows:

chosen node name

Generated symbol

zephyr,flash

CONFIG_FLASH

zephyr,sram

CONFIG_SRAM_SIZE/CONFIG_SRAM_BASE_ADDRESS (via DT_SRAM_SIZE/DT_SRAM_BASE_ADDRESS)

zephyr,ccm

DT_CCM

zephyr,console

DT_UART_CONSOLE_ON_DEV_NAME

zephyr,shell-uart

DT_UART_SHELL_ON_DEV_NAME

zephyr,bt-uart

DT_BT_UART_ON_DEV_NAME

zephyr,uart-pipe

DT_UART_PIPE_ON_DEV_NAME

zephyr,bt-mon-uart

DT_BT_MONITOR_ON_DEV_NAME

zephyr,uart-mcumgr

DT_UART_MCUMGR_ON_DEV_NAME

Adding support for devicetree in drivers

As drivers and other source code is converted over to make use of devicetree generated information, these drivers may require changes to match the generated #define information.

Source Tree Hierarchy

The devicetree files are located in a couple of different directories. The directory split is done based on architecture, and there is also a common directory where architecture agnostic devicetree and YAML binding files are located.

Assuming the current working directory is the ZEPHYR_BASE, the directory hierarchy looks like the following:

dts/common/
dts/<ARCH>/
dts/bindings/
boards/<ARCH>/<BOARD>/

The common directory contains a skeleton.dtsi which provides devicetree root node definition. The bindings subdirectory contains YAML binding files used to instruct how the python DTS parsing script should extract nodes information in a format that will be usable by the system.

Example: Subset of DTS/YAML files for NXP FRDM K64F (Subject to Change):

dts/arm/armv7-m.dtsi
dts/arm/k6x/nxp_k6x.dtsi
boards/arm/frdm_k64f/frdm_k64f.dts
dts/bindings/interrupt-controller/arm,v7m-nvic.yaml
dts/bindings/gpio/nxp,kinetis-gpio.yaml
dts/bindings/pinctrl/nxp,kinetis-pinmux.yaml
dts/bindings/serial/nxp,kinetis-uart.yaml

Devicetree Bindings

.dts files describe the available hardware devices, but don’t tell the system which pieces of information are useful, or what kind of configuration output (#define’s) should be generated. Bindings provide this information. Bindings are files in YAML format.

Configuration output is only generated for devices that have bindings.

Nodes are mapped to bindings via their compatible string(s). Take the following node as an example:

bar-device {
     compatible = "foo-company,bar-device";
     ...
};

This node would get mapped to a binding with this in it:

compatible: "foo-company,bar-device"

You might also run across this legacy syntax, which works the same way:

...

properties:
    compatible:
        constraint: "foo-company,bar-device"

    ...

Bindings are stored in dts/bindings/. The filename usually matches the compatible string.

If a node has more than one compatible string, then the first binding found is used, going from the first string to the last. For example, a node with compatible = "foo-company,bar-device", "generic-bar-device" would get mapped to the binding for generic-bar-device if there is no binding for foo-company,bar-device.

If a node appears on a bus (e.g. I2C or SPI), then the bus type is also taken into account when mapping nodes to bindings. See the description of parent and child in the template below.

Below is a template that shows the format of binding files, stored in dts/binding-template.yaml.

title: Short description of the node

description: |
    Longer free-form description of the node. Can have multiple
    lines/paragraphs.

    See https://yaml-multiline.info/ for formatting help.

# Used to map nodes to bindings
compatible: "manufacturer,device"

# The 'compatible' above would match this node:
#
#     device {
#         compatible = "manufacturer,device";
#         ...
#     };
#
# Assuming no binding has 'compatible: "manufacturer,device-v2"', it would also
# match this node:
#
#     device {
#         compatible = "manufacturer,device-v2", "manufacturer,device";
#         ...
#     };
#
# Strings in 'compatible' properties on nodes are tried from left to right, and
# the first binding found is used.
#
# If more than one binding for a compatible is found, an error is raised.

# Bindings can include other files, which can be used to share common
# definitions between bindings.
#
# Included files are merged into bindings with a simple recursive dictionary
# merge. It is up to the binding author to make sure that the final merged
# binding is well-formed, though it is checked by the code as well.
#
# It is an error if a key appears with a different value in a binding and in a
# file it includes, with one exception: A binding can have 'required: true' for
# some property for which the included file has 'required: false' (see the
# description of 'properties' below). The 'required: true' from the binding
# takes precedence, allowing bindings to strengthen requirements from included
# files.
#
# Note that weakening requirements by having 'required: false' where the
# included file has 'required: true' is an error. This is meant to keep the
# organization clean.
#
# The file base.yaml contains definitions for many common properties. When
# writing a new binding, it is a good idea to check if base.yaml already
# defines some of the needed properties, and including it in that case. Note
# that you can make a property defined in base.yaml obligatory like this
# (taking 'reg' as an example):
#
#     reg:
#         required: true
#
# This relies on the dictionary merge to fill in the other keys for 'reg', like
# 'type'.
#
# When including multiple files, any overlapping 'required' keys on properties
# in the included files are ORed together. This makes sure that a
# 'required: true' is always respected.
include: other.yaml # or [other1.yaml, other2.yaml]

# If the node describes a bus, then the bus type should be given, like below.
# The name has "child" in it since it describes the bus children of the node
# appear on.
child-bus: <string describing bus type, e.g. "i2c">

# If the node appears on a bus, then the bus type should be given, like below.
#
# When looking for a binding for a node, the code checks if the binding for the
# parent node contains 'child-bus: <bus type>'. If it does, then only bindings
# with a matching 'parent-bus: <bus type>' are considered. This allows the same
# type of device to have different bindings depending on what bus it appears
# on.
parent-bus: <string describing bus type, e.g. "i2c">

# 'properties' describes properties on the node, e.g.
#
#   reg = <1 2>;
#   current-speed = <115200>;
#   label = "foo";
#
# This is used to check that required properties appear, and to
# control the format of output generated for them. Except for some
# special-cased properties like 'reg', only properties listed here will
# generate output.
#
# A typical property entry looks like this:
#
#   <property name>:
#     required: <true | false>
#     type: <string | int | boolean | array | uint8-array | string-array |
#            phandle | phandles | phandle-array | compound>
#     description: <description of the property>
#     enum:
#       - <item1>
#       - <item2>
#       ...
#       - <itemN>
#     const: <string | int>
#     default: <default>
#
# These types are available:
#
#   - 'type: int' is for properties that are assigned a single 32-bit value,
#     like
#
#         frequency = <100>;
#
#   - 'type: array' is for properties that are assigned zero or more 32-bit
#     values, like
#
#         pin-config = <1 2 3>;
#
#   - 'type: uint8-array' is for properties that are assigned zero or more
#     bytes with the [] syntax, like
#
#         lookup-table = [89 AB CD EF];
#
#     Each byte is given in hex.
#
#     This type is called 'bytestring' in the Devicetree specification.
#
#   - 'type: string' is for properties that are assigned a single string, like
#
#         ident = "foo";
#
#   - 'type: string-array' if for properties that are assigned zero or more
#     strings, like
#
#         idents = "foo", "bar", "baz";
#
#   - 'type: boolean' is for properties used as flags that don't take a value,
#     like
#
#         hw-flow-control;
#
#     The macro generated for the property gets set to 1 if the property exists
#     on the node, and to 0 otherwise. When combined with 'required: true',
#     this type just forces the flag to appear on the node. The output will
#     always be 1 in that case.
#
#     Warning: Since a macro is always generated for 'type: boolean'
#     properties, don't use #ifdef in tests. Do this instead:
#
#         #if DT_SOME_BOOLEAN_PROP == 1
#
#   - 'type: phandle' is for properties that are assigned a single phandle,
#     like
#
#         foo = <&label>;
#
#   - 'type: phandles' is for properties that are assigned zero or more
#     phandles, like
#
#         foo = <&label1 &label2 ...>;
#
#   - 'type: phandle-array' is for properties that take a list of phandles and
#     (possibly) 32-bit numbers, like
#
#         pwms = <&ctrl-1 1 2 &ctrl-2 3 4>;
#
#     This type requires that the property works in the standard way that
#     devicetree properties like pwms, clocks, *-gpios, and io-channels work.
#     Taking 'pwms' as an example, the final -s is stripped from the property
#     name, and #pwm-cells is looked up in the node for the controller
#     (&ctrl-1/&ctrl-2) to determine the number of data values after the
#     phandle. The binding for each controller must also have a *-cells key
#     (e.g. pwm-cells), giving names to data values. See below for an
#     explanation of *-cells.
#
#     A *-names (e.g. pwm-names) property can appear on the node as well,
#     giving a name to each entry (the 'pwms' example above has two entries,
#     <&ctrl-1 1 2> and <&ctrl-2 3 4>).
#
#     Because other property names are derived from the name of the property by
#     removing the final -s, the property name must end in -s. An error is
#     raised if it doesn't.
#
#     *-gpios properties are special-cased so that e.g. foo-gpios resolves to
#     #gpio-cells rather than #foo-gpio-cells.
#
#     All phandle-array properties support mapping through *-map properties,
#     e.g. gpio-map. See the devicetree spec.
#
#   - 'type: compound' is a catch-all for more complex types, e.g.
#
#         foo = <&label1>, [01 02];
#
# 'type: array' and the other array types also allow splitting the value into
# several <> blocks, e.g. like this:
#
#     foo = <1 2>, <3 4>;                         // Okay for 'type: array'
#     foo = <&label1 &label2>, <&label3 &label4>; // Okay for 'type: phandles'
#     foo = <&label1 1 2>, <&label2 3 4>;         // Okay for 'type: phandle-array'
#     etc.
#
# The optional 'default:' setting gives a value that will be used if the
# property is missing from the device tree node. If 'default: <default>' is
# given for a property <prop> and <prop> is missing, then the output will be as
# if '<prop> = <default>' had appeared (except YAML data types are used for the
# default value).
#
# Note that it only makes sense to combine 'default:' with 'required: false'.
# Combining it with 'required: true' will raise an error.
#
# See below for examples of 'default:'. Putting 'default:' on any property type
# besides those used in the examples will raise an error.
properties:
    # Describes a property like 'current-speed = <115200>;'. We pretend that
    # it's obligatory for the example node and set 'required: true'.
    current-speed:
        type: int
        required: true
        description: Initial baud rate for bar-device

    # Describes an optional property like 'keys = "foo", "bar";'
    keys:
        type: string-array
        required: false
        description: Keys for bar-device

    # Describes an optional property like 'maximum-speed = "full-speed";
    # the enum specifies known values that the string property may take
    maximum-speed:
        type: string
        required: false
        description: Configures USB controllers to work up to a specific speed.
        enum:
           - "low-speed"
           - "full-speed"
           - "high-speed"
           - "super-speed"

    # Describes a required property '#address-cells = <1>';  the const
    # specifies that the value for the property is expected to be the value 1
    "#address-cells":
        type: int
        required: true
        const: 1

    int-with-default:
        type: int
        required: false
        default: 123

    array-with-default:
        type: array
        required: false
        default: [1, 2, 3] # Same as 'array-with-default = <1 2 3>'

    string-with-default:
        type: string
        required: false
        default: "foo"

    string-array-with-default:
        type: string-array
        required: false
        default: ["foo", "bar"] # Same as 'string-array-with-default = "foo", "bar"'

    uint8-array-with-default:
        type: uint8-array
        required: false
        default: [0x12, 0x34] # Same as 'uint8-array-with-default = [12 34]'

# 'child-binding' can be used when a node has children that all share the same
# properties. Each child gets the contents of 'child-binding' as its binding
# (though an explicit 'compatible = ...' on the child node takes precedence, if
# a binding is found for it).
#
# The example below is for a binding for PWM LEDs, where the child nodes are
# required to have a 'pwms' property. It corresponds to this .dts structure
# (assuming the binding has 'compatible: "pwm-leds"'):
#
# pwmleds {
#         compatible = "pwm-leds";
#
#         red_pwm_led {
#                 pwms = <&pwm3 4 15625000>;
#         };
#         green_pwm_led {
#                 pwms = <&pwm3 0 15625000>;
#         };
#         ...
# };
child-binding:
    title: PWM LED
    description: LED that uses PWM

    properties:
        pwms:
            type: phandle-array
            required: true

# 'child-binding' also works recursively. For example, the binding below would
# provide a binding for the 'grandchild' node in this .dts (assuming
# 'compatible: "foo"'):
#
# parent {
#         compatible = "foo";
#         child {
#                 grandchild {
#                         prop = <123>;
#                 };
#         };
# }
#
# WARNING: Due to implementation issues with legacy code, only up to two levels
# of 'child-binding:' nesting (like below) is supported. This restriction will
# go away in Zephyr 2.2.
child-binding:
    title: ...
    description: ...

    ...

    child-binding:
        title: ...
        description: ...

        properties:
            prop:
                type: int
                required: true

# If the binding describes an interrupt controller, GPIO controller, pinmux
# device, or any other node referenced by other nodes via 'phandle-array'
# properties, then *-cells should be given.
#
# To understand the purpose of *-cells, assume that some node has
#
#     pwms = <&pwm-ctrl 1 2>;
#
# , where &pwm-ctrl refers to a node whose binding is this file.
#
# The <1 2> part of the property value is called a *specifier* (this
# terminology is from the devicetree specification), and contains additional
# data associated with the GPIO. Here, the specifier has two cells, and the
# node pointed at by &gpio-ctrl is expected to have '#pwm-cells = <2>'.
#
# *-cells gives a name to each cell in the specifier. These names are used when
# generating identifiers.
#
# In this example, assume that 1 refers to a pin and that 2 is a flag value.
# This gives a *-cells assignment like below.
pwm-cells:
    - channel # name of first cell
    - period  # name of second cell

# If the specifier is empty (e.g. '#clock-cells = <0>'), then *-cells can
# either be omitted (recommended) or set to an empty array. Note that an empty
# array is specified as e.g. 'clock-cells: []' in YAML.

# As a special case, all *-gpio properties map to the key 'gpio-cells',
# regardless of prefix
gpio-cells:
    - pin
    - flags

# This older syntax is deprecated and will generate a warning when used. It
# works as a catch-all, where the name of the referencing 'phandle-array'
# property doesn't matter.
"#cells":
    - pin
    - flags

Legacy binding syntax

Various parts of the binding syntax were simplified and generalized for the Zephyr 2.1 release.

The binding below shows various legacy syntax.

title: ...
description: ...

inherits:
    !include foo.yaml

parent:
    bus: spi

properties:
    compatible:
        constraint: "company,device"
        type: string-array

    frequency:
        type: int
        category: optional

sub-node:
    properties:
        child-prop:
            type: int
            category: required

# Assume this is a binding for an interrupt controller
"#cells":
    - irq
    - priority
    - flags

This should now be written like this:

title: ...
description: ...

compatible: "company,device"

include: foo.yaml

parent-bus: spi

properties:
    frequency:
        type: int
        required: false

child-binding:
    title: ...
    description: ...

    properties:
        child-prop:
            type: int
            required: true

interrupt-cells:
    - irq
    - priority
    - cells

The legacy syntax is still supported for backwards compatibility, but generates deprecation warnings. Support will be dropped in the Zephyr 2.3 release.

Include files generation

At build time, after a board’s .dts file has been processed by the DTC (Devicetree Compiler), a corresponding .dts_compiled file is generated under the zephyr directory. This .dts_compiled file is processed by the python DTS parsing script and generates an include file named include/generated/generated_dts_board_unfixed.h that holds all the information extracted from the DTS file with the format specified by the YAML bindings. For example:

/* gpio_keys */
#define DT_GPIO_KEYS_0               1

/* button_0 */
#define DT_GPIO_KEYS_BUTTON_0_GPIOS_CONTROLLER       "GPIO_2"
#define DT_GPIO_KEYS_BUTTON_0_GPIOS_FLAGS    0
#define DT_GPIO_KEYS_BUTTON_0_GPIOS_PIN              6
#define DT_GPIO_KEYS_BUTTON_0_LABEL          "User SW2"

#define DT_GPIO_KEYS_SW1_GPIOS_CONTROLLER            DT_GPIO_KEYS_BUTTON_0_GPIOS_CONTROLLER
#define DT_GPIO_KEYS_SW1_GPIOS_FLAGS                 DT_GPIO_KEYS_BUTTON_0_GPIOS_FLAGS
#define DT_GPIO_KEYS_SW1_GPIOS_PIN                   DT_GPIO_KEYS_BUTTON_0_GPIOS_PIN
#define DT_ALIAS_SW1_GPIOS_CONTROLLE                 DT_GPIO_KEYS_BUTTON_0_GPIOS_CONTROLLER
#define DT_ALIAS_SW1_GPIOS_FLAGS                     DT_GPIO_KEYS_BUTTON_0_GPIOS_FLAGS
#define DT_ALIAS_SW1_GPIOS_PIN                       DT_GPIO_KEYS_BUTTON_0_GPIOS_PIN
#define DT_ALIAS_SW1_LABEL                           DT_GPIO_KEYS_BUTTON_0_LABEL

Additionally, a file named generated_dts_board_fixups.h is generated in the same directory concatenating all board-related fixup files.

The include file include/generated_dts_board.h includes both these generated files, giving Zephyr C source files access to the board’s devicetree information.

GPIO Nexus Nodes

Each board has a set of General Purpose Input/Output (GPIO) peripherals that can be accessed through the GPIO module. Many boards provide headers that allow shields from other vendors to be mounted on their boards. Each shield identifies its hardware in a devicetree overlay.

GPIOs accessed by the shield peripherals must be identified using the shield GPIO abstraction, for example from the arduino-r3-header compatible. Boards that provide the header must map the header pins to SOC-specific pins. This is accomplished by including a nexus node that looks like the following into the board devicetree file:

arduino_header: connector {
        compatible = "arduino-header-r3";
        #gpio-cells = <2>;
        gpio-map-mask = <0xffffffff 0xffffffc0>;
        gpio-map-pass-thru = <0 0x3f>;
        gpio-map = <0 0 &gpioa 0 0>,    /* A0 */
                   <1 0 &gpioa 1 0>,    /* A1 */
                   <2 0 &gpioa 4 0>,    /* A2 */
                   <3 0 &gpiob 0 0>,    /* A3 */
                   <4 0 &gpioc 1 0>,    /* A4 */
                   <5 0 &gpioc 0 0>,    /* A5 */
                   <6 0 &gpioa 3 0>,    /* D0 */
                   <7 0 &gpioa 2 0>,    /* D1 */
                   <8 0 &gpioa 10 0>,   /* D2 */
                   <9 0 &gpiob 3 0>,    /* D3 */
                   <10 0 &gpiob 5 0>,   /* D4 */
                   <11 0 &gpiob 4 0>,   /* D5 */
                   <12 0 &gpiob 10 0>,  /* D6 */
                   <13 0 &gpioa 8 0>,   /* D7 */
                   <14 0 &gpioa 9 0>,   /* D8 */
                   <15 0 &gpioc 7 0>,   /* D9 */
                   <16 0 &gpiob 6 0>,   /* D10 */
                   <17 0 &gpioa 7 0>,   /* D11 */
                   <18 0 &gpioa 6 0>,   /* D12 */
                   <19 0 &gpioa 5 0>,   /* D13 */
                   <20 0 &gpiob 9 0>,   /* D14 */
                   <21 0 &gpiob 8 0>;   /* D15 */
};

This specifies how Arduino pin references like <&arduino_header 11 0> are converted to SOC gpio pin references like <&gpiob 4 0>.

In Zephyr GPIO specifiers generally have two parameters (indicated by #gpio-cells = <2>): the pin number and a set of flags. The low 6 bits of the flags correspond to features that can be configured in devicetree. In some cases it’s necessary to use a non-zero flag value to tell the driver how a particular pin behaves, as with:

drdy-gpios = <&arduino_header 11 GPIO_ACTIVE_LOW>;

After preprocessing this becomes <&arduino_header 11 1>. Normally the presence of such a flag would cause the map lookup to fail, because there is no map entry with a non-zero flags value. The gpio-map-mask property specifies that, for lookup, all bits of the pin and all but the low 6 bits of the flags are used to identify the specifier. Then the gpio-map-pass-thru specifies that the low 6 bits of the flags are copied over, so the SOC GPIO reference becomes <&gpiob 4 1> as intended.

See nexus node for more information about this capability.

Flash Partitions

Device tree can be used to describe a partition layout for any flash device in the system.

Two important uses for this mechanism are:

  1. To force the Zephyr image to be linked into a specific area on Flash.

    This is useful, for example, if the Zephyr image must be linked at some offset from the flash device’s start, to be loaded by a bootloader at runtime.

  2. To generate compile-time definitions for the partition layout, which can be shared by Zephyr subsystems and applications to operate on specific areas in flash.

    This is useful, for example, to create areas for storing file systems or other persistent state. These defines only describe the boundaries of each partition. They don’t, for example, initialize a partition’s flash contents with a file system.

Partitions are generally managed using device tree overlays. Refer to Devicetree Overlays for details on using overlay files.

Defining Partitions

The partition layout for a flash device is described inside the partitions child node of the flash device’s node in the device tree.

You can define partitions for any flash device on the system.

Most Zephyr-supported SoCs with flash support in device tree will define a label flash0. This label refers to the primary on-die flash programmed to run Zephyr. To generate partitions for this device, add the following snippet to a device tree overlay file:

&flash0 {
        partitions {
                compatible = "fixed-partitions";
                #address-cells = <1>;
                #size-cells = <1>;

                /* Define your partitions here; see below */
        };
};

To define partitions for another flash device, modify the above to either use its label or provide a complete path to the flash device node in the device tree.

The content of the partitions node looks like this:

partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        partition1_label: partition@START_OFFSET_1 {
                label = "partition1_name";
                reg = <0xSTART_OFFSET_1 0xSIZE_1>;
        };

        /* ... */

        partitionN_label: partition@START_OFFSET_N {
                label = "partitionN_name";
                reg = <0xSTART_OFFSET_N 0xSIZE_N>;
        };
};

Where:

  • partitionX_label are device tree labels that can be used elsewhere in the device tree to refer to the partition

  • partitionX_name controls how defines generated by the Zephyr build system for this partition will be named

  • START_OFFSET_x is the start offset in hexadecimal notation of the partition from the beginning of the flash device

  • SIZE_x is the hexadecimal size, in bytes, of the flash partition

The partitions do not have to cover the entire flash device. The device tree compiler currently does not check if partitions overlap; you must ensure they do not when defining them.

Example Primary Flash Partition Layout

Here is a complete (but hypothetical) example device tree overlay snippet illustrating these ideas. Notice how the partitions do not overlap, but also do not cover the entire device.

&flash0 {
        partitions {
                compatible = "fixed-partitions";
                #address-cells = <1>;
                #size-cells = <1>;

                code_dts_label: partition@8000 {
                        label = "zephyr-code";
                        reg = <0x00008000 0x34000>;
                };

                data_dts_label: partition@70000 {
                        label = "application-data";
                        reg = <0x00070000 0xD000>;
                };
        };
};

Linking Zephyr Within a Partition

To force the linker to output a Zephyr image within a given flash partition, add this to a device tree overlay:

/ {
        chosen {
                zephyr,code-partition = &slot0_partition;
        };
};

If the chosen node has no zephyr,code-partition property, the application image link uses the entire flash device. If a zephyr,code-partition property is defined, the application link will be restricted to that partition.

Flash Partition Macros

The Zephyr build system generates definitions for each flash device partition. These definitions are available to any files which include <zephyr.h>.

Consider this flash partition:

dts_label: partition@START_OFFSET {
        label = "def-name";
        reg = <0xSTART_OFFSET 0xSIZE>;
};

The build system will generate the following corresponding defines:

#define FLASH_AREA_DEF_NAME_LABEL        "def-name"
#define FLASH_AREA_DEF_NAME_OFFSET_0     0xSTART_OFFSET
#define FLASH_AREA_DEF_NAME_SIZE_0       0xSIZE
#define FLASH_AREA_DEF_NAME_OFFSET       FLASH_AREA_MCUBOOT_OFFSET_0
#define FLASH_AREA_DEF_NAME_SIZE         FLASH_AREA_MCUBOOT_SIZE_0

As you can see, the label property is capitalized when forming the macro names. Other simple conversions to ensure it is a valid C identifier, such as converting “-” to “_”, are also performed. The offsets and sizes are available as well.

MCUboot Partitions

MCUboot is a secure bootloader for 32-bit microcontrollers.

Some Zephyr boards provide definitions for the flash partitions which are required to build MCUboot itself, as well as any applications which must be chain-loaded by MCUboot.

The device tree labels for these partitions are:

boot_partition

This is the partition where the bootloader is expected to be placed. MCUboot’s build system will attempt to link the MCUboot image into this partition.

slot0_partition

MCUboot loads the executable application image from this partition. Any application bootable by MCUboot must be linked to run from this partition.

slot1_partition

This is the partition which stores firmware upgrade images. Zephyr applications which receive firmware updates must ensure the upgrade images are placed in this partition (the Zephyr DFU subsystem can be used for this purpose). MCUboot checks for upgrade images in this partition, and can move them to slot0_partition for execution. The slot0_partition and slot1_partition must be the same size.

scratch_partition

This partition is used as temporary storage while swapping the contents of slot0_partition and slot1_partition.

Important

Upgrade images are only temporarily stored in slot1_partition. They must be linked to execute of out of slot0_partition.

See the MCUboot documentation for more details on these partitions.

File System Partitions

storage_partition

This is the area where e.g. NFFS expects its partition.