4. 设备树GPIO绑定:设备树语法基础、GPIO控制器节点、gpios属性、引脚复用配置

设备树(Device Tree)是嵌入式Linux中描述硬件拓扑的核心机制。对于GPIO驱动开发而言,设备树承担着“硬件描述语言”的角色——它告诉内核:哪个芯片负责GPIO控制、哪些引脚被用作输入/输出、中断触发方式是什么、引脚复用功能如何切换。本章从语法基础出发,逐步深入到GPIO控制器节点、gpios属性及引脚复用配置,为后续中断注册与驱动编写打下坚实的硬件描述基础。

4.1 设备树语法基础

设备树使用节点(node)和属性(property)来描述硬件。每个节点代表一个设备或总线,属性则描述设备的配置参数。以下是必须掌握的核心语法要素:

语法元素 格式 说明
节点定义 node-name@unit-address { ... }; 节点名称+寄存器地址(可选)
属性赋值 property-name = <value>; 支持整数、字符串、数组、二进制数据
标签引用 &label { ... }; 通过标签扩展或覆盖已有节点
包含文件 #include "filename.dtsi" 引入公共设备树头文件
宏定义 #define GPIO_ACTIVE_HIGH 0 配合#include使用,增强可读性
关键约定:设备树中#address-cells和#size-cells决定子节点reg属性的地址和长度字段位数。GPIO控制器通常设置#address-cells = <1>、#size-cells = <0>。

以下是一个典型的设备树片段,展示节点层级与属性写法:

/dts-v1/;

#include <dt-bindings/gpio/gpio.h>

/ {
    model = "Embedded GPIO Demo Board";
    compatible = "vendor,board-rev1";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;

        gpio0: gpio@ff710000 {
            compatible = "vendor,gpio-controller";
            reg = <0xff710000 0x1000>;
            gpio-controller;
            #gpio-cells = <2>;
            interrupts = <0 32 4>;
        };
    };
};
提示:#gpio-cells = <2>表示每个GPIO引用需要2个参数:引脚号(0~31)和标志位(如GPIO_ACTIVE_HIGH或GPIO_ACTIVE_LOW)。
设备树节点层级结构 设备树节点层级结构 / (根节点) soc gpio0: gpio@ff710000 compatible = "vendor,gpio-controller" uart0: serial@ff690000 compatible = "vendor,uart" 属性列表 #gpio-cells = <2> gpio-controller; interrupts = <0 32 4>

4.2 GPIO控制器节点

GPIO控制器节点是设备树中描述GPIO硬件模块的核心节点。它必须包含以下关键属性:

  • gpio-controller; —— 标记该节点为GPIO控制器,是必须的布尔属性。
  • #gpio-cells —— 指定引用GPIO时需要的参数个数,通常为2(引脚号+标志位)。
  • compatible —— 匹配驱动字符串,如"vendor,gpio-controller"。
  • reg —— 控制器寄存器基地址和地址范围。
  • interrupts —— 控制器本身的中断号,用于支持引脚中断功能。
常见错误:忘记添加gpio-controller;属性会导致内核无法将该节点识别为GPIO控制器,其他设备通过gpios引用时会返回-EPROBE_DEFER或-EINVAL。

以下是一个完整的GPIO控制器节点示例,包含中断控制器功能:

gpio2: gpio@ff720000 {
    compatible = "vendor,gpio-controller";
    reg = <0xff720000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    interrupts = <0 33 4>;
    clocks = <&clk_gate GPIO2_CLK>;
    ngpios = <32>;
};
属性 值示例 作用
interrupt-controller; 布尔 声明该节点也是中断控制器
#interrupt-cells <2> 中断描述参数:<引脚号 触发类型>
ngpios <32> 该控制器管理的GPIO引脚总数
clocks <&clk_gate GPIO2_CLK> GPIO模块的时钟源
GPIO控制器节点内部结构 GPIO控制器节点内部结构 gpio2: gpio@ff720000 compatible = "vendor,gpio-controller" 标识属性 gpio-controller; interrupt-controller; 配置属性 #gpio-cells = <2> #interrupt-cells = <2> 资源属性 reg = <0xff720000 0x1000> interrupts = <0 33 4> 驱动匹配流程 1. 内核解析 compatible 匹配驱动 2. 读取 reg 获取寄存器基地址 3. 注册 gpio_chip 和 irq_chip

4.3 gpios属性:如何引用GPIO引脚

当外设设备需要使用GPIO引脚时,通过gpios属性(或cs-gpios、reset-gpios等命名变体)引用GPIO控制器中的具体引脚。引用格式为:

