Basics of Device Tree (Part 4): Common Device Descriptions

1. I2C Devices

1.1 I2C Controller

Description of the I2C bus controller node:

i2c0: i2c@10002000 {
    compatible = "arm,versatile-i2c";
    reg = <0x10002000 0x1000>;
    interrupts = <0 30 4>;
    #address-cells = <1>;
    #size-cells = <0>;
    clocks = <&osc24M>;
    clock-frequency = <100000>;  /* 100KHz standard mode */
    status = "okay";
};

Key Attribute Descriptions:

  • <span>#address-cells = <1></span>: The I2C device address is represented by one cell
  • <span>#size-cells = <0></span>: I2C devices have no size concept
  • <span>clock-frequency</span>: I2C bus frequency (100K/400K/1M, etc.)

1.2 I2C Device Nodes

I2C devices as child nodes of the I2C controller:

EEPROM Device

i2c0: i2c@10002000 {
    #address-cells = <1>;
    #size-cells = <0>;

    /* AT24C32 EEPROM */
    eeprom: at24c32@50 {
        compatible = "atmel,24c32";
        reg = <0x50>;              /* I2C device address */
        pagesize = <32>;            /* Page size 32 bytes */
        size = <4096>;             /* Total capacity 4KB */
        address-width = <16>;      /* Address width 16 bits */
    };
};

Temperature Sensor

i2c0: i2c@10002000 {
    #address-cells = <1>;
    #size-cells = <0>;

    /* LM75 Temperature Sensor */
    temp_sensor: lm75@48 {
        compatible = "national,lm75";
        reg = <0x48>;
        vs-supply = <&vdd_3v3>;
        temperature-max = <85000>;  /* Maximum temperature 85°C */
        temperature-min = <-40000>; /* Minimum temperature -40°C */
    };
};

RTC Clock Chip

i2c0: i2c@10002000 {
    #address-cells = <1>;
    #size-cells = <0>;

    /* DS1307 RTC */
    rtc: ds1307@68 {
        compatible = "dallas,ds1307";
        reg = <0x68>;
        interrupt-parent = <&gpio0>;
        interrupts = <20 IRQ_TYPE_EDGE_FALLING>;
    };
};

2. SPI Devices

2.1 SPI Controller

Description of the SPI bus controller node:

spi0: spi@10004000 {
    compatible = "arm,pl022", "arm,primecell";
    reg = <0x10004000 0x1000>;
    interrupts = <0 32 4>;
    #address-cells = <1>;
    #size-cells = <0>;
    clocks = <&osc24M>, <&osc24M>;
    clock-names = "spiclk", "apb_pclk";
    status = "okay";
};

2.2 SPI Device Nodes

SPI devices as child nodes of the SPI controller, using<span>reg</span> to specify the chip select number:

SPI Flash

spi0: spi@10004000 {
    #address-cells = <1>;
    #size-cells = <0>;

    /* SPI NOR Flash */
    flash: m25p80@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;                 /* Chip select 0 */
        spi-max-frequency = <40000000>;

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

            partition@0 {
                label = "bootloader";
                reg = <0x0 0x40000>;
                read-only;
            };

            partition@40000 {
                label = "kernel";
                reg = <0x40000 0x300000>;
            };
        };
    };
};

SPI ADC Device

spi0: spi@10004000 {
    #address-cells = <1>;
    #size-cells = <0>;

    /* MCP3008 ADC */
    adc: mcp3008@1 {
        compatible = "microchip,mcp3008";
        reg = <1>;                 /* Chip select 1 */
        spi-max-frequency = <1000000>;
        vref-supply = <&vdd_3v3>;
    };
};

3. GPIO Devices

3.1 GPIO Controller

GPIO controller node:

gpio0: gpio@10015000 {
    compatible = "arm,pl061", "arm,primecell";
    reg = <0x10015000 0x1000>;
    interrupts = <0 8 4>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    clocks = <&osc24M>;
    clock-names = "apb_pclk";
};

Key Attributes:

  • <span>gpio-controller</span>: Identifies as a GPIO controller
  • <span>#gpio-cells = <2></span>: GPIO references require two parameters (pin number, flags)
  • <span>interrupt-controller</span>: Can act as an interrupt controller
  • <span>#interrupt-cells = <2></span>: Interrupt references require two parameters (pin number, trigger type)

