4. 设备树中GPIO的绑定:gpio-controller与#gpio-cells属性、GPIO specifier格式、标准GPIO绑定示例

在嵌入式Linux网关硬件抽象层中,GPIO是最基础且使用最频繁的硬件资源。设备树(Device Tree)通过一套标准化的绑定机制,将GPIO控制器的硬件描述与驱动程序的资源获取解耦。本章节深入解析GPIO绑定的核心属性、GPIO specifier的编码格式,并通过完整的绑定示例展示如何在网关主板上描述GPIO资源。

4.1 GPIO绑定的核心属性

设备树中,任何一个能够控制GPIO引脚的硬件模块(如GPIO控制器、GPIO扩展芯片、SoC内部的GPIO Bank)都必须声明两个关键属性,以告知内核该节点是一个GPIO提供者。

属性名 类型 必需性 说明
gpio-controller 空属性(boolean) 必需 标记该节点是一个GPIO控制器,可以对外提供GPIO引脚
#gpio-cells u32 必需 指定引用该控制器时,GPIO specifier中需要多少个cell(32位整数)来描述一个GPIO

关键理解: gpio-controller 是“提供者”的声明,#gpio-cells 是“接口契约”的定义。消费者节点通过GPIO specifier引用引脚时,必须按照 #gpio-cells 规定的格式提供参数。

4.2 GPIO specifier格式

GPIO specifier是消费者节点中用于引用一个具体GPIO引用的参数序列。其格式由GPIO控制器的 #gpio-cells 值决定。最常见的格式是 #gpio-cells = <2>,即两个cell的格式。

