设备树(Device Tree)

一、为什么需要设备树

二、DTS 语法

1. 文件类型

2. 基本结构

/dts-v1/;
/include/ "imx6ull.dtsi"        // 老式 include
// 或
#include "imx6ull.dtsi"           // C 风格(5.x 起支持)

/ {
    model = "MyBoard";
    compatible = "myvendor,myboard", "fsl,imx6ull";

    chosen {
        stdout-path = &uart1;
    };

    aliases {
        ethernet0 = &fec1;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;   // 512MB
    };

    leds {
        compatible = "gpio-leds";
        led-status {
            label = "status";
            gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
            default-state = "off";
        };
    };

    uart1: serial@40010000 {
        compatible = "fsl,imx6ul-uart", "fsl,imx21-uart";
        reg = <0x40010000 0x4000>;
        interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&clks IMX6UL_CLK_UART1_IPG>,
                 <&clks IMX6UL_CLK_UART1_SERIAL>;
        clock-names = "ipg", "per";
        status = "okay";
    };
};

3. 节点和属性

node@addr {
    compatible = "...";
    reg = <...>;
    #address-cells = <1>;
    #size-cells = <1>;
    status = "okay";        // 或 "disabled"
    phandle = <1>;          // 引用
};

引用方式:
- <&node>:引用 node
- <&node arg1 arg2>:带参数
- <&{path}>:用 full path

三、常用 binding 关键属性

1. 标准属性

compatible       // 匹配驱动的关键
reg              // 寄存器基址 + 大小
interrupts       // 中断号
clocks           // 时钟引用
clock-names
resets
reset-names
pinctrl-0        // 引脚控制
pinctrl-names
power-domains
dma-names
#address-cells
#size-cells
status           // "okay" / "disabled"

2. GPIO

gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;        // 通用
interrupt-gpios = <&gpio2 3 IRQ_TYPE_LEVEL_LOW>;

3. 引脚控制

pinctrl-0 = <&pinctrl_uart1>;

四、内核 API

1. 获取节点

struct device_node *np = of_find_node_by_path("/uart1");
struct device_node *np = of_find_node_by_name(root, "uart1");
struct device_node *np = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-uart");
struct device_node *np = of_get_parent(np);
struct device_node *child;
for_each_child_of_node(np, child) { /* ... */ }

2. 属性读

u32 val;
of_property_read_u32(np, "clock-frequency", &val);
of_property_read_string(np, "label", &str);
of_property_read_u32_array(np, "reg", arr, ARRAY_SIZE(arr));
of_property_read_u64(np, "clock-frequency", &val64);

3. 找子节点(带 compatible)

struct device_node *child;
for_each_compatible_node(child, NULL, "myvendor,mysensor") {
    /* 处理每个匹配节点 */
}

4. 与 platform 驱动集成

static const struct of_device_id my_of_match[] = {
    { .compatible = "myvendor,mydevice" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name           = "mydevice",
        .of_match_table = my_of_match,
    },
};

probe 中:

static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;

    u32 val;
    of_property_read_u32(np, "myprop", &val);

    // 用 devm_ API 自动清理
    void __iomem *base = devm_platform_ioremap_resource(pdev, 0, NULL);
    int irq = platform_get_irq(pdev, 0);
    return 0;
}

五、编译

# 在内核顶层
make dtbs
make imx6ull-myboard.dtb

arch/arm/boot/dts/<soc>.dtb

六、加载

# U-Boot
fatload mmc 0:1 ${fdt_addr_r} myboard.dtb
fdt addr ${fdt_addr_r}
fdt move ${fdt_addr_r} ${fdt_addr}
bootz ${kernel_addr_r} - ${fdt_addr}

# Linux
mount -t configfs none /sys/kernel/config
mkdir /sys/kernel/config/device-tree/overlays/myspi
cat myspi.dtbo > /sys/kernel/config/device-tree/overlays/myspi/dtbo
echo 1 > /sys/kernel/config/device-tree/overlays/myspi/status

七、debugfs + device tree

mount -t debugfs none /sys/kernel/debug

ls /sys/kernel/debug/device-tree/
cat /sys/kernel/debug/device-tree/uart1/compatible
cat /sys/kernel/debug/device-tree/uart1/reg

八、常用工具

dtc -I dts -O dtb -o myboard.dtb myboard.dts
dtc -I dtb -O dts -o myboard.dts myboard.dtb
fdtdump myboard.dtb
fdtget myboard.dtb /uart1 compatible