3.2 GPIO Usage Examples

GPIO LED

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

leds {
    compatible = "gpio-leds";

    led0: led-0 {
        label = "status";
        gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
        default-state = "on";
    };

    led1: led-1 {
        label = "error";
        gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
        linux,default-trigger = "heartbeat";
    };
};

GPIO Button

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

gpio_keys {
    compatible = "gpio-keys";

    button0: button-0 {
        label = "User Button";
        linux,code = <116>;  /* KEY_POWER */
        gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
        gpio-key,wakeup;
    };

    button1: button-1 {
        label = "Reset Button";
        linux,code = <408>;  /* KEY_RESTART */
        gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
        interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
    };
};

GPIO Expander Chip

i2c0: i2c@10002000 {
    #address-cells = <1>;
    #size-cells = <0>;

    /* PCA9555 GPIO Expander */
    gpio_expander: pca9555@20 {
        compatible = "nxp,pca9555";
        reg = <0x20>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
        interrupt-parent = <&gpio0>;
        interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
    };
};

/* Using expanded GPIO */
custom_device {
    compatible = "vendor,custom";
    enable-gpios = <&gpio_expander 0 GPIO_ACTIVE_HIGH>;
    reset-gpios = <&gpio_expander 1 GPIO_ACTIVE_LOW>;
};

4. PCIe Devices

4.1 PCIe Controller

PCIe controller node:

pcie: pcie@40000000 {
    compatible = "vendor,pcie-controller";
    reg = <0x40000000 0x1000000>,    /* Configuration space */
          <0x50000000 0x20000000>;   /* I/O space */
    reg-names = "config", "io";
    #address-cells = <3>;
    #size-cells = <2>;
    device_type = "pci";
    ranges = <0x81000000 0x0 0x00000000 0x50000000 0x0 0x00010000   /* I/O */
              0x82000000 0x0 0x60000000 0x60000000 0x0 0x10000000>; /* MEM */
    interrupt-map-mask = <0xf800 0x0 0x0 0x7>;
    interrupt-map = <0x0 0x0 0x0 0x1 &gic 0 0 0 88 4>,
                    <0x0 0x0 0x0 0x2 &gic 0 0 0 89 4>,
                    <0x0 0x0 0x0 0x3 &gic 0 0 0 90 4>,
                    <0x0 0x0 0x0 0x4 &gic 0 0 0 91 4>;
    status = "okay";
};

Key Attribute Descriptions:

  • <span>#address-cells = <3></span>: PCI addresses require three cells (bus, device, function)
  • <span>#size-cells = <2></span>: PCI sizes require two cells (64-bit)
  • <span>ranges</span>: Defines PCI address space mapping
  • <span>interrupt-map</span>: Defines interrupt routing

4.2 PCIe Devices

PCIe devices are usually auto-discovered by the kernel but can be configured via the device tree:

pcie: pcie@40000000 {
    /* PCIe devices are automatically enumerated by the kernel */
    /* Device tree can override specific device configurations */
};

/* PCIe device overrides (if supported) */
&pcie {
    /* Some platforms support configuring PCIe devices via device tree */
};

5. Interrupt Configuration

5.1 Interrupt Controller

GIC Interrupt Controller

#include <dt-bindings/interrupt-controller/arm-gic.h>

gic: interrupt-controller@1e001000 {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x1e001000 0x1000>,
          <0x1e000100 0x100>;
};

Interrupt Attribute Format:

  • <span><GIC_SPI interrupt-num trigger-type></span>
  • <span>GIC_SPI</span>: Shared peripheral interrupt
  • <span>GIC_PPI</span>: Private peripheral interrupt
  • Trigger types:<span>IRQ_TYPE_LEVEL_HIGH</span>, <span>IRQ_TYPE_EDGE_RISING</span>, etc.

GPIO Interrupt Controller

gpio0: gpio@10015000 {
    compatible = "arm,pl061";
    reg = <0x10015000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    interrupt-parent = <&gic>;
    interrupts = <0 8 4>;
};

5.2 Device Interrupt Configuration

Standard Interrupt Attributes

#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/interrupt-controller/irq.h>

uart0: serial@10009000 {
    compatible = "arm,pl011";
    reg = <0x10009000 0x1000>;
    interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-names = "uart";
};

