设备树详解与实践
本文档介绍 RK3588 Linux 平台下的设备树(Device Tree)开发,包括 DTS 语法、RK3588 设备树结构、GPIO/I2C/SPI 节点配置、设备树编译与 Overlay 机制。
适用范围: 在 PC 上编辑 .dts 文件后,在 RK3588 SDK 中交叉编译生成 DTB,最终烧录到 C-3588LQ V1.0 + SHIMETAPI-M5-R2A V1.0 底板。
1. 概念基础
1.1 设备树的作用
设备树(Device Tree)是一种描述硬件资源的数据结构,将硬件配置信息从 Linux 内核中解耦出来。RK3588 用设备树描述:
- SoC 内部资源(GPIO、I2C、SPI、UART 等)
- 板级外设的连接关系
- 设备基地址、中断、时钟、Pinmux 等
编译流程:.dts 源文件 → dtc 编译 → .dtb(Device Tree Blob)→ U-Boot 加载到内存 → 内核启动时解析。
1.2 RK3588 与 RK3568 的关键差异
| 差异点 | RK3588 | RK3568 |
|---|---|---|
| CPU | 8 核(4×A76+4×A55) | 4 核(4×A55) |
| NPU | 内置 6 TOPS | 无内置 NPU |
| 显示控制器 | VOP2 / VOP3 | VOP |
| 设备树 rknpu 节点 | rknpu@fde40000 | 无 |
2. 准备工作
2.1 硬件 / 软件清单
| 项目 | 内容 |
|---|---|
| 主控板 | C-3588LQ 核心板 + SHIMETAPI-M5-R2A 底板 |
| 开发电脑 | Ubuntu 20.04/22.04 LTS(64 位),已安装 RK3588 Linux SDK |
| RK3588 Linux SDK | 已按 ch01-Linux_SDK编译 配置完成 |
| 设备树工具链 | dtc / fdtget / fdtput(详见 §3) |
2.2 设备树工具链
| 工具 | 用途 | 安装命令 |
|---|---|---|
dtc | DTS 编译 / DTB 反编译 | sudo apt-get install device-tree-compiler |
dtc -I dtb -O dts | DTB → DTS(反编译) | 已包含在 dtc 中 |
dtc -I dts -O dtb | DTS → DTB(编译) | 已包含在 dtc 中 |
fdtget | 查看 DTB 属性 | sudo apt-get install libfdt-dev(含 fdtget/fdtput) |
fdtput | 修改 DTB 属性 | 同上 |
在开发板(已烧录的 RK3588 系统)上执行以下命令验证工具是否可用:
dtc --version # 设备端默认未安装,需自行 apt 安装出厂镜像不自带 dtc。如需在设备端反编译 DTB,先
apt install -y device-tree-compiler。
2.3 内核配置
开启设备树支持:
| 配置项 | 说明 | 建议 |
|---|---|---|
CONFIG_OF=y | 设备树基础支持 | 必选 |
CONFIG_OF_FLAT_TREE | 扁平化设备树 | 必选 |
CONFIG_OF_EARLY_FLATTREE | 早期设备树 | 必选 |
CONFIG_OF_DYNAMIC | 动态设备树(Overlay 基础) | 必选 |
CONFIG_OF_OVERLAY | Overlay 覆盖 | 推荐 |
CONFIG_OF_CONFIGFS | configfs 接口挂载 Overlay | 推荐 |
3. DTS 语法基础
3.1 基本结构
/dts-v1/;
/ {
model = "shimetapi RK3588 Board";
compatible = "rockchip,rk3588";
chosen {
stdout-path = &uart2;
bootargs = "earlycon=uart8250,mmio32,0xfe660000 console=ttyFIQ0";
};
memory {
device_type = "memory";
reg = <0x0 0x0 0x0 0x80000000>; /* 2GB RAM */
};
};3.2 常用属性
| 属性 | 说明 | 示例 |
|---|---|---|
compatible | 设备兼容性标识 | "rockchip,rk3588-uart" |
reg | 寄存器地址/大小 | <0x0 0xfe660000 0x0 0x100> |
interrupts | 中断号 | <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH> |
clocks | 时钟引用 | <&cru PCLK_UART2> |
pinctrl | Pinmux 配置 | pinctrl-0 = <&uart2m0_xfer> |
status | 设备状态 | "okay" 或 "disabled" |
#address-cells | 地址字段单元格数 | <2>(64 位地址) |
#size-cells | 大小字段单元格数 | <2>(64 位大小) |
3.3 标签与引用
// 定义标签(label)
uart2: serial@fe660000 {
compatible = "rockchip,rk3588-uart";
reg = <0x0 0xfe660000 0x0 0x100>;
interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru SCLK_UART2>, <&cru PCLK_UART2>;
clock-names = "baudclk", "apb_pclk";
pinctrl-names = "default";
pinctrl-0 = <&uart2m0_xfer>;
status = "disabled";
};
// 使用 & 引用标签修改节点
&uart2 {
status = "okay";
};4. RK3588 设备树结构
4.1 文件层次
RK3588 设备树采用分层引用结构:
rk3588.dtsi # SoC 级定义(所有 IP 模块、时钟、中断)
├── rk3588-pinctrl.dtsi # Pinmux 定义(所有 IOMUX 配置)
├── rk3588-power.dtsi # 电源域定义
└── rk3588-vop.dtsi # VOP 显示控制器定义
rk3588-evb.dtsi # EVB 板级定义(PMIC、DDR、外设)
├── rk3588-evb1-ddr4-v10.dts # 最终板级 DTS 文件
└── rk3588-evb1-ddr4-v10-linux.dts # Linux 专用配置电脑端 列出 SDK 中的设备树源文件:
ls kernel/arch/arm64/boot/dts/rockchip/rk3588*.dts
ls kernel/arch/arm64/boot/dts/rockchip/rk3588*.dtsi4.2 SoC 级 dtsi 结构(rk3588.dtsi 示例)
#include <dt-bindings/...>
/ {
compatible = "rockchip,rk3588";
// CPU 核心(8 核:4×A76 + 4×A55)
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a76";
reg = <0x0 0x0>;
enable-method = "psci";
clocks = <&scmi_clk 0>;
operating-points-v2 = <&cpu0_opp_table>;
};
// cpu1~cpu3 (A76) 和 cpu4~cpu7 (A55) 类似...
};
// 内存控制器
dmc: dmc {
compatible = "rockchip,rk3588-dmc";
};
soc: soc@fda00000 {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <2>;
ranges;
// 中断控制器(GICv3)
gic: interrupt-controller@fd400000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x0 0xfd400000 0x0 0x10000>;
};
// 时钟控制器
cru: clock-controller@fdd20000 {
compatible = "rockchip,rk3588-cru";
reg = <0x0 0xfdd20000 0x0 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
};
// NPU 节点(RK3588 特有,6 TOPS)
rknpu: rknpu@fde40000 {
compatible = "rockchip,rknpu";
reg = <0x0 0xfde40000 0x0 0x10000>;
};
};
};4.3 设备树路径速查
设备端可读取当前生效的设备树:
cat /proc/device-tree/model输出:
Rockchip RK3588 EVB10 V10 Boardcat /proc/device-tree/compatible | tr '\0' '\n'输出(节选):
rockchip,rk3588-evb10-v10
rockchip,rk3588ls /proc/device-tree/ | head -20输出(节选):
adc-keys
#address-cells
aliases
arm-pmu
av1d@fdc70000
avsd-plus@fdb51000
backlight
bt-sco
bt-sound
can@fea50000
compatible字段以 null 字符分隔多个值。/proc/device-tree/包含所有运行时设备树节点的目录视图。
5. GPIO 节点配置
5.1 GPIO 控制器节点
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO0>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};5.2 GPIO 使用示例
my_device {
compatible = "my,device";
// GPIO4_C0 (GPIO4 组, pin 16), 低电平有效
enable-gpios = <&gpio4 16 GPIO_ACTIVE_LOW>;
// GPIO4_C1, 高电平有效
reset-gpios = <&gpio4 17 GPIO_ACTIVE_HIGH>;
};RK3588 GPIO 编号规则:每组 GPIO 有 32 个 pin,编号为 0-31。GPIO4_C0 表示 GPIO4 的第 C 组第 0 个 pin,即 4 × 32 + 2 × 8 + 0 = 144(换算公式:GPIO 组号 × 32 + 子组号 × 8 + 引脚号,子组 A/B/C 分别对应 0/1/2)。
5.3 Pinmux 配置(pinctrl)
pinctrl: pinctrl {
compatible = "rockchip,rk3588-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
// 功能组定义
uart2 {
uart2m0_xfer: uart2m0-xfer {
rockchip,pins =
<0 RK_PA14 2 &pcfg_pull_up>, // GPIO0_A14, func 2
<0 RK_PA15 2 &pcfg_pull_up>; // GPIO0_A15, func 2
};
};
};6. I2C / SPI 节点配置
6.1 I2C 控制器 + 从设备
// 控制器(默认 disabled,需在板级 .dts 中 enable)
i2c1: i2c@fe5a0000 {
compatible = "rockchip,rk3588-i2c", "rockchip,rk3399-i2c";
reg = <0x0 0xfe5a0000 0x0 0x1000>;
clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c1m0_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
// 在板级 enable 并挂载 I2C 温度传感器
&i2c1 {
status = "okay";
clock-frequency = <400000>; // 400kHz (Fast Mode)
temp_sensor: temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio3>;
interrupts = <RK_PB1 IRQ_TYPE_LEVEL_LOW>;
};
};6.2 SPI 控制器 + SPI Flash
spi1: spi@fe610000 {
compatible = "rockchip,rk3588-spi", "rockchip,rk3066-spi";
reg = <0x0 0xfe610000 0x0 0x1000>;
clocks = <&cru CLK_SPI1>, <&cru PCLK_SPI1>;
clock-names = "spiclk", "apb_pclk";
interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&spi1m1_cs0 &spi1m1_pins>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
&spi1 {
status = "okay";
max-freq = <50000000>; // 50MHz
spi_flash: flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <50000000>;
m25p,fast-read;
};
};7. 设备树编译与 Overlay
7.1 DTS → DTB(编译)
电脑端 三种方式:
# 方式一:内核编译系统(推荐)
cd <SDK_PATH>/kernel
make ARCH=arm64 rockchip_linux_defconfig
make ARCH=arm64 dtbs # 编译所有 DTB
make ARCH=arm64 rk3588-xxx.dtb # 编译单个
# 方式二:dtc 直接编译
dtc -I dts -O dtb -o output.dtb input.dts
# 方式三:编译 Overlay
dtc -I dts -O dtb -@ -o my-overlay.dtbo my-overlay.dts
-@标志表示启用符号自动处理,是 Overlay 编译必需项。
7.2 DTB → DTS(反编译)
电脑端:
# 反编译为可读 DTS
dtc -I dtb -O dts -o output.dts input.dtb
# 反编译并保留符号
dtc -I dtb -O dts -@ -o output.dts input.dtb
# 查看 DTB(不反编译,仅查看)
fdtdump input.dtb7.3 RK3588 SDK 编译流程
.dts源文件 → C 预处理器处理(解析#include)dtc编译为.dtbscripts/mkimg --dtb工具打包为dtb.img(即 resource.img)dtb.img与kernel.img一起打包进boot.img
7.4 Overlay(运行时设备树覆盖)
Overlay(设备树覆盖片段)允许在系统运行时动态加载设备树片段,不用重新编译整个设备树,常用于热插拔外设支持。
编写示例:
// my-overlay.dts
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&i2c1>;
__overlay__ {
status = "okay";
clock-frequency = <100000>;
my_sensor: my-sensor@48 {
compatible = "my,company-sensor";
reg = <0x48>;
};
};
};
};加载(设备端 / configfs):
mkdir /sys/kernel/config/device-tree/overlays/my-overlay
cat my-overlay.dtbo > /sys/kernel/config/device-tree/overlays/my-overlay/dtboU-Boot 加载方式(电脑端 / U-Boot CLI):
load mmc 1:1 ${fdt_addr_r} /overlays/my-overlay.dtbo
fdt apply ${fdt_addr_r}查看与卸载(设备端):
ls /sys/kernel/config/device-tree/overlays/
rmdir /sys/kernel/config/device-tree/overlays/my-overlay8. SPL 设备树
RK3588 的 SPL(Secondary Program Loader)也使用设备树,用于 DDR 初始化、存储设备识别等早期启动阶段。
SPL 设备树有特殊标记 u-boot,dm-spl 和 u-boot,dm-pre-reloc,只有带这些标记的节点才会被编译进 SPL 的 DTB:
&uart2 {
u-boot,dm-pre-reloc; // 在 SPL 阶段初始化
status = "okay";
};
&sdmmc0 {
u-boot,dm-spl; // 在 SPL 阶段使用
status = "okay";
};9. 内核设备树 API 示例
驱动中读取设备树属性的典型模式:
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct gpio_desc *reset_gpio;
u32 val;
int ret;
// 读取整型属性
ret = of_property_read_u32(np, "my,property", &val);
if (ret) {
dev_err(dev, "failed to read my,property\n");
return ret;
}
// 读取 GPIO
reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(reset_gpio))
return PTR_ERR(reset_gpio);
// 读取中断
int irq = irq_of_parse_and_map(np, 0);
return 0;
}内核启动时调用链:
start_kernel()→setup_arch()→setup_machine_fdt()→unflatten_device_tree()。
10. 故障排除
10.1 DTB 与内核版本不匹配
现象: 内核启动提示 FDT: __unflatten_device_tree() failed 或设备无响应。
解决方案:
电脑端:
cd <SDK_PATH>/kernel
make ARCH=arm64 scripts
scripts/dtc/dtc --version # 确认 dtc 版本10.2 设备树节点未生效
现象: 修改设备树后设备无反应或驱动未加载。
解决方案:
电脑端:反编译烧录的 DTB 验证:
dtc -I dtb -O dts -o /tmp/check.dts boot.img.dtb
grep -A5 "my_device" /tmp/check.dts确认实际生效节点包含修改。
10.3 Pinmux 冲突
现象: 多个外设配置了相同的 GPIO 引脚。
解决方案:
- 检查硬件原理图,确认引脚分配不冲突
- 在设备树中确保每个 GPIO 引脚只被一个外设使用
- 用
pinctrl-names区分不同状态下的 pinmux(如 default / sleep / idle)
相关文档
- 01_Linux_SDK编译 — SDK 编译流程
- 02_内核配置与裁剪 — 内核配置
- 05_hardware-reference/00_硬件事实基线 — 硬件事实清单
