4. 设备树绑定文档:如何阅读内核 Documentation/devicetree/bindings/、常见绑定规范(GPIO、时钟、中断)
设备树绑定文档是内核官方提供的“硬件描述字典”,它规定了每个 IP 或外设的节点应该包含哪些属性、属性值的格式以及子节点的结构。本章将教你如何高效阅读 Documentation/devicetree/bindings/ 目录下的文档,并深入解析 GPIO、时钟、中断这三种最核心的绑定规范。
gpios、clocks、interrupts 的 cell 含义。
4.1 绑定文档的通用结构与阅读方法
内核绑定文档通常采用 YAML 格式(.yaml)或纯文本格式(.txt)。YAML 格式是当前主流,结构更清晰。一个标准的绑定文档包含以下核心部分:
| 章节 | 内容说明 | 阅读重点 |
|---|---|---|
| Title / Description | 描述该绑定对应的硬件模块名称和功能 | 确认是否是你需要的外设 |
| Maintainers | 该绑定的维护者列表 | 遇到问题时可联系或查看 git log |
| Properties | 节点属性的详细定义,包括必选(required)和可选(optional) | 重点关注 compatible 字符串和 reg 属性 |
| Pattern Properties | 子节点名称的模式匹配规则 | 用于定义子节点命名规范 |
| Required / Optional | 明确列出哪些属性是必须提供的 | 遗漏必选属性会导致驱动 probe 失败 |
| Examples | 一个完整的设备树节点示例 | 直接复制示例并修改是最快的移植方式 |
阅读绑定文档的推荐步骤:
- 定位文件:在
Documentation/devicetree/bindings/下按厂商或 IP 类型查找,例如gpio/、clock/、interrupt-controller/。 - 检查 compatible:找到文档中的
compatible属性定义,确认字符串是否与驱动代码匹配。 - 核对 reg:确认地址和大小(#address-cells, #size-cells)的格式。
- 检查中断/时钟/GPIO 引用:确认父控制器的 phandle 和 cell 数量。
- 参考示例:将示例节点复制到你的 dts 文件中,再根据实际硬件修改。
grep -r "your-compatible" Documentation/devicetree/bindings/ 可以快速找到对应的绑定文档。YAML 文档的可读性更高,推荐优先阅读 .yaml 文件。
4.2 GPIO 绑定规范
GPIO(通用输入输出)是嵌入式系统中最常用的外设接口。设备树中通过 gpios 或 gpio 属性来描述 GPIO 的连接关系。GPIO 绑定规范的核心是 GPIO 控制器节点和 GPIO 使用者节点。
4.2.1 GPIO 控制器节点
GPIO 控制器节点必须声明 gpio-controller 属性,并通过 #gpio-cells 指定引用该控制器时需要多少个 cell 来描述 GPIO。通常 #gpio-cells = <2>,第一个 cell 是 GPIO 编号,第二个 cell 是标志位(如高电平有效、低电平有效)。
// GPIO 控制器节点示例(基于 GPIO 控制器绑定文档)
gpio0: gpio@ff730000 {
compatible = "snps,dw-apb-gpio";
reg = <0xff730000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <0 43 4>;
};
4.2.2 GPIO 使用者节点
外设节点通过 gpios 属性引用 GPIO 控制器。格式为:<&gpio_controller gpio_number flags>。
// GPIO 使用者节点示例
leds {
compatible = "gpio-leds";
led0 {
label = "power";
gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
gpio-controller 或 #gpio-cells,会导致引用该控制器的节点无法正确解析 GPIO。检查 dts 编译时的警告信息可以快速定位此类问题。
GPIO 标志位定义在 include/dt-bindings/gpio/gpio.h 中,常用值如下:
| 宏定义 | 值 | 含义 |
|---|---|---|
GPIO_ACTIVE_HIGH |
0 | 高电平有效 |
GPIO_ACTIVE_LOW |
1 | 低电平有效 |
GPIO_OPEN_DRAIN |
16 | 开漏输出 |
GPIO_OPEN_SOURCE |
32 | 开源输出 |
4.3 时钟绑定规范
时钟系统是 SoC 的“心跳”,设备树通过 clocks 和 clock-names 属性来描述时钟的供给关系。时钟绑定规范的核心是 时钟控制器节点和 时钟使用者节点。
4.3.1 时钟控制器节点
时钟控制器节点必须声明 #clock-cells 属性,指定引用该控制器时需要多少个 cell 来描述时钟输出。对于固定频率时钟,#clock-cells = <0>;对于多输出时钟,#clock-cells = <1> 或更多。
// 时钟控制器节点示例
clk_pll: clock-controller@ff760000 {
compatible = "rockchip,rk3399-pll";
reg = <0xff760000 0x1000>;
clocks = <&xin24m>;
clock-names = "xin24m";
#clock-cells = <1>;
};
4.3.2 时钟使用者节点
外设节点通过 clocks 属性引用时钟控制器,并通过 clock-names 为每个时钟指定名称,方便驱动通过名称获取时钟句柄。
// 时钟使用者节点示例
uart0: serial@ff180000 {
compatible = "rockchip,rk3399-uart", "snps,dw-apb-uart";
reg = <0xff180000 0x100>;
interrupts = <0 99 4>;
clocks = <&clk_pll 2>, <&clk_pll 3>;
clock-names = "baudclk", "apb_pclk";
};
#clock-cells = <1> 时,第一个 cell 是时钟输出索引。具体索引值对应的时钟名称需要查阅时钟控制器的绑定文档或数据手册。例如 <&clk_pll 2> 可能对应 PLL 的第二个输出。
时钟绑定中常用的宏定义位于 include/dt-bindings/clock/ 目录下,按 SoC 型号分类。例如 rk3399-cru.h 中定义了各个时钟的索引号:
// rk3399-cru.h 片段
#define PLL_APLLL 0
#define PLL_APLLB 1
#define PLL_DPLL 2
#define PLL_CPLL 3
#define PLL_GPLL 4
#define PLL_NPLL 5
4.4 中断绑定规范
中断是外设与 CPU 通信的重要机制。设备树通过 interrupts、interrupt-parent 和 interrupts-extended 属性来描述中断连接。中断绑定规范的核心是 中断控制器节点和 中断使用者节点。
4.4.1 中断控制器节点
中断控制器节点必须声明 interrupt-controller 属性,并通过 #interrupt-cells 指定引用时需要多少个 cell。对于 GIC(通用中断控制器),通常 #interrupt-cells = <3>,分别表示中断类型、中断号、触发类型。
// 中断控制器节点示例(GIC)
gic: interrupt-controller@fee00000 {
compatible = "arm,gic-v3";
reg = <0xfee00000 0x10000>, <0xfef00000 0x80000>;
interrupt-controller;
#interrupt-cells = <3>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
};
4.4.2 中断使用者节点
外设节点通过 interrupts 属性描述中断线。如果系统中有多个中断控制器,可以使用 interrupt-parent 指定父控制器,或使用 interrupts-extended 为每个中断指定不同的父控制器。
// 中断使用者节点示例
eth0: ethernet@ff3f0000 {
compatible = "snps,dwmac-4.20a";
reg = <0xff3f0000 0x4000>;
interrupts = <0 101 4>, <0 102 4>;
interrupt-names = "macirq", "eth_wake_irq";
interrupt-parent = <&gic>;
};
中断触发类型定义在 include/dt-bindings/interrupt-controller/irq.h 中:
| 宏定义 | 值 | 含义 |
|---|---|---|
IRQ_TYPE_EDGE_RISING |
1 | 上升沿触发 |
IRQ_TYPE_EDGE_FALLING |
2 | 下降沿触发 |
IRQ_TYPE_EDGE_BOTH |
3 | 双边沿触发 |
IRQ_TYPE_LEVEL_HIGH |
4 | 高电平触发 |
IRQ_TYPE_LEVEL_LOW |
8 | 低电平触发 |
4.5 绑定文档阅读实战案例
假设我们需要在 RK3399 平台上添加一个 I2C 设备,以下是查阅绑定文档的完整流程:
- 定位绑定文档:在
Documentation/devicetree/bindings/i2c/目录下找到i2c-rk3399.yaml或i2c-designware.txt。 - 检查 compatible:文档中列出
rockchip,rk3399-i2c和snps,designware-i2c。 - 查看属性定义:确认
reg、interrupts、clocks、clock-names是必选属性。 - 查看子节点:文档规定 I2C 总线上的从设备作为子节点,必须包含
reg属性(I2C 地址)。 - 参考示例:复制示例中的节点定义,修改 compatible 和 reg 值。
// 根据绑定文档编写的 I2C 节点
i2c0: i2c@ff3c0000 {
compatible = "rockchip,rk3399-i2c", "snps,designware-i2c";
reg = <0xff3c0000 0x1000>;
interrupts = <0 57 4>;
clocks = <&clk_pll 4>, <&clk_pll 5>;
clock-names = "i2c", "pclk";
#address-cells = <1>;
#size-cells = <0>;
// I2C 从设备
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
};
compatible(驱动匹配)、reg(地址空间)、interrupts/clocks/gpios(资源引用)。