4. 设备树基础:GPIO 控制器与使用者节点

设备树(Device Tree)是嵌入式 Linux 中描述硬件拓扑的标准机制。在 GPIO 子系统开发中,设备树承担着“硬件连线图”的角色:它告诉内核哪个引脚属于哪个控制器、引脚默认电平、中断触发方式等关键信息。本章聚焦设备树语法基础,并深入解析 GPIO 控制器节点与使用者节点的绑定规则。

4.1 设备树语法速览

设备树源文件(.dts)采用树形结构,每个硬件设备对应一个节点(node),节点内包含若干属性(property)。以下是最常用的语法元素:

语法元素 示例 说明
根节点 / { ... }; 设备树的顶层容器,所有设备节点挂载其下
节点定义 gpio0: gpio@ff310000 { ... }; gpio0 为标签(label),gpio@ff310000 为节点名+寄存器地址
属性赋值 compatible = "vendor,device"; 字符串属性,用于驱动匹配
32 位整数 reg = <0xff310000 0x1000>; 地址和长度对,通常描述寄存器范围
数组 gpios = <&gpio0 15 0>; 引用其他节点并传递参数
包含文件 #include "imx6ul.dtsi" 引入 SoC 级 .dtsi 头文件,复用公共定义
讲师提示: 设备树中的 <&gpio0 15 0> 这种写法称为 phandle + 参数 模式。&gpio0 是对标签 gpio0 节点的引用,后面的 15 和 0 是传递给该节点的参数,具体含义由 #gpio-cells 决定。

4.2 GPIO 控制器节点

GPIO 控制器节点描述 SoC 内部的一个 GPIO 模块。它必须声明两个关键属性:

  • gpio-controller —— 空属性,标记该节点是一个 GPIO 控制器。
  • #gpio-cells —— 整型属性,指定使用者在引用该控制器时需要提供几个参数(cell)。

以下是一个典型的 GPIO 控制器节点(以 i.MX6UL 为例):

gpio1: gpio@0209c000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x0209c000 0x4000>;
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

该节点中 #gpio-cells = <2> 表示:任何其他节点通过 gpios 引用 gpio1 时,必须提供 2 个参数——引脚号(0~31)和标志位(如 GPIO_ACTIVE_HIGH/LOW)。

关键区分: #gpio-cells 与 #interrupt-cells 是两个独立的属性。前者用于 GPIO 输出/输入控制,后者用于中断触发。一个控制器可以同时支持两种功能,但参数个数可能不同。

4.3 GPIO 使用者节点

使用者节点(consumer node)代表一个需要占用 GPIO 引脚的设备,例如 LED、按键、传感器等。它通过 gpios 属性(或 cs-gpios、reset-gpios 等命名后缀)来声明引脚连接。

基本语法格式:

gpios = <&gpio_controller pin_number flags>;

其中 pin_number 和 flags 的个数由控制器节点的 #gpio-cells 决定。以下是一个 LED 子节点的完整示例:

/ {
    leds {
        compatible = "gpio-leds";
        led-red {
            label = "red-led";
            gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
            linux,default-trigger = "heartbeat";
        };
    };
};

这里 gpios 提供了 3 个值:

  1. &gpio1 —— 引用 gpio1 控制器节点。
  2. 15 —— 引脚编号(GPIO1_IO15)。
  3. GPIO_ACTIVE_LOW —— 标志位,表示低电平有效(内核中定义为 1)。
常见错误: 如果控制器定义 #gpio-cells = <2>,但使用者只写了 gpios = <&gpio1 15>(缺少 flags),内核在解析时会报错并忽略该引脚。务必检查 #gpio-cells 与 gpios 参数个数是否匹配。

4.4 GPIO 控制器与使用者关系图

下图展示了设备树中 GPIO 控制器节点与使用者节点之间的引用关系,以及 #gpio-cells 如何决定参数传递格式:

设备树 GPIO 控制器与使用者节点关系 GPIO 控制器节点 gpio1: gpio@0209c000 compatible = "fsl,imx6ul-gpio" gpio-controller; #gpio-cells = <2> GPIO 使用者节点 led-red { label = "red-led"; gpios = <&gpio1 15 0>; }; phandle 引用 #gpio-cells = <2> 参数含义 参数 1:引脚编号(0~31) 参数 2:标志位(0=GPIO_ACTIVE_HIGH, 1=GPIO_ACTIVE_LOW) 使用者必须提供恰好 2 个参数,否则内核解析失败

4.5 多控制器与命名后缀

一个设备可能占用多个 GPIO 引脚,且这些引脚可能来自不同的控制器。设备树允许使用 命名后缀 来区分不同用途的引脚:

属性名 典型用途 示例
gpios 通用 GPIO 引脚 gpios = <&gpio1 10 0>;
cs-gpios SPI 片选引脚 cs-gpios = <&gpio2 5 0>;
reset-gpios 复位引脚 reset-gpios = <&gpio3 12 GPIO_ACTIVE_LOW>;
irq-gpios 中断请求引脚 irq-gpios = <&gpio4 7 0>;

内核通过 of_get_named_gpio_flags() 等 API 根据属性名解析对应的引脚。命名后缀使设备树更具可读性,也便于驱动代码按功能查找引脚。

4.6 本章小结

  • 设备树使用 节点 + 属性 描述硬件,compatible 是驱动匹配的关键。
  • GPIO 控制器节点必须声明 gpio-controller 和 #gpio-cells,后者决定使用者提供参数的个数。
  • 使用者节点通过 gpios 属性引用控制器,参数包括 phandle、引脚号、标志位。
  • 多引脚设备可使用 cs-gpios、reset-gpios 等命名后缀区分功能。
  • 参数个数不匹配是设备树最常见的错误之一,务必在编译时使用 dtc 工具检查。