3. RK3568 设备树基础:语法、绑定与文件结构
设备树,说白了就是一块“硬件描述板”。
它告诉内核:你有哪些外设、它们挂在哪条总线上、寄存器地址是多少、中断号是多少。没有设备树,Linux 内核根本不知道你的 RK3568 板子上接了什么东西。
我刚开始接触设备树时,也觉得它不过是一堆 .dts 文件。直到有一次,我因为一个 status = "disabled" 没改过来,整整查了两天为什么 SPI 驱动不工作……嗯,从那以后我再也不敢小看设备树了。
3.1 设备树语法速览
设备树的语法其实不复杂。核心就几个概念:节点(node)、属性(property)、值(value)。
/dts-v1/;
/ {
compatible = "rockchip,rk3568-evb";
model = "Rockchip RK3568 EVB Board";
chosen {
stdout-path = &uart2;
};
memory@a00000 {
device_type = "memory";
reg = <0x0 0x00200000 0x0 0x80000000>;
};
uart2: serial@fe660000 {
compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
reg = <0x0 0xfe660000 0x0 0x100>;
interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru SCLK_UART2>, <&cru PCLK_UART2>;
clock-names = "baudclk", "apb_pclk";
status = "disabled";
};
};
看到没?每个节点都有一个 compatible 属性,这是驱动匹配的关键。内核通过它找到对应的驱动程序。
compatible 字符串是否与驱动里的 of_match_table 完全一致。大小写、逗号、空格,一个都不能错。我曾经因为多打了一个空格,浪费了整整一个下午。
3.2 RK3568 专用绑定
RK3568 的绑定,说白了就是 Rockchip 自己定义的一套“方言”。
你想想看,通用设备树语法只能描述基本的外设。但 RK3568 有自己的时钟控制器、电源管理单元、GRF(通用寄存器文件)……这些都需要专用的绑定来描述。
| 绑定名称 | 描述 | 典型 compatible |
|---|---|---|
| CRU | 时钟与复位单元 | rockchip,rk3568-cru |
| GRF | 通用寄存器文件 | rockchip,rk3568-grf |
| PMU | 电源管理单元 | rockchip,rk3568-pmu |
| Pinctrl | 引脚复用与配置 | rockchip,rk3568-pinctrl |
| IOMUX | IO 复用控制器 | rockchip,rk3568-iomux |
举个例子,GRF 的绑定长这样:
grf: syscon@fdc20000 {
compatible = "rockchip,rk3568-grf", "syscon", "simple-mfd";
reg = <0x0 0xfdc20000 0x0 0x10000>;
};
注意这里的 syscon 和 simple-mfd。它们告诉内核:这个节点不仅是一个寄存器块,还可能包含多个子功能。我在项目中就遇到过,有些驱动需要直接操作 GRF 里的某个位,但没加 syscon 导致 regmap 初始化失败。
Documentation/devicetree/bindings/ 目录下。我建议你每次移植新外设时,先去那里翻一翻。Rockchip 的绑定更新很快,网上搜到的可能已经过时了。
3.3 dts/dtsi 文件结构解析
RK3568 的设备树文件,通常分成两层:
- .dtsi:芯片级描述。包含所有 SoC 内部外设、时钟、中断控制器等。这部分由 Rockchip 提供,一般不需要修改。
- .dts:板级描述。包含具体板子上的外设、GPIO 配置、引脚复用等。这部分需要你根据硬件原理图来写。
典型的文件结构:
arch/arm64/boot/dts/rockchip/
├── rk3568.dtsi # SoC 级描述
├── rk3568-pinctrl.dtsi # 引脚复用描述
├── rk3568-evb.dts # EVB 板级描述
└── rk3568-myboard.dts # 你自己的板子
rk3568.dtsi 里定义了所有外设节点,但大部分 status = "disabled"。你需要在自己的 .dts 里通过 &uart2 { status = "okay"; }; 这样的方式,把需要用到的外设“激活”。
#include 机制和 C 语言很像。你可以用 #include "rk3568.dtsi" 引入 SoC 级描述,然后在后面覆盖或添加自己的节点。但注意,/dts-v1/; 必须放在最开头。
3.4 知识结构图
下面这张图,是我自己整理 RK3568 设备树时画的。它帮你理清 dts/dtsi 之间的关系,以及设备树如何与驱动匹配。
3.5 实战建议
写设备树时,我建议你遵循几个原则:
- 不要直接修改 .dtsi。所有板级修改都放在 .dts 里,用
&node { };的方式覆盖。这样方便后续升级内核。 - 先确认引脚复用。RK3568 的引脚功能很多,同一个引脚可能被多个外设使用。我遇到过最坑的一次,就是 UART 和 SPI 用了同一个引脚,结果两个都工作不正常。
- 编译后反查。用
dtc -I dtb -O dts反编译生成的 .dtb,确认你的修改是否生效。有时候语法错误不会报错,但节点根本没被编译进去。
arch/arm64/boot/dts/rockchip/Makefile 里加上你的 .dts 文件名,然后 make dtbs 就能编译。我习惯在编译后,用 strings 命令查看 .dtb 里是否包含我的板子名字——这招虽然土,但很管用。