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 初始化失败。

避坑指南:RK3568 的绑定文档在 Linux 内核源码的 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 之间的关系,以及设备树如何与驱动匹配。

RK3568 设备树知识结构图 RK3568 设备树知识结构 rk3568.dtsi SoC 级描述(芯片内部) rk3568-pinctrl.dtsi 引脚复用描述 其他 .dtsi 时钟、GRF、PMU 等 rk3568-myboard.dts 板级描述(你的硬件配置) rk3568-myboard.dtb 编译后的二进制设备树 Linux 内核驱动匹配 通过 compatible 属性匹配驱动 关键点 #include 机制 status 属性

3.5 实战建议

写设备树时,我建议你遵循几个原则:

  1. 不要直接修改 .dtsi。所有板级修改都放在 .dts 里,用 &node { }; 的方式覆盖。这样方便后续升级内核。
  2. 先确认引脚复用。RK3568 的引脚功能很多,同一个引脚可能被多个外设使用。我遇到过最坑的一次,就是 UART 和 SPI 用了同一个引脚,结果两个都工作不正常。
  3. 编译后反查。用 dtc -I dtb -O dts 反编译生成的 .dtb,确认你的修改是否生效。有时候语法错误不会报错,但节点根本没被编译进去。
一个小技巧:在 arch/arm64/boot/dts/rockchip/Makefile 里加上你的 .dts 文件名,然后 make dtbs 就能编译。我习惯在编译后,用 strings 命令查看 .dtb 里是否包含我的板子名字——这招虽然土,但很管用。