4、Rockchip设备树架构:rk3588.dtsi、rk3588-evb.dts等文件结构、include与覆盖机制
设备树这东西,说白了就是一块「硬件描述画板」。Rockchip 的玩法,是把公用的、芯片级的东西放在 .dtsi 里,把板级差异扔进 .dts。我刚开始接触 RK3588 时,面对那一堆 #include 和 & 符号,说实话有点懵。后来拆了几个官方 evb 的 dts,才摸清套路。
今天我们就来拆解一下:rk3588.dtsi 里到底藏了什么?rk3588-evb.dts 又是怎么「叠加上去」的?
4.1 文件结构:从芯片到板子
Rockchip 的设备树文件,遵循一套清晰的层级。我习惯把它想象成「俄罗斯套娃」——最里面是芯片本身,外面一层层套上 soc、板级、产品级。
| 文件 | 层级 | 描述 |
|---|---|---|
rk3588.dtsi |
芯片级 | RK3588 所有内置 IP 的默认配置,比如 CPU 核心、GPU、NPU、DDR 控制器、中断控制器等。这是最底层的「硬件清单」。 |
rk3588-evb.dtsi |
板级公共 | EVB 开发板的公共配置,比如 eMMC、SD 卡、以太网、USB 等外设的引脚复用和电源。 |
rk3588-evb1-lp4.dts |
具体产品 | 最终编译的设备树源文件,通过 #include 把上面两个文件拉进来,再覆盖或新增自己特有的配置。 |
嗯,这里要注意:.dtsi 是 不能单独编译 的,它必须被 .dts 包含。你可以把 .dtsi 理解成 C 语言里的头文件,只是它描述的是硬件。
4.2 include 机制:不只是简单的文本粘贴
设备树的 #include 和 C 语言的 #include 很像,但有个关键区别——它支持 覆盖(overlay)。我在项目中遇到过好几次,明明在 .dtsi 里配好了某个外设,结果在 .dts 里重新赋值后,之前的配置就全丢了。
为什么会这样?因为设备树的解析规则是:后出现的属性覆盖先出现的。如果你在 .dtsi 里写了:
&uart2 {
status = "disabled";
pinctrl-0 = &uart2m0_xfer;
};
然后在 .dts 里写:
&uart2 {
status = "okay";
};
最终 uart2 的 status 是 "okay",但 pinctrl-0 呢?没了。因为你在 .dts 里重新引用了 &uart2 节点,相当于重新打开了一个作用域,之前的子属性不会自动继承。
.dtsi 里配好了 SPI 的引脚和时钟,结果在 .dts 里只改了个 status,其他配置全丢了。正确的做法是:要么在 .dts 里把需要的属性全部重写一遍,要么用 /delete-property/ 先删掉再重新赋值。
4.3 覆盖机制:& 符号的妙用
设备树的覆盖,核心就是 & 符号。它用来引用一个已经存在的节点,然后往里面添加或修改属性。我个人的习惯是:能用 & 就别重新定义整个节点,这样代码更清晰,也更容易维护。
举个例子,在 rk3588.dtsi 里,i2c2 节点可能是这样的:
i2c2: i2c@feac0000 {
compatible = "rockchip,rk3588-i2c";
reg = <0x0 0xfeac0000 0x0 0x1000>;
interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru CLK_I2C2>, <&cru PCLK_I2C2>;
clock-names = "i2c", "pclk";
pinctrl-0 = <&i2c2m0_xfer>;
status = "disabled";
};
然后在 rk3588-evb.dtsi 里,你想挂一个触摸屏:
&i2c2 {
status = "okay";
clock-frequency = <400000>;
touch@38 {
compatible = "goodix,gt911";
reg = <0x38>;
interrupt-parent = <&gpio1>;
interrupts = <RK_PB5 IRQ_TYPE_EDGE_FALLING>;
};
};
你看,&i2c2 引用了之前定义的节点,然后添加了 clock-frequency 和一个子节点 touch@38。原来的 compatible、reg、interrupts 这些属性,都还在,不会被覆盖。
.dtsi 里把所有外设的 status 都设为 "disabled",然后在 .dts 里按需打开。这样既安全又灵活,不会因为忘记关某个外设导致引脚冲突。
4.4 核心结构图:设备树文件关系
下面这张图,是我自己画的一个文件关系图。你看一眼,就能明白 .dtsi 和 .dts 是怎么「拼」在一起的。
4.5 实战:如何正确覆盖一个节点
我记得有一次,客户要求在 RK3588 上同时启用两个 SPI 控制器,但其中一个要改引脚。我当时的做法是:
- 在
rk3588.dtsi里,两个 SPI 的status都是"disabled"。 - 在
rk3588-evb.dtsi里,用&spi0和&spi1分别打开,并配置各自的引脚。 - 在最终的产品
.dts里,只覆盖需要改动的属性,比如max-freq或cs-gpios。
这样做的最大好处是:改动点集中,排查问题快。如果哪天 SPI0 不工作了,我只需要看 rk3588-evb.dtsi 里 &spi0 那块代码,不用去翻几千行的 rk3588.dtsi。
.dtsi 里定义完整的基础配置,在 .dts 里只做最小化的覆盖。这样既保持了芯片级配置的通用性,又保证了板级配置的灵活性。
4.6 常见陷阱与避坑
- 陷阱一: 在
.dts里重新定义了整个节点,导致.dtsi里的所有子属性丢失。我曾经因为这个,把 I2C 的时钟频率配错了,调试了两天。 - 陷阱二: 多个
.dtsi文件同时覆盖同一个节点,后 include 的覆盖先 include 的。我建议用/include/时注意顺序。 - 陷阱三: 忘记在
.dts里#include必要的.dtsi,编译直接报错。嗯,这个低级错误我犯过不止一次。
好了,关于 Rockchip 设备树的文件结构和覆盖机制,就聊到这里。你只要记住:.dtsi 是芯片的「底牌」,.dts 是板子的「定制」,& 符号是你修改的「手术刀」。下次看到那些层层嵌套的 #include,你就知道它们是怎么拼起来的了。