第二章 RK平台设备树(DTS)深度解析

设备树,圈里人常叫它 DTS。说白了,它就是一块「硬件描述板」。告诉内核:你有哪些外设、挂在哪条总线上、中断号是多少、GPIO 怎么配。没有它,Linux 内核就是个瞎子。

我在 RK 平台上栽过最大的跟头,十有八九都跟 DTS 写错了有关。有一次点不亮 MIPI 屏,查了三天,最后发现是 pinctrl 里一个引脚功能选错了。嗯,从那以后我再也不敢轻视这个 .dts 文件了。

2.1 DTS 语法与结构——先看懂骨架

一个 .dts 文件,本质上是一棵单根树。根节点叫 /,下面挂各种子节点。每个节点描述一个设备或一段硬件资源。

/dts-v1/;

#include "rk3588.dtsi"

/ {
    model = "RK3588 EVB Board";
    compatible = "rockchip,rk3588-evb", "rockchip,rk3588";

    chosen {
        stdout-path = &uart2;
    };

    memory@a00000 {
        device_type = "memory";
        reg = <0x0 0xa00000 0x0 0x80000000>;
    };
};

结构其实不复杂。几个关键点我拎出来说说:

  • 根节点:/,整棵树的起点。model 和 compatible 是必填项,内核靠它匹配板级。
  • 节点名:格式是 name@address。比如 i2c@ff3f0000,address 是寄存器基址,用来区分同名节点。
  • 属性:键值对。值可以是字符串、32 位整数数组、64 位整数数组、二进制数据。
  • 标签:比如 &uart2,用来引用其他节点。我习惯把所有外设节点都打上标签,方便后面覆盖。

核心原则:.dtsi 是头文件,放 SoC 通用定义;.dts 是板级文件,放具体板子的差异。千万别把板级差异写进 .dtsi,否则换一块板子你就得改 SoC 文件,那场面……我经历过一次,很酸爽。

2.2 RK 专用绑定——rockchip,pinctrl 到底怎么用

RK 平台最绕不开的就是 rockchip,pinctrl。它负责引脚功能复用、上下拉、驱动能力。我见过太多人在这里翻车。

先看一个 I2C 节点的完整写法:

&i2c2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&i2c2m0_xfer>;

    clock-frequency = <400000>;

    gt9xx: touchscreen@14 {
        compatible = "goodix,gt9271";
        reg = <0x14>;
        interrupt-parent = <&gpio1>;
        interrupts = <RK_PB0 IRQ_TYPE_LEVEL_LOW>;
        irq-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
        reset-gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
    };
};

注意 pinctrl-0 引用的 &i2c2m0_xfer。这个定义在 .dtsi 里长这样:

&i2c2 {
    pinctrl-names = "default";
    pinctrl-0 = <&i2c2m0_xfer>;
};

&pinctrl {
    i2c2m0_xfer: i2c2m0-xfer {
        rockchip,pins =
            <2 RK_PB3 1 &pcfg_pull_none>,
            <2 RK_PB4 1 &pcfg_pull_none>;
    };
};

这里 rockchip,pins 的格式是:<bank pin func pull>。bank 是 GPIO 组,pin 是组内引脚,func 是功能号(0=GPIO, 1=功能1, 2=功能2...),pull 是上下拉配置。

字段 含义 常见值
bank GPIO 组编号 0~4(RK3588)
pin 组内引脚号 RK_PA0 ~ RK_PD7
func 功能复用选择 0=GPIO, 1~3=外设功能
pull 上下拉配置 pcfg_pull_none / pcfg_pull_up / pcfg_pull_down

我曾经踩过的坑:I2C 总线死活不通,示波器量 SCL 有波形,SDA 一直低。查了半天,发现 pinctrl 里把 SDA 配成了 pcfg_pull_down。I2C 总线要求 SDA 和 SCL 都是开漏加上拉,你拉低了,设备怎么拉得动?改成 pcfg_pull_none 就好了。

