4. GPIO控制器节点:在设备树中描述GPIO控制器

GPIO控制器节点是设备树中描述SoC内部GPIO硬件模块的核心节点。它向内核声明:“我是一颗GPIO控制器,我可以控制若干引脚,其他设备可以通过我申请和使用GPIO资源。” 理解GPIO控制器节点的描述规则,是编写正确GPIO按键设备树的前提。

核心概念: 设备树中的GPIO控制器节点不仅仅是一个硬件声明,它同时定义了“如何引用该控制器的GPIO引脚”的接口规范。这个接口规范由 #gpio-cells 属性决定。

4.1 GPIO控制器节点的基本结构

一个典型的GPIO控制器节点在设备树中通常位于SoC的 soc 节点下,其基本结构如下:

gpio0: gpio@ff730000 {
    compatible = "rockchip,gpio-bank";
    reg = <0x0 0xff730000 0x0 0x100>;
    interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clk_gpio0>;
    
    gpio-controller;
    #gpio-cells = <2>;
    
    gpio-ranges = <&pinctrl 0 0 32>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

上述节点中,与GPIO控制器身份直接相关的两个核心属性就是 gpio-controller 和 #gpio-cells。下面逐一深入解析。

4.2 gpio-controller:声明GPIO控制器身份

gpio-controller 是一个布尔属性(无值属性)。它的存在即表明该节点是一个GPIO控制器。内核在解析设备树时,遇到带有此属性的节点,会将其注册到GPIO子系统中,成为一个可用的GPIO芯片(gpio_chip)。

属性 类型 含义 是否必须
gpio-controller 布尔(空) 声明该节点为GPIO控制器 是
#gpio-cells u32 定义引用GPIO时所需的参数个数 是
gpio-ranges phandle+arg 将GPIO引脚映射到pinctrl引脚 推荐
interrupt-controller 布尔(空) 声明该节点同时也是中断控制器 按需
常见错误: 初学者容易忘记添加 gpio-controller; 属性,导致内核无法识别该节点为GPIO控制器,后续所有引用该节点的GPIO描述都会解析失败,驱动probe时返回 -EPROBE_DEFER 或 -EINVAL。

4.3 #gpio-cells:定义GPIO引脚的描述格式

#gpio-cells 是一个32位整数值,它规定了其他设备节点在引用该GPIO控制器的引脚时,需要在 gpios 或 xxx-gpios 属性中提供多少个参数(cell)。这是设备树GPIO描述中最重要的“协议约定”。

4.3.1 两种常见的 #gpio-cells 值

#gpio-cells 值 参数格式 典型应用 示例
1 <引脚号> 简单GPIO,仅需引脚编号 gpios = <&gpio0 15>;
2 <引脚号 标志位> 需要配置GPIO方向、上拉等标志 gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;

目前绝大多数GPIO控制器使用 #gpio-cells = <2>,因为第二个参数(标志位)提供了极大的灵活性,可以指定GPIO的有效电平极性。

4.3.2 第二个参数(标志位)的常用宏定义

在 <dt-bindings/gpio/gpio.h> 中定义了常用的标志位宏:

#define GPIO_ACTIVE_HIGH    0   // 高电平有效(默认)
#define GPIO_ACTIVE_LOW     1   // 低电平有效

在按键驱动场景中,GPIO_ACTIVE_LOW 非常常见,因为大多数按键按下时引脚被拉低到地。

4.4 GPIO控制器节点与引用节点的关系

下面的SVG图展示了GPIO控制器节点如何通过 #gpio-cells 协议,与使用GPIO的外设节点建立联系:

GPIO控制器节点与引用节点关系图 GPIO控制器节点与引用节点关系 GPIO控制器节点 gpio@ff730000 compatible = "rockchip,gpio-bank" reg = <0xff730000 0x100> gpio-controller; #gpio-cells = <2>; 定义协议 #gpio-cells = <2> 协议 参数1:引脚号 (0-31) 参数2:标志位 (0/1) 遵守协议 按键节点 key@0 compatible = "gpio-keys" gpios = < &gpio0 15 0>

4.5 内核如何解析GPIO控制器节点

当内核启动并解析设备树时,对GPIO控制器节点的处理流程如下:

  1. 节点遍历: 内核遍历设备树中的所有节点,查找带有 gpio-controller 属性的节点。
  2. 芯片注册: 调用 gpiochip_add_data() 或 of_gpiochip_add() 将该节点注册为一个GPIO芯片。
  3. cells记录: 读取 #gpio-cells 的值,存储在芯片的 of_gpio_n_cells 字段中,供后续GPIO解析使用。
  4. 引脚映射: 解析 gpio-ranges 属性,建立GPIO编号与pinctrl引脚编号的映射关系。
  5. 中断注册: 如果节点同时包含 interrupt-controller 属性,则注册为中断控制器,支持GPIO中断功能。
关键内核函数: of_get_named_gpio_flags() 是驱动中解析 gpios 属性的核心函数。它根据GPIO控制器节点的 #gpio-cells 值,自动解析出引脚号和标志位。如果 #gpio-cells 为2,则第二个参数会被解析为 enum of_gpio_flags 中的标志。

4.6 实际案例:Rockchip GPIO控制器节点

以Rockchip RK3399的GPIO0控制器为例,其完整的设备树节点如下:

gpio0: gpio@ff720000 {
    compatible = "rockchip,gpio-bank";
    reg = <0x0 0xff720000 0x0 0x100>;
    interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru PCLK_GPIO0>;
    
    gpio-controller;
    #gpio-cells = <2>;
    
    gpio-ranges = <&pinctrl 0 0 32>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

在这个节点中:

  • gpio-controller 告诉内核:这是一个GPIO控制器。
  • #gpio-cells = <2> 告诉内核:引用此控制器的GPIO时,需要提供2个参数(引脚号 + 标志位)。
  • gpio-ranges = <&pinctrl 0 0 32> 表示该控制器管理32个引脚,从pinctrl的引脚0开始映射。
  • interrupt-controller 和 #interrupt-cells = <2> 表示该GPIO控制器也能处理中断,为后续按键中断功能打下基础。

4.7 常见错误与调试方法

错误现象 可能原因 排查方法
驱动probe返回 -EPROBE_DEFER GPIO控制器节点缺少 gpio-controller 检查设备树中该节点是否包含 gpio-controller;
GPIO解析失败,返回值错误 #gpio-cells 值与实际提供的参数个数不匹配 核对 gpios 属性中的参数个数是否等于 #gpio-cells
引脚电平与预期相反 标志位设置错误,或未使用 GPIO_ACTIVE_LOW 检查第二个参数是否为 GPIO_ACTIVE_LOW (1)
中断无法触发 缺少 interrupt-controller 属性 确认GPIO控制器节点包含 interrupt-controller;
调试技巧: 在Linux内核启动时,可以通过 dmesg | grep gpio 查看GPIO芯片的注册信息。如果看到类似 gpiochip_add: registered GPIOs 0 to 31 on device: gpio@ff720000 的日志,说明GPIO控制器节点解析成功。此外,/sys/kernel/debug/gpio 文件系统也提供了所有GPIO控制器的运行时状态。

4.8 本章小结