Introduction
For anyone developing custom Android devices, especially those based on new or specialized Systems-on-Chip (SoCs), understanding and manipulating the Device Tree Source (.dts) is paramount. The Device Tree is a data structure that describes the hardware components of a system, making it possible for a single kernel image to support multiple hardware configurations. In the Android ecosystem, where device diversity is immense, the Device Tree provides a flexible and efficient way to convey hardware specifics to the Linux kernel without hardcoding, thereby simplifying kernel development and maintenance for custom SoCs.
This deep dive will explore the fundamentals of Android Device Tree, its structure, common properties, and provide practical guidance on how to modify it for your custom SoC, covering everything from identifying hardware to compiling and debugging.
The Essence of Device Tree
What is Device Tree?
At its core, a Device Tree is a hierarchical data structure that describes the non-discoverable hardware components of a system. Before Device Tree, kernel developers had to maintain numerous board-specific files, leading to code duplication and maintenance nightmares. The Device Tree aims to solve this problem by abstracting hardware details from the kernel. It’s written in Device Tree Source (.dts) files, which are then compiled into a Device Tree Blob (.dtb) by the Device Tree Compiler (DTC). The .dtb file is passed to the kernel at boot time, allowing the kernel to configure itself dynamically based on the hardware it discovers.
The Linux kernel uses the information in the .dtb to identify and initialize various hardware components such as CPUs, memory, peripheral controllers (I2C, SPI, UART), GPIOs, interrupt controllers, and more. This separation of hardware description from kernel code significantly improves portability and reduces the effort required to support new boards or custom SoCs.
Why it Matters for Custom SoCs
Developing an Android device around a custom SoC presents unique challenges. Often, these SoCs have specialized peripherals, custom pin configurations, and unique power management requirements. Without a robust mechanism to describe this hardware, every minor change would necessitate kernel recompilation or even extensive driver modifications. The Device Tree addresses this by:
- Hardware Abstraction: Providing a standardized way to describe hardware, allowing the kernel to remain generic.
- Flexibility: Easily enabling or disabling peripherals, adjusting addresses, and modifying pinmux settings without altering kernel C code.
- Rapid Prototyping: Accelerating development cycles for new hardware iterations by minimizing kernel-side changes.
- Vendor Independence: Reducing reliance on SoC vendor-specific kernel patches by describing hardware in an open, standardized format.
Device Tree Structure and Syntax
Basic .dts File Anatomy
A .dts file defines a tree structure of nodes, each representing a hardware component. Nodes can have child nodes and properties. Properties are key-value pairs that describe specific attributes of a node.
Key elements include:
- Nodes: Represent hardware components (e.g., CPU, I2C controller, specific sensor). They have a name and often an address (e.g.,
i2c@7800000). - Properties: Describe the node (e.g.,
compatiblestring, `reg` address range, `interrupts`). - Phandles: References to other nodes, enabling connections between hardware components.
- Includes: `include` directives allow breaking down large Device Trees into smaller, manageable `.dtsi` (Device Tree Source Include) files, often used for SoC-specific or common board features.
Common Properties
Some essential properties you’ll encounter:
compatible: A string list identifying the device and its compatible drivers. For example,"vendor,custom-sensor-v1", "generic,sensor".reg: Defines the address and size of the device’s memory-mapped registers. For example,<0x7800000 0x1000>.interrupts: Specifies the interrupt line(s) used by the device and their type (e.g.,<20 IRQ_TYPE_EDGE_RISING>).status: Indicates whether a device is enabled or disabled ("okay"or"disabled").pinctrl-namesandpinctrl-N: Define pin control states for the device.#address-cellsand#size-cells: Used by parent nodes to describe how addresses and sizes are encoded for their children.
Simplified DTS Snippet Example
/ { compatible = "vendor,custom-soc", "generic-soc"; #address-cells = <1>; #size-cells = <1>; i2c1: i2c@7800000 { compatible = "vendor,i2c-controller"; reg = <0x7800000 0x1000>; #address-cells = <1>; #size-cells = <0>; clocks = <&clk_i2c1>; status = "okay"; sensor@68 { compatible = "manufacturer,custom-sensor-v1"; reg = <0x68>; interrupt-parent = <&gpio>; interrupts = <20 IRQ_TYPE_EDGE_RISING>; /* GPIO pin 20, rising edge */ status = "okay"; }; }; gpio: gpio@8000000 { compatible = "vendor,gpio-controller"; reg = <0x8000000 0x1000>; gpio-cells = <2>; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };};
Modifying Device Tree for a Custom SoC
Identifying Hardware Components
Before modifying the DTS, you need a thorough understanding of your custom SoC’s hardware. This involves consulting:
- SoC Datasheets: To understand memory maps, register addresses, interrupt lines, and supported peripherals.
- Schematics: To identify how peripherals are connected (e.g., I2C bus numbers, SPI chip selects, GPIO assignments for interrupts or LEDs).
- Board Layout: Physical placement of components can sometimes hint at connections.
List out all non-discoverable components: I2C devices (sensors, PMICs), SPI devices, UARTs for debugging, display panels, camera modules, custom buttons, LEDs, and their respective connections (I2C address, GPIO pin, interrupt line).
Locating the Base DTS
Your journey begins in the kernel source tree, typically under arch/arm64/boot/dts/ for 64-bit ARM architectures. You’ll find directories like vendor/ or qcom/, rockchip/, etc. Inside, look for the `.dts` file corresponding to your development board or a similar reference board. Often, the main board `.dts` will include several `.dtsi` files that define the SoC’s core peripherals.
Adding a New Device Node
Let’s say you have a new accelerometer (ADXL345) connected to I2C1, with its interrupt line connected to GPIO pin 21. You would locate the `i2c1` node in your board’s DTS (or an included `.dtsi`) and add a child node:
// In your board's .dts file or an appropriate .dtsi fragment, within the i2c1 node&i2c1 { accel@1c { compatible = "adi,adxl345"; reg = <0x1c>; interrupt-parent = <&gpio>; interrupts = <21 IRQ_TYPE_EDGE_FALLING>; // Assuming GPIO 21 for interrupt vdd-supply = <&vcc_3v3>; // Example power supply reference (if applicable) status = "okay"; };};
Here, `&i2c1` is a phandle reference to the I2C1 controller node, allowing us to augment it. The `reg` property `<0x1c>` specifies the I2C slave address. The `interrupt-parent` points to the GPIO controller responsible for pin 21, and `interrupts` details the specific pin and interrupt type.
Modifying Existing Nodes
You might need to enable or reconfigure an existing peripheral. For instance, to enable an LED connected to GPIO pin 5:
// In your board's .dts file, for a specific GPIO lineled_indicator { compatible = "gpio-leds"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_led_gpios>; // Reference to a pinctrl definition led0 { label = "status_led"; gpios = <&gpio 5 GPIO_ACTIVE_HIGH>; // GPIO controller, pin 5, active high default-state = "off"; status = "okay"; };};# Example pinctrl definition, typically in a .dtsi or pinctrl-specific .dts&pinctrl { pinctrl_led_gpios: led_gpios { gpio-hog; // Claim the pin gpios = <5 0>; // Pin 5, no special flags output-low; // Set as output low initially drive-strength = <8>; // 8mA drive strength bias-disable; // Disable pull-up/pull-down };};
This example defines a `gpio-leds` node and a specific `led0` child. Crucially, it references a `pinctrl` (pin control) node, `pinctrl_led_gpios`. Pin controllers define how physical pins are configured (e.g., input/output, pull-up/down, drive strength). You must correctly configure the pinmux for your GPIOs to function.
Compiling and Flashing the DTB
After modifications, the Device Tree Source needs to be compiled into a Device Tree Blob (`.dtb`).
- Navigate to your kernel source:
cd /path/to/your/kernel/source - Build the DTBs: Make sure your kernel configuration (`.config`) is set up for your architecture and board.
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make your_defconfigARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make dtbsThis command will compile all `.dts` files into `.dtb` files. Your specific `.dtb` will typically be found at `arch/arm64/boot/dts//.dtb`.
- Flash the DTB: The `.dtb` needs to be loaded by the bootloader. It can be flashed to a dedicated `dtb` partition or bundled within the `boot.img` (Android’s standard boot image).
# If you have a dedicated DTB partitionfastboot flash dtb <board-name>.dtb# If your DTB is part of the boot.img (more common in modern Android)fastboot flash boot boot.imgYou might need to regenerate your `boot.img` if you’re bundling the `.dtb` with it.
- Verify DTB: After rebooting, you can verify the loaded device tree on the device:
adb shell "ls -lR /proc/device-tree/"This will show the entire Device Tree hierarchy as seen by the kernel. You can also decompile the loaded `.dtb`:
adb shell "cat /sys/firmware/fdt > /sdcard/current.dtb"adb pull /sdcard/current.dtbdtc -I dtb -O dts current.dtb > current.dts
Debugging Device Tree Issues
Common Pitfalls
- Syntax Errors: Missing semicolons, incorrect property formats.
- Incorrect Addresses/Interrupts: Mismatches between DTS and actual hardware.
- `compatible` String Mismatch: If the driver expects `”foo,bar”` but DTS provides `”foo,baz”`, the driver won’t probe.
- Missing `status = “okay”`: Devices might be disabled by default in a parent `.dtsi`.
- Pinmux Conflicts: Incorrect `pinctrl` settings leading to non-functional peripherals.
- Missing `phandles`: References to non-existent nodes.
Debugging Tools
dmesg: The kernel message buffer is your best friend. Look for messages related to your device’s driver probing, I2C/SPI bus errors, or Device Tree parsing errors./proc/device-tree/: Allows you to inspect the live Device Tree.dtc: The Device Tree Compiler can be used to decompile a `.dtb` back to `.dts` for inspection, helpful for comparing your compiled `.dtb` against your source.
Example `dmesg` output showing a driver probe failure:
[ 10.123456] custom-sensor manufacturer,custom-sensor-v1: probe of 7-0068 failed with error -22[ 10.123489] i2c 7-0068: probe failed
This indicates that the `custom-sensor` driver, which expected a device with `compatible = “manufacturer,custom-sensor-v1″` at I2C address 0x68, failed to initialize. The error code `-22` (EINVAL) often points to invalid arguments or incorrect device tree properties.
Conclusion
The Device Tree is a cornerstone of modern Linux kernel development for embedded systems, particularly within the diverse Android landscape. Mastering its structure, syntax, and modification techniques is essential for anyone developing custom Android devices with unique SoCs. By precisely describing hardware configurations, the Device Tree enables powerful hardware abstraction, streamlines kernel development, and ultimately brings your custom hardware to life efficiently.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →