平台设备驱动程序
片上设备
物理存在于 SoC 内部的硬件模块,其特点是焊死在芯片内部,不可插拔,不可枚举(无discovery机制)
- UART 控制器
- I2C 控制器
- SPI 控制器
- GPIO
- Timer
平台设备
设备树
设备树解决什么问题
Device Tree 用来描述板级硬件资源,尤其适合描述 SoC 内部这类无法自动枚举的设备,例如
UART、I2C、SPI、GPIO、Timer 等。Bootloader 启动内核前把 .dtb 放到内存中,内核启动后解析
.dtb,再根据节点中的 compatible、reg、interrupts、pinctrl 等信息完成设备创建和驱动匹配。
可以把设备树理解成“硬件描述”和“驱动代码”的分离:驱动不应该把某块板子的寄存器地址、中断号、 引脚复用写死在 C 代码里,而是从设备树读取。
文件关系和编译链路
| 文件/工具 | 作用 |
|---|---|
.dts | 板级设备树源码,通常一个开发板对应一个 .dts。 |
.dtsi | 公共设备树片段,常用于 SoC、芯片系列、公共外设配置复用。 |
DTC | Device Tree Compiler,把 .dts 编译成 .dtb。 |
.dtb | 设备树二进制文件,由 Bootloader 加载并传给内核。 |
.dtsi 的用法类似 C 语言头文件,可以被 .dts 通过 #include 引入;.dtsi 本身也可以继续
include 其它 .dtsi。
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "a1.dtsi"
#include <dt-bindings/interrupt-controller/irq.h>
DTC 源码位于内核的 scripts/dtc 目录。内核选中 CONFIG_OF 后,编译内核时会生成主机侧
可执行程序 DTC。
基本结构
根节点
根节点 / 描述整块板子的顶层信息,常见属性是 model 和 compatible。
/dts-v1/;
#include "topeet-rk3588-linux.dtsi"
#include "rk3588-linux.dtsi"
#include "topeet-screen-lcds.dts"
#include "topeet-camera-config.dtsi"
/ {
model = "TOPEET RK3588 LP4 LP5 Board";
compatible = "rockchip,rk3588-evb3-lp5-v10", "rockchip,rk3588";
};
model:当前开发板名称,主要用于标识。compatible:匹配 machine 或驱动,通常按“从具体到通用”的顺序写。
子节点
设备节点的一般格式如下:
[label:] node-name[@unit-address] {
[property definitions];
[child nodes];
};
**label:**节点标签,用于
&label引用,必须全局唯一。(可选字段)- 提供简短的引用名称,避免写冗长的全路径,提高可读性
**node-name:**节点名称,表示设备类型,可以重复。(必须字段)
**unit-address:**节点地址,通常应与
reg中的首地址对应。(可选字段)- 格式规范:节点名@地址,紧跟着节点名称后,地址值支持10/16进制,字符串,需与reg属性匹配
**属性定义:**节点的核心,由一系列键值对组成,用来描述设备的具体属性(如compatible属性)

