4. 设备树GPIO控制器:GPIO控制器节点、gpio-specifier、GPIO号与标志位
在嵌入式Linux系统中,GPIO(通用输入输出)是最基础且使用最频繁的硬件资源之一。设备树通过标准化的描述方式,将GPIO控制器的硬件拓扑、引脚编号规则以及电气属性传递给内核。本章深入解析设备树中GPIO控制器的节点定义、gpio-specifier的编码规则,以及GPIO号与标志位的映射关系。
4.1 GPIO控制器节点(gpio-controller)
设备树中,每个能够控制GPIO引脚的硬件模块都必须声明为GPIO控制器节点。该节点通过两个关键属性标识其身份:
| 属性 | 类型 | 说明 |
|---|---|---|
gpio-controller |
空属性(布尔) | 声明该节点是一个GPIO控制器 |
#gpio-cells |
u32 | 指定引用该控制器时需要的gpio-specifier单元数,通常为2 |
关键规则: 一个设备节点可以同时是GPIO控制器和中断控制器,但必须分别声明
gpio-controller和interrupt-controller属性,并各自指定#gpio-cells和#interrupt-cells。
典型GPIO控制器节点示例:
gpio0: gpio@ff310000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff310000 0x0 0x1000>;
interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_gpio0>;
gpio-controller; // 声明为GPIO控制器
#gpio-cells = <2>; // 每个GPIO引用需要2个单元
interrupt-controller; // 同时作为中断控制器
#interrupt-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>; // 引脚范围映射
};
实战提示:
gpio-ranges属性将GPIO控制器的逻辑引脚号映射到pinctrl子系统的物理引脚号,确保GPIO和pinctrl的引脚管理一致。缺少该属性可能导致GPIO申请失败或引脚复用冲突。
4.2 gpio-specifier:GPIO引用编码规则
gpio-specifier是设备树中引用某个GPIO控制器的具体引脚时使用的编码序列。其长度由被引用控制器的#gpio-cells决定,通常为2个单元(cells)。
| 单元序号 | 名称 | 说明 |
|---|---|---|
| 第1个cell | GPIO号 | 相对于该控制器的引脚偏移量,从0开始 |
| 第2个cell | GPIO标志位 | 引脚配置标志,如高/低电平有效、上拉/下拉等 |
引用语法格式:
<&[GPIO控制器标签] [GPIO号] [标志位]>
实际设备树中的引用示例:
led-red {
compatible = "gpio-leds";
gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>;
};
sdmmc_pwr: sdmmc-pwr {
compatible = "regulator-fixed";
regulator-name = "sdmmc_vcc";
gpio = <&gpio3 RK_PA2 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
避坑指南: 注意
gpios(复数)和gpio(单数)属性的区别。gpios用于需要多个GPIO的场景(如GPIO LED组),gpio用于单个GPIO控制(如固定稳压器使能)。内核解析函数不同,混用会导致驱动加载失败。
4.3 GPIO号与标志位详解
4.3.1 GPIO号的编码方式
设备树中的GPIO号是相对于所属控制器的本地偏移量,而非系统全局编号。内核通过以下公式计算全局GPIO号:
全局GPIO号 = GPIO控制器基址 + 设备树中指定的GPIO号
其中,GPIO控制器基址由驱动注册时通过gpiochip_add或gpiochip_add_data指定,通常与设备树中gpio-ranges的起始引脚号一致。
| GPIO控制器 | 基址(base) | 设备树GPIO号 | 全局GPIO号 |
|---|---|---|---|
| gpio0 | 0 | 12 | 12 |
| gpio1 | 32 | 5 | 37 |
| gpio2 | 64 | 20 | 84 |
4.3.2 GPIO标志位定义
标志位用于描述引脚的电气特性,内核预定义了以下标准标志(定义在dt-bindings/gpio/gpio.h中):
| 宏定义 | 值 | 说明 |
|---|---|---|
GPIO_ACTIVE_HIGH |
0 | 高电平有效(默认) |
GPIO_ACTIVE_LOW |
1 | 低电平有效 |
GPIO_OPEN_DRAIN |
2 | 开漏输出 |
GPIO_OPEN_SOURCE |
4 | 开源输出 |
GPIO_PULL_UP |
8 | 内部上拉使能 |
GPIO_PULL_DOWN |
16 | 内部下拉使能 |
组合使用: 标志位可以通过按位或组合,例如
GPIO_ACTIVE_LOW | GPIO_PULL_UP 表示低电平有效且使能内部上拉。内核在of_get_named_gpio_flags解析时会自动合并标志位。
4.4 GPIO控制器与pinctrl的交互流程
下图展示了设备树GPIO控制器节点、pinctrl子系统以及V4L2驱动之间的引脚申请与配置流程:
4.5 常见GPIO标志位使用场景
不同外设对GPIO标志位的需求差异很大,以下表格总结了典型场景的推荐配置:
| 外设类型 | 推荐标志位 | 说明 |
|---|---|---|
| LED指示灯 | GPIO_ACTIVE_LOW |
共阳极LED,低电平点亮 |
| 按键输入 | GPIO_ACTIVE_LOW | GPIO_PULL_UP |
外部按下拉低,内部上拉保证空闲高电平 |
| 摄像头复位 | GPIO_ACTIVE_LOW |
复位信号通常低电平有效 |
| SD卡检测 | GPIO_ACTIVE_LOW | GPIO_PULL_UP |
卡插入时引脚被拉低 |
| 电源使能 | GPIO_ACTIVE_HIGH |
高电平开启稳压器 |
| I2C总线GPIO模拟 | GPIO_OPEN_DRAIN |
开漏输出,符合I2C总线规范 |
4.6 内核GPIO API与设备树标志位的对应关系
内核提供两套GPIO API:legacy GPIO(整数编号)和 descriptor-based GPIO(gpiod结构体)。设备树标志位对两者的影响不同:
| API类型 | 函数 | 标志位处理方式 |
|---|---|---|
| Legacy | of_get_named_gpio_flags |
返回原始标志位,驱动需自行调用gpio_direction_output并设置电平 |
| Descriptor | gpiod_get / devm_gpiod_get |
自动根据GPIO_ACTIVE_LOW反转电平,驱动无需关心物理电平极性 |
| Descriptor | gpiod_get_index |
支持多GPIO引脚的索引访问,标志位自动处理 |
最佳实践: 新驱动开发应优先使用descriptor-based GPIO API(
gpiod_*系列函数)。该API自动处理GPIO_ACTIVE_LOW标志位反转,使驱动代码与硬件极性解耦,提高可移植性。Legacy API仅用于兼容旧代码。
4.7 调试技巧:验证GPIO控制器配置
在调试设备树GPIO配置时,以下方法可快速验证节点定义是否正确:
- 查看设备树解析结果:
# 检查GPIO控制器是否被正确识别 ls /sys/kernel/debug/gpio # 查看具体GPIO控制器的引脚映射 cat /sys/kernel/debug/gpio | grep gpiochip - 验证gpio-specifier解析:
# 使用gpioinfo查看全局GPIO号与设备树标签的对应关系 gpioinfo # 查看特定GPIO的消费方信息 cat /sys/kernel/debug/gpio | grep "gpio-XXX" - 检查pinctrl与GPIO的一致性:
# 查看引脚复用状态 cat /sys/kernel/debug/pinctrl/*/pinmux-pins # 确认GPIO对应的引脚没有被其他外设占用 cat /sys/kernel/debug/pinctrl/*/pinmux-select
常见错误: 设备树中GPIO号超出控制器范围(如32引脚的控制器指定了GPIO号35),或
gpio-ranges缺失导致pinctrl无法正确配置引脚复用。这类错误通常表现为gpio_request返回-EINVAL或-EBUSY。
4.8 本章小结
设备树GPIO控制器的核心概念可归纳为以下要点:
- gpio-controller 和 #gpio-cells 是声明GPIO控制器的必备属性,缺一不可。
- gpio-specifier 由GPIO号和标志位组成,标志位支持按位组合,用于描述引脚电气特性。
- 设备树中的GPIO号是本地偏移量,内核通过控制器基址映射为全局编号。
- gpio-ranges 属性是GPIO控制器与pinctrl子系统的桥梁,确保引脚复用配置正确。
- V4L2驱动应使用descriptor-based GPIO API,利用设备树标志位自动处理电平极性。