4. GPIO控制器节点:在设备树中描述GPIO控制器
GPIO控制器节点是设备树中描述SoC内部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的外设节点建立联系:
4.5 内核如何解析GPIO控制器节点
当内核启动并解析设备树时,对GPIO控制器节点的处理流程如下:
- 节点遍历: 内核遍历设备树中的所有节点,查找带有
gpio-controller属性的节点。 - 芯片注册: 调用
gpiochip_add_data()或of_gpiochip_add()将该节点注册为一个GPIO芯片。 - cells记录: 读取
#gpio-cells的值,存储在芯片的of_gpio_n_cells字段中,供后续GPIO解析使用。 - 引脚映射: 解析
gpio-ranges属性,建立GPIO编号与pinctrl引脚编号的映射关系。 - 中断注册: 如果节点同时包含
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; |
dmesg | grep gpio 查看GPIO芯片的注册信息。如果看到类似 gpiochip_add: registered GPIOs 0 to 31 on device: gpio@ff720000 的日志,说明GPIO控制器节点解析成功。此外,/sys/kernel/debug/gpio 文件系统也提供了所有GPIO控制器的运行时状态。