第3章 设备树基础:DTS语法详解与RK平台层级结构
设备树(Device Tree)这东西,说白了就是一块「硬件描述板」。Linux内核通过它来知道你的板子上接了哪些外设、用的哪个引脚、电压多少。我刚开始接触RK平台时,觉得DTS就是个配置文件,后来踩了几次坑才明白——它其实是驱动和硬件之间的桥梁,写错了,内核根本认不出你的设备。
3.1 DTS语法速览
DTS的语法并不复杂,核心就几个概念:节点、属性、引用。我们直接看一个例子:
/dts-v1/;
/ {
model = "Rockchip RK3568 EVB";
compatible = "rockchip,rk3568-evb", "rockchip,rk3568";
chosen {
stdout-path = &uart2;
};
memory {
device_type = "memory";
reg = <0x0 0x00200000 0x0 0x80000000>;
};
};
这里有几个要点:
- 根节点:
/表示设备树的根,所有设备都挂在这个下面 - 属性:
compatible是匹配驱动的关键字符串,内核通过它找到对应的驱动 - reg:描述地址和大小,格式是
<地址 长度> - &uart2:这是引用语法,指向其他地方定义的节点
我的习惯:写DTS时,我总会在每个节点上加一个
status = "okay" 或 "disabled"。这样调试时一眼就能看出哪些设备被激活了,不用去翻原理图。
3.2 RK平台设备树层级结构
RK平台的设备树是分层的,一共三层。我画了张图,你看完就明白了:
这三层的关系,我总结一下:
| 层级 | 文件名 | 内容范围 | 修改频率 |
|---|---|---|---|
| 芯片级 | soc.dtsi | CPU、GIC、定时器、DMA等 | 几乎不动 |
| 系列级 | rk3568.dtsi | 外设控制器、pinctrl、时钟 | SDK升级时更新 |
| 板级 | board.dts | GPIO分配、外设使能、电源 | 经常改 |
我曾经踩过的坑:有次我在 board.dts 里改了一个 GPIO 的 pinctrl 配置,但死活不生效。查了半天才发现,rkxxx.dtsi 里已经定义了这个引脚的功能,而且
status = "okay" 写在系列级文件里。board.dts 的覆盖没生效,因为编译顺序是反的。记住:后编译的覆盖先编译的,board.dts 最后编译,所以它的优先级最高。
3.3 pinctrl与GPIO配置
pinctrl 是RK平台里最容易出问题的地方。它的作用就是告诉芯片:这个引脚现在是 GPIO 还是 I2C 的 SCL?上拉还是下拉?驱动能力多大?
看一个典型的 pinctrl 配置:
&pinctrl {
i2c1 {
i2c1_xfer: i2c1-xfer {
rockchip,pins =
<2 RK_PB0 1 &pcfg_pull_up>,
<2 RK_PB1 1 &pcfg_pull_up>;
};
};
lcd_rst {
lcd_rst_gpio: lcd-rst-gpio {
rockchip,pins =
<3 RK_PC5 0 &pcfg_pull_none>;
};
};
};
这里 rockchip,pins 的格式是:
- bank号:GPIO组,如 2 表示 GPIO2
- 引脚号:RK_PB0 表示 Bank B 的第 0 脚
- 功能号:0 表示 GPIO,1 表示 I2C,2 表示 SPI 等
- 配置:上拉、下拉、悬空等
关键点:功能号 0 永远是 GPIO 模式。如果你想让一个引脚做普通 GPIO,功能号写 0 就行。我见过有人把功能号写成 1,结果 GPIO 死活拉不高的案例。
3.4 在设备树中引用GPIO
外设驱动里怎么用这些引脚?通过 gpios 属性:
&i2c1 {
status = "okay";
clock-frequency = <400000>;
touch@38 {
compatible = "edt,edt-ft5406";
reg = <0x38>;
interrupt-parent = <&gpio3>;
interrupts = <RK_PC5 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_LOW>;
};
};
注意这里的写法:
interrupt-parent指定中断控制器是哪个 GPIO bankinterrupts里写引脚号和触发方式reset-gpios是自定义属性,驱动里通过of_get_named_gpio来获取
我个人的调试技巧:当 GPIO 不工作时,先别急着改驱动。在
/sys/kernel/debug/gpio 里看一下引脚状态,确认内核确实把它设成了输出/输入。如果 debugfs 里显示的状态和预期不符,99% 是 pinctrl 配置错了。
3.5 常见编译与调试命令
最后,给你几个我常用的命令,省得每次去翻文档:
| 操作 | 命令 | 说明 |
|---|---|---|
| 编译设备树 | make dtbs |
编译所有设备树 |
| 编译单个 | make rk3568-evb.dtb |
只编译目标板 |
| 反编译 | dtc -I dtb -O dts -o tmp.dts rk3568-evb.dtb |
查看最终生成的设备树 |
| 检查语法 | dtc -I dts -O dtb -o /dev/null board.dts |
只检查不输出 |
注意:反编译出来的 dts 和源文件可能不完全一样,因为编译过程中有些属性会被展开或合并。但用来排查「某个属性到底有没有生效」非常有用。我每次改完设备树,都会反编译确认一下最终结果。