Multiple Interrupts

device@20000000 {
    compatible = "vendor,multi-irq-device";
    reg = <0x20000000 0x1000>;
    interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 51 IRQ_TYPE_EDGE_RISING>;
    interrupt-names = "main", "aux";
};

GPIO Interrupt

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

sensor@30000000 {
    compatible = "vendor,sensor";
    reg = <0x30000000 0x1000>;
    interrupt-parent = <&gpio0>;
    interrupts = <10 IRQ_TYPE_EDGE_FALLING>;
};

Interrupt Expansion

/* Using interrupts from GPIO expander */
i2c0: i2c@10002000 {
    gpio_expander: pca9555@20 {
        compatible = "nxp,pca9555";
        reg = <0x20>;
        interrupt-controller;
        #interrupt-cells = <2>;
        interrupt-parent = <&gpio0>;
        interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
    };
};

/* Device using expander interrupts */
sensor {
    compatible = "vendor,sensor";
    interrupt-parent = <&gpio_expander>;
    interrupts = <5 IRQ_TYPE_EDGE_RISING>;
};

6. Comprehensive Example

The following is a comprehensive example that includes various devices:

/dts-v1/;

#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    compatible = "vendor,demo-board";
    model = "Demo Board v1.0";

    #address-cells = <1>;
    #size-cells = <1>;

    /* Interrupt Controller */
    gic: interrupt-controller@1e001000 {
        compatible = "arm,cortex-a9-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x1e001000 0x1000>,
              <0x1e000100 0x100>;
    };

    /* GPIO Controller */
    gpio0: gpio@10015000 {
        compatible = "arm,pl061";
        reg = <0x10015000 0x1000>;
        interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    /* I2C Controller */
    i2c0: i2c@10002000 {
        compatible = "arm,versatile-i2c";
        reg = <0x10002000 0x1000>;
        interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
        #address-cells = <1>;
        #size-cells = <0>;
        clock-frequency = <100000>;

        /* EEPROM */
        eeprom@50 {
            compatible = "atmel,24c32";
            reg = <0x50>;
            pagesize = <32>;
        };

        /* Temperature Sensor */
        temp_sensor@48 {
            compatible = "national,lm75";
            reg = <0x48>;
        };
    };

    /* SPI Controller */
    spi0: spi@10004000 {
        compatible = "arm,pl022";
        reg = <0x10004000 0x1000>;
        interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
        #address-cells = <1>;
        #size-cells = <0>;

        /* SPI Flash */
        flash@0 {
            compatible = "jedec,spi-nor";
            reg = <0>;
            spi-max-frequency = <40000000>;
        };
    };

    /* GPIO LED */
    leds {
        compatible = "gpio-leds";
        led0: led-0 {
            label = "status";
            gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
            default-state = "on";
        };
    };

    /* GPIO Buttons */
    gpio_keys {
        compatible = "gpio-keys";
        button0: button-0 {
            label = "User Button";
            linux,code = <116>;
            gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
        };
    };
};

7. Summary

In this section, we learned about the device tree description methods for common devices:

Key Points:

  • I2C Devices:<span>#size-cells = <0></span>, device address is in<span>reg</span>
  • SPI Devices: Use<span>reg</span> to specify chip select number,<span>spi-max-frequency</span> to set frequency
  • GPIO Devices: Require<span>gpio-controller</span> and<span>#gpio-cells</span> attributes
  • PCIe Devices: Complex address mapping and interrupt routing
  • Interrupt Configuration: Use standard interrupt attributes, support multi-level interrupts

Best Practices:

  1. Follow device binding specifications: Use standard<span>compatible</span> strings
  2. Correctly set address cells: Set<span>#address-cells</span> and<span>#size-cells</span> according to bus type
  3. Use header file constants: Define interrupt types, GPIO states, etc., through header files
  4. Organize nodes logically: Group by function, maintain clear hierarchy
  5. Add necessary comments: Explain device purpose and key parameters

Series Summary: Through this series of four articles, we have learned the basic concepts of device trees, compilation commands, syntax specifications, and common device descriptions. The device tree, as a standard method for hardware description, plays an important role in embedded Linux development. Mastering the use of device trees can greatly improve development efficiency and system portability.

Leave a Comment