4. 设备树绑定文档:如何阅读内核 Documentation/devicetree/bindings/、常见绑定规范(GPIO、时钟、中断)

设备树绑定文档是内核官方提供的“硬件描述字典”,它规定了每个 IP 或外设的节点应该包含哪些属性、属性值的格式以及子节点的结构。本章将教你如何高效阅读 Documentation/devicetree/bindings/ 目录下的文档,并深入解析 GPIO、时钟、中断这三种最核心的绑定规范。

学习目标:掌握绑定文档的通用结构;能够独立查阅 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 一个完整的设备树节点示例 直接复制示例并修改是最快的移植方式

阅读绑定文档的推荐步骤:

  1. 定位文件:在 Documentation/devicetree/bindings/ 下按厂商或 IP 类型查找,例如 gpio/、clock/、interrupt-controller/。
  2. 检查 compatible:找到文档中的 compatible 属性定义,确认字符串是否与驱动代码匹配。
  3. 核对 reg:确认地址和大小(#address-cells, #size-cells)的格式。
  4. 检查中断/时钟/GPIO 引用:确认父控制器的 phandle 和 cell 数量。
  5. 参考示例:将示例节点复制到你的 dts 文件中,再根据实际硬件修改。
实战技巧:在 Linux 源码根目录下使用 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 控制器节点中声明 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 开源输出
GPIO 绑定关系结构图 GPIO 控制器节点 gpio-controller; #gpio-cells = <2>; interrupt-controller; GPIO 使用者节点 gpios = <&gpio0 12 0>; label = "power"; default-state = "on"; 引用(phandle + 2 cells) GPIO 绑定关键点 控制器必须声明 gpio-controller 和 #gpio-cells 使用者通过 gpios 属性引用,格式:<&phandle gpio_num flags>

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
时钟绑定关系结构图 时钟源(晶振) xin24m: clock@0 { clock-frequency = <24000000>; 时钟控制器 #clock-cells = <1>; clocks = <&xin24m>; 外设节点(UART) clocks = <&clk_pll 2>; clock-names = "baudclk"; 输入 输出(索引 2) 时钟绑定关键点 控制器必须声明 #clock-cells,指定输出索引的 cell 数量 使用者通过 clocks 和 clock-names 配对引用

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>;
};
中断号映射:GIC 的中断号通常分为 SPI(共享外设中断,ID 32~1019)和 PPI(私有外设中断,ID 16~31)。设备树中填写的中断号是硬件中断号,驱动中使用的 IRQ 号由内核在初始化时动态映射。注意 SPI 中断号在设备树中通常减去 32 填写(取决于 GIC 驱动实现),但更常见的是直接填写硬件中断号,由 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 低电平触发
中断绑定关系结构图 GIC 中断控制器 interrupt-controller; #interrupt-cells = <3>; compatible = "arm,gic-v3"; 外设节点(Ethernet) interrupts = <0 101 4>; interrupt-parent = <&gic>; interrupt-names = "macirq"; 中断线(SPI 101, 电平触发) 中断绑定关键点 中断控制器必须声明 interrupt-controller 和 #interrupt-cells 使用者通过 interrupts 描述中断线,interrupt-parent 指定父控制器

4.5 绑定文档阅读实战案例

假设我们需要在 RK3399 平台上添加一个 I2C 设备,以下是查阅绑定文档的完整流程:

  1. 定位绑定文档:在 Documentation/devicetree/bindings/i2c/ 目录下找到 i2c-rk3399.yaml 或 i2c-designware.txt。
  2. 检查 compatible:文档中列出 rockchip,rk3399-i2c 和 snps,designware-i2c。
  3. 查看属性定义:确认 reg、interrupts、clocks、clock-names 是必选属性。
  4. 查看子节点:文档规定 I2C 总线上的从设备作为子节点,必须包含 reg 属性(I2C 地址)。
  5. 参考示例:复制示例中的节点定义,修改 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>;
    };
};
实战总结:绑定文档是设备树开发的“宪法”。每次添加新设备时,先花 5 分钟阅读对应的绑定文档,可以避免 80% 的常见错误。记住三个核心属性:compatible(驱动匹配)、reg(地址空间)、interrupts/clocks/gpios(资源引用)。