gpios = <&gpio_controller pin_number flags>;

其中flags常用宏定义(定义在dt-bindings/gpio/gpio.h中):

宏名称 值 含义
GPIO_ACTIVE_HIGH 0 高电平有效(默认)
GPIO_ACTIVE_LOW 1 低电平有效
GPIO_OPEN_DRAIN 16 开漏输出
GPIO_OPEN_SOURCE 32 开源输出
GPIO_PULL_UP 64 内部上拉
GPIO_PULL_DOWN 128 内部下拉

实际设备树中的使用示例:

#include <dt-bindings/gpio/gpio.h>

led-red {
    compatible = "gpio-leds";
    gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
    linux,default-trigger = "heartbeat";
};

button-reset {
    compatible = "gpio-keys";
    gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
    label = "reset_button";
    linux,code = <KEY_RESTART>;
};
最佳实践:始终使用#include <dt-bindings/gpio/gpio.h>中的宏定义,而不是直接写数字0或1。这能显著提高设备树的可读性和可维护性。
gpios属性引用关系 gpios属性引用关系 led-red compatible = "gpio-leds" gpios = <&gpio0 12 0> (控制器 引脚号 标志位) gpio0: gpio@ff710000 #gpio-cells = <2> 引脚12 → 输出高电平 物理引脚12 GPIO0_12

4.4 引脚复用配置

引脚复用(Pin Mux)是嵌入式系统中常见的需求——同一个物理引脚可能被配置为GPIO、UART、I2C、SPI等功能。设备树通过pinctrl子系统描述引脚复用状态。核心概念包括:

  • pinctrl-names —— 定义状态名称列表,如"default"、"sleep"、"active"。
  • pinctrl-0、pinctrl-1 —— 对应每个状态的引脚配置句柄。
  • pinmux节点 —— 在SoC设备树中预定义,描述引脚功能选择寄存器配置。

典型的引脚复用配置示例:

&uart1 {
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart1_tx_pins &uart1_rx_pins>;
    pinctrl-1 = <&uart1_sleep_pins>;
    status = "okay";
};

/* 在SoC dtsi中预定义的引脚组 */
uart1_tx_pins: uart1_tx_pins {
    pins = "GPIO1_12";
    function = "uart1_tx";
    bias-pull-up;
    drive-strength = <4>;
};

uart1_rx_pins: uart1_rx_pins {
    pins = "GPIO1_13";
    function = "uart1_rx";
    bias-pull-up;
};
pinctrl属性 作用 典型值
pins 指定引脚名称或编号 "GPIO1_12"、"PAD_CTRL0"
function 引脚功能选择 "uart1_tx"、"gpio"
bias-pull-up 内部上拉 布尔属性
bias-pull-down 内部下拉 布尔属性
bias-disable 禁用上下拉 布尔属性
drive-strength 驱动能力(mA) <4>、<8>
input-enable 使能输入缓冲 布尔属性
output-high 初始输出高电平 布尔属性
驱动加载时序:当设备驱动probe时,内核pinctrl子系统自动将引脚切换到pinctrl-names中名为"default"的状态。如果定义了"sleep"状态,系统休眠时会自动切换。
引脚复用配置流程 引脚复用配置流程 uart1 设备节点 pinctrl-names = "default", "sleep" pinctrl-0 = <&uart1_tx_pins &uart1_rx_pins> uart1_tx_pins pins = "GPIO1_12" function = "uart1_tx" uart1_rx_pins pins = "GPIO1_13" function = "uart1_rx" 物理引脚 GPIO1_12 / GPIO1_13

4.5 综合案例:带中断的按键设备树配置

将以上知识点整合,展示一个完整的带中断功能的按键设备树配置:

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    gpio-keys {
        compatible = "gpio-keys";
        #address-cells = <1>;
        #size-cells = <0>;
        pinctrl-names = "default";
        pinctrl-0 = <&key_pins>;

        button@0 {
            label = "USER_KEY";
            gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
            interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
            linux,code = <KEY_ENTER>;
            debounce-interval = <50>;
        };
    };
};

&pinctrl {
    key_pins: key_pins {
        pins = "GPIO0_15";
        function = "gpio";
        bias-pull-up;
        input-enable;
    };
};
注意:当使用gpios属性时,如果GPIO控制器也支持中断,可以在同一节点中同时使用gpios和interrupts属性。但interrupts属性中的第一个参数是引脚号,第二个参数是触发类型,与gpios的标志位含义不同,不要混淆。