4.2.1 标准两cell格式(#gpio-cells = <2>)

Cell序号 名称 说明
cell 0 GPIO编号 控制器内部的引脚索引号,从0开始
cell 1 GPIO标志位 位掩码,定义引脚的电气特性与逻辑行为

GPIO标志位(cell 1)的常用位定义:

位 宏定义 值 含义
bit 0 GPIO_ACTIVE_LOW 1 引脚低电平有效(逻辑1对应低电平)
bit 1 GPIO_OPEN_DRAIN 2 引脚配置为开漏输出
bit 2 GPIO_OPEN_SOURCE 4 引脚配置为开源输出
bit 3 GPIO_PULL_UP 8 使能内部上拉电阻
bit 4 GPIO_PULL_DOWN 16 使能内部下拉电阻

实践建议: 在网关硬件抽象层中,对于复位信号、中断请求等关键控制线,务必在设备树中明确设置 GPIO_ACTIVE_LOW 标志,使驱动逻辑与硬件原理图保持一致,避免因电平极性误解导致的驱动bug。

4.2.2 单cell格式(#gpio-cells = <1>)

部分简单的GPIO控制器(如某些GPIO扩展芯片)可能只使用单cell格式,此时cell 0仅表示引脚编号,不携带标志位。驱动中默认使用高电平有效、推挽输出。

4.3 GPIO绑定关系SVG架构图

下图展示了设备树中GPIO提供者与消费者之间的绑定关系,以及GPIO specifier在其中的传递路径。

设备树GPIO绑定关系架构图 设备树GPIO绑定关系架构 GPIO控制器节点 gpio-controller; #gpio-cells = <2>; 提供引脚:GPIO0 ~ GPIO31 基地址:0x1C20800 消费者:LED节点 compatible = "gpio-leds"; gpios = <&gpio0 12 0>; GPIO12, 高电平有效 消费者:按键节点 compatible = "gpio-keys"; gpios = <&gpio0 7 1>; GPIO7, 低电平有效 GPIO specifier (12, 0) GPIO specifier (7, 1) 绑定规则 消费者通过 <&phandle pin flags> 引用提供者的GPIO引脚 phandle指向gpio-controller节点

4.4 标准GPIO绑定示例

以下是一个完整的网关主板设备树片段,展示了从GPIO控制器声明到多个消费者节点引用的全过程。该示例基于典型的ARM Cortex-A7网关SoC。

4.4.1 GPIO控制器声明

soc {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "simple-bus";
    ranges;

    gpio0: gpio@1c20800 {
        compatible = "allwinner,sun8i-h3-gpio";
        reg = <0x01c20800 0x400>;
        interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&ccu CLK_APB1>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    gpio1: gpio@1c20c00 {
        compatible = "allwinner,sun8i-h3-gpio";
        reg = <0x01c20c00 0x400>;
        interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&ccu CLK_APB1>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };
};

4.4.2 消费者节点引用GPIO

/* 网关状态指示灯:使用gpio0的引脚12,高电平有效 */
leds {
    compatible = "gpio-leds";
    status-led {
        label = "gateway:green:status";
        gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
        default-state = "on";
    };
    alarm-led {
        label = "gateway:red:alarm";
        gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
        default-state = "off";
        linux,default-trigger = "none";
    };
};

/* 网关物理按键:使用gpio1的引脚7,低电平有效,内部上拉 */
keys {
    compatible = "gpio-keys";
    #address-cells = <1>;
    #size-cells = <0>;
    autorepeat;
    reset-key {
        label = "Reset Button";
        linux,code = <KEY_RESTART>;
        gpios = <&gpio1 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
        debounce-interval = <50>;
    };
};

/* 网关4G模块电源控制:使用gpio0的引脚3,高电平有效 */
&pinctrl {
    modem_power_pin: modem_power_pin {
        pins = "PA3";
        function = "gpio_out";
    };
};

&gpio0 {
    modem-power {
        gpio-hog;
        gpios = <3 GPIO_ACTIVE_HIGH>;
        output-high;
        line-name = "modem-power";
    };
};

常见错误: 在消费者节点中引用GPIO时,gpios 属性的第一个参数是GPIO控制器的phandle,第二个参数是引脚编号,第三个参数是标志位。初学者容易混淆引脚编号与物理引脚序号——设备树中的编号是控制器内部的软件索引,并非芯片封装的物理引脚号。务必查阅SoC数据手册的GPIO Bank映射表。

4.5 GPIO绑定中的高级用法

4.5.1 GPIO Hogging(引脚预配置)

GPIO Hogging允许在设备树中直接配置GPIO引脚的初始状态(输出高/低、输入),而无需编写额外的驱动代码。这对于网关中那些只需要固定电平的控制信号(如复位、使能、电源控制)非常有用。

&gpio0 {
    /* 在GPIO控制器节点内部定义hog子节点 */
    usb-hub-reset {
        gpio-hog;
        gpios = <5 GPIO_ACTIVE_LOW>;
        output-high;    /* 初始输出高电平,使USB HUB正常工作 */
        line-name = "usb-hub-reset";
    };
    
    eth-phy-power {
        gpio-hog;
        gpios = <8 GPIO_ACTIVE_HIGH>;
        output-high;
        line-name = "eth-phy-power";
    };
};

4.5.2 多控制器引用

在复杂的网关设计中,可能同时使用SoC内置GPIO和外部I2C/SPI扩展GPIO。消费者节点可以引用不同控制器的引脚。

/* 外部GPIO扩展芯片:基于I2C的PCA9535 */
&i2c0 {
    gpio_expander: gpio@20 {
        compatible = "nxp,pca9535";
        reg = <0x20>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-parent = <&gpio0>;
        interrupts = <10 IRQ_TYPE_EDGE_FALLING>;
    };
};

/* 消费者同时使用SoC GPIO和扩展GPIO */
sensor-enable {
    compatible = "gpio-enable";
    enable-gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>,
                   <&gpio_expander 3 GPIO_ACTIVE_LOW>;
};

4.6 GPIO绑定验证流程

在将设备树提交给硬件抽象层之前,建议按照以下步骤验证GPIO绑定的正确性:

  1. 语法检查: 使用 dtc -I dts -O dtb 编译设备树源文件,确保无语法错误。
  2. 绑定检查: 使用 dt-validate 工具检查是否满足内核的GPIO绑定schema。
  3. phandle解析: 确认所有 &gpio0、&gpio1 等phandle引用正确指向已声明的GPIO控制器节点。
  4. 引脚冲突检测: 检查是否有多个消费者节点引用了同一个GPIO引脚(除非是共享输入场景)。
  5. 标志位验证: 确认 GPIO_ACTIVE_LOW 等标志位与硬件原理图一致。

网关HAL层最佳实践: 在硬件抽象层中,建议将所有GPIO的命名与功能定义集中在设备树的一个单独包含文件中(如 gateway-gpio-map.dtsi),便于硬件改版时集中修改,避免在多个驱动文件中分散定义。

4.7 GPIO绑定与PWM绑定的对比

对比维度 GPIO绑定 PWM绑定
提供者标记 gpio-controller #pwm-cells + 无特定标记(通过compatible识别)
cells数量 通常为2(引脚号 + 标志位) 通常为3(通道号 + 周期 + 极性)
消费者属性名 gpios 或 xxx-gpios pwms 或 xxx-pwms
标志位含义 电气特性(极性、开漏、上拉) 极性(PWM_POLARITY_INVERTED)
典型应用 数字输入/输出、中断、复位 背光调节、电机速度、蜂鸣器

4.8 本章小结