示例:
uart0: serial@0x10030000 { //是 label,方便其他 DTS 节点用 &uart0 引用
compatible = "ingenic,8250-uart";
reg = <0x10030000 0x1000>;
reg-shift = <2>;
interrupt-parent = <&core_intc>;
interrupts = <IRQ_UART0>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_pc1>;
status = "disabled";
};
特殊节点
chosen
不描述硬件,用于bootloader向内核传递运行是的参数
chosen {
bootargs = "earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.gicv3_pseudo_nmi=0 rw rootwait rcupdate.rcu_expedited=1 rcu_nocbs=all";
};
- bootargs: 传递内核启动参数
- **stdout-path:**指定内核标准输出设备
aliases
节点顾名思义就是“别名”。设备树中的硬件节点路径通常非常长且复杂,为了方便人类阅读和内核驱动快速找到设备,就给长路径起一个短别名。
aliases {
i2s0 = "/i2s@19700000";
i2s1 = "/i2s@19800000";
i2c0 = "/i2c@18d00000";
i2c1 = "/i2c@18e00000";
gpio0 = "/gpio@1B000000";
gpio1 = "/gpio@1B100000";
gpio2 = "/gpio@1B200000";
efuse0 = "/efuse@1BA00000";
};
cpus
描述板子上的中央处理器(CPU)信息,device_type = "cpu":这是每个 CPU 子节点必须包含的属性,用来明确告诉内核“我是一个处理器核心”。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a7";
reg = <0xf00>;
};
cpu@1 {
device_type = "cpu";
compatible = "arm,cortex-a7";
reg = <0xf01>;
};
};
memory
描述系统的物理内存(RAM)布局
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;/* 物理内存的起始地址是 0x80000000(2GB 处),大小是 0x20000000(512MB) */
};
标准属性
| 属性 | 作用 |
|---|---|
compatible | 最重要的匹配属性,驱动通常通过 of_match_table 与它匹配。 |
reg | 定义该设备的寄存器基地址和长度 |
status | 控制节点是否启用,常见值是 okay、disabled。 |
interrupt-parent | 指定中断所属的中断控制器。 |
interrupts | 描述中断号及触发方式,具体格式由中断控制器决定。 |
pinctrl-names | 定义 pinctrl 状态名称,例如 default、sleep。 |
pinctrl-0 | 引用具体引脚复用配置,例如把 GPIO 切换成 UART 功能。 |
compatible
内核通过该属性的值来为设备匹配对应的驱动程序,是设备树中最关键的属性之一。
- **格式:**采用"厂商,设备型号"格式(如"sony,imx415").值可以是单个字符串或字符串列表,也可以是多值。
compatible = "rockchip,rk3588-evb3-lp5-v10", "rockchip,rk3588"
reg
- **核心作用:**定义设备在父总线地址空间中的寄存器地址范围或内存映射区域,告知驱动程序硬件资源的具体位置。
- **格式:**由一个或多个对组成
寻址属性
- #address-cells
- 定义子节点reg属性中**,地址字段占用的32位单元**
- #size-cells
- 定义子节点reg属性中,大小字段占用的32位单元
例:
/ {
compatible = "rockchip,rk3399";
#address-cells = <2>;
#size-cells = <2>;
};
root节点的子节点 reg格式: address占2个cell,address占2个cell reg = <addr_hi addr_low size_hi size_low>

&csi2_dphy3 {
status = "okay";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
mipidphy3_in_ucam0: endpoint@1 {
reg = <1>;
remote-endpoint = <&ov5695_out_4>;
data-lanes = <1 2>;
};
mipidphy3_in_ucam0_ov13850: endpoint@2 {
reg = <2>;
remote-endpoint = <&ov13850_out_4>;
data-lanes = <1 2 3 4>;
};
mipidphy3_in_ucam0_imx415: endpoint@3 {
reg = <3>;
remote-endpoint = <&imx415_out_4>;
data-lanes = <1 2 3 4>;
};
};
port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
csidphy3_out: endpoint@0 {
reg = <0>;
remote-endpoint = <&mipi4_csi2_input>;
};
};
};
};
特有属性
| 属性 | 作用 |
|---|---|
| reg-shift | 描述 UART 寄存器地址步进 |
reg-shift = <2>后,驱动访问第n个寄存器时地址变成:base + (n << 2)。- 也就是:
UART_RX/TX:base + 0x00UART_IER:base + 0x04UART_IIR/FCR:base + 0x08UART_LCR:base + 0x0c
引用与板级覆盖
SoC 级 .dtsi 通常先定义硬件节点,并默认 status = "disabled"。板级 .dts 再通过 &label
引用该节点,按实际硬件连接启用或补充配置。
rtc: rtc@0x10003000 {
compatible = "ingenic,rtc";
reg = <0x10003000 0x1000>;
interrupt-parent = <&core_intc>;
interrupts = <IRQ_RTC>;
status = "disabled";
};
&rtc {
status = "okay"; /* 修改状态 */
};
&label 的含义不是重新创建节点,而是找到已经存在的节点,对它追加、修改或覆盖属性。
地址描述
设备树用 #address-cells、#size-cells 和 reg 描述子节点地址。它们通常出现在总线节点中,
例如 soc、i2c、spi、pci、simple-bus 等。
cell
cell 是设备树中地址和长度的基本单位,1 cell = 32 bit = 4 字节。在 .dts 文件里,尖括号
< > 中的每个数字通常就是一个 cell。
#address-cells:子节点地址占几个 cell。#size-cells:子节点长度占几个 cell。reg:按父节点的 cell 规则解释地址和长度。
reg
reg 的基本形式是:
reg = <address size>;
但 address 和 size 实际上可能由多个 cell 组成:
/* 每个元组代表设备使用的一段地址范围 */
reg = <address... size...>;
32 位地址常见写法:
#address-cells = <1>;
#size-cells = <1>;
uart@fe660000 {
reg = <0xfe660000 0x100>;
};
64 位地址常见写法:
#address-cells = <2>;
#size-cells = <2>;
如果子节点只需要编号,不需要长度,例如某些 ports、I2C 设备、SPI 设备,#size-cells 可以为
0。