2.3 GPIO 与 I2C 节点实战——手写一个触摸屏节点

实战是最好的学习方式。我们从头写一个 GT9271 触摸屏的节点。

先看原理图:

  • I2C 接 i2c2,地址 0x14
  • 中断脚:GPIO1_B0
  • 复位脚:GPIO1_B1
  • 供电:3.3V(不需要 DTS 控制)

完整节点:

&i2c2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&i2c2m0_xfer>;
    clock-frequency = <400000>;

    gt9271: touchscreen@14 {
        compatible = "goodix,gt9271";
        reg = <0x14>;
        interrupt-parent = <&gpio1>;
        interrupts = <RK_PB0 IRQ_TYPE_LEVEL_LOW>;
        irq-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
        reset-gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
        AVDD28-supply = <&vcc3v3_sys>;
        VDDIO-supply = <&vcc3v3_sys>;
    };
};

几个容易出错的地方:

  • interrupts 和 irq-gpios 都要写,但用途不同。前者是内核中断子系统用的,后者是驱动里 gpio_to_irq() 用的。我建议两个都写上,兼容性最好。
  • AVDD28-supply 和 VDDIO-supply 是 regulator 绑定。如果供电没配,驱动 probe 时会返回 -EPROBE_DEFER,然后内核会重试。但如果你根本没定义这个 regulator,那就永远 probe 不成功。

调试技巧:如果触摸屏没反应,先看 /sys/kernel/debug/gpio 确认 GPIO 状态。再用 i2cdetect -y 2 看设备有没有在总线上。我一般三步走:先看 GPIO 电平,再看 I2C 通信,最后查中断。90% 的问题出在前两步。

2.4 DTS 编译与反编译——你总得会修砖

写好的 .dts 要编译成 .dtb,内核启动时加载。反过来,出问题时你得能从 .dtb 反编译回 .dts 看看实际生效的是什么。

编译命令:

# 编译单个 dts
dtc -I dts -O dtb -o rk3588-evb.dtb rk3588-evb.dts

# 反编译 dtb 到 dts
dtc -I dtb -O dts -o rk3588-evb.dts rk3588-evb.dtb

反编译出来的 .dts 会包含所有展开后的内容,包括 .dtsi 里 include 进来的。这是排查问题的利器。

举个例子:你怀疑 pinctrl 没生效,反编译 .dtb 看看:

i2c2: i2c@ff3f0000 {
    pinctrl-0 = <0x12345678>;
    status = "okay";
};

看到 0x12345678 这种数字了吗?那是 phandle,指向实际的 pinctrl 节点。你再往下翻,找到对应的 pinctrl 节点,看看 pins 配置对不对。

场景 命令 说明
编译 dtc -I dts -O dtb dts → dtb
反编译 dtc -I dtb -O dts dtb → dts
检查语法 dtc -I dts -O dtb -o /dev/null 只检查不输出
生成汇编 dtc -I dts -O asm 调试用

注意:反编译出来的 .dts 可读性不如原版。所有标签名会被展开成 phandle 数字,注释也没了。但它能告诉你「内核实际看到的是什么」。我习惯在改完 DTS 后,先编译再反编译,对比一下关键节点,确保没被覆盖或覆盖错了。

2.5 知识结构总览

下面这张图把本章的核心知识点串起来了。你可以把它当一张「DTS 调试地图」用。

RK平台DTS知识结构图 DTS 设备树 语法与结构 RK 专用绑定 编译与反编译 根节点 / 属性与值 标签引用 pinctrl GPIO 节点 I2C 节点 dtc 编译 反编译调试 phandle 分析 实战:GT9271 触摸屏节点 原理图分析 节点编写 编译验证

这张图从左到右,从上到下,就是 DTS 调试的完整路径。先看懂语法结构,再掌握 RK 专用绑定,然后学会编译反编译,最后用实战案例串起来。我建议你把这图存下来,以后调 DTS 问题时对着看,思路会清晰很多。