4. GPIO子系统概述
GPIO(General Purpose Input/Output)子系统是Linux内核中管理通用输入输出引脚的框架。它向上为驱动开发者提供统一的API接口,向下通过pinctrl子系统与硬件寄存器交互。理解GPIO子系统的分层架构和provider/consumer模型,是正确配置设备树GPIO节点、编写GPIO驱动以及排查硬件复用冲突的前提。
4.1 Linux GPIO子系统分层架构
GPIO子系统采用经典的分层设计,从下到上依次为:硬件层 → GPIO控制器驱动层 → GPIO核心层 → GPIO消费层。每一层职责清晰,通过标准接口解耦。
架构要点:
- 硬件层:SoC内部的GPIO寄存器组,每个bank通常包含32个引脚,支持输入、输出、中断等功能。
- 控制器驱动层:实现gpio_chip结构体,注册到核心层,负责读写引脚电平、配置方向、处理中断。
- 核心层:提供gpiolib API,管理全局GPIO编号空间,维护引脚状态和引用计数。
- 消费层:其他驱动(如LED、按键、SPI、I2C)通过gpiod_*系列函数请求和使用GPIO。
4.2 GPIO控制器(GPIO Controller)
GPIO控制器驱动是子系统中最底层的软件实体,通过填充struct gpio_chip结构体并调用gpiochip_add_data()注册到核心层。每个gpio_chip代表一个GPIO bank,管理一组连续的引脚。
| gpio_chip成员 | 类型 | 说明 |
|---|---|---|
.label |
const char * | 控制器名称,通常与设备树节点名一致 |
.base |
int | 全局GPIO编号起始值,-1表示由内核动态分配 |
.ngpio |
int | 该bank包含的引脚数量,通常为32 |
.request |
int (*)(struct gpio_chip *, unsigned offset) | 引脚被请求时调用,可用于pinctrl复用检查 |
.free |
void (*)(struct gpio_chip *, unsigned offset) | 引脚被释放时调用 |
.direction_input |
int (*)(struct gpio_chip *, unsigned offset) | 设置引脚为输入模式 |
.direction_output |
int (*)(struct gpio_chip *, unsigned offset, int value) | 设置引脚为输出模式并设定初始电平 |
.get |
int (*)(struct gpio_chip *, unsigned offset) | 读取引脚当前电平 |
.set |
void (*)(struct gpio_chip *, unsigned offset, int value) | 设置引脚输出电平 |
.to_irq |
int (*)(struct gpio_chip *, unsigned offset) | 将GPIO引脚映射为中断号 |
调试提示:在用户空间可通过
/sys/kernel/debug/gpio 查看所有已注册的gpio_chip及其引脚状态。使用 cat /sys/kernel/debug/gpio 可快速确认控制器是否成功注册、引脚是否被占用。
4.3 GPIO Consumer与Provider模型
GPIO子系统采用provider-consumer设计模式,这是设备树驱动开发中最重要的概念之一。
- Provider(提供者):实现gpio_chip并注册到内核的GPIO控制器驱动。它在设备树中通过
gpio-controller属性声明自己是一个GPIO提供者,并通过#gpio-cells指定描述一个GPIO所需的参数个数(通常为2:引脚号和标志位)。 - Consumer(消费者):使用GPIO功能的其他驱动模块。消费者在设备树中通过
<provider_phandle> <pin_number> <flags>格式引用GPIO,内核解析后自动调用gpiod_get()获取描述符。
4.4 设备树中的GPIO绑定
在设备树中,GPIO provider和consumer通过标准属性完成绑定。以下是一个典型示例:
// GPIO Provider(控制器节点)
gpio1: gpio@50002000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x50002000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller; // 声明为GPIO提供者
#gpio-cells = <2>; // 每个GPIO描述需要2个cell
interrupt-controller;
#interrupt-cells = <2>;
};
// GPIO Consumer(使用GPIO的设备)
leds {
compatible = "gpio-leds";
status-led {
label = "status";
gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; // 引用gpio1的第15号引脚,低有效
linux,default-trigger = "heartbeat";
};
};
// 另一个Consumer示例:按键
button {
compatible = "gpio-keys";
power-key {
label = "Power";
gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
linux,code = <KEY_POWER>;
};
};
常见错误:
- Provider节点缺少
gpio-controller或#gpio-cells属性,导致consumer引用时解析失败。 - Consumer中
gpios属性拼写错误(误写为gpio),内核无法识别。 - 引脚号超出provider的
ngpio范围,驱动加载时报错invalid GPIO。 - 同一引脚被多个consumer同时请求,导致
gpiod_request返回-EBUSY。
4.5 GPIO编号与API映射
理解GPIO编号的映射关系对调试至关重要。内核中存在三种编号方式:
| 编号类型 | 范围 | 使用场景 | 示例 |
|---|---|---|---|
| 全局编号 | 0 ~ 511(取决于架构) | 旧版API(gpio_request/gpio_direction_output) | gpio_request(47, "my-led") |
| 描述符 | struct gpio_desc * | 新版API(gpiod_get/gpiod_set_value) | desc = gpiod_get(dev, "enable", GPIOD_OUT_LOW) |
| 设备树偏移 | 0 ~ ngpio-1 | 设备树中引用的引脚号 | <&gpio1 15 0> 中的15 |
调试命令:
cat /sys/kernel/debug/gpio— 查看所有GPIO引脚状态、方向、使用者ls /sys/class/gpio/— 查看通过sysfs导出的GPIOgpioinfo(libgpiod工具)— 查看当前系统GPIO线路信息gpioget gpiochip0 15— 读取gpiochip0第15号引脚电平
4.6 本章小结
GPIO子系统通过分层架构和provider-consumer模型,实现了硬件无关的GPIO管理。核心要点包括:
- gpio_chip是控制器驱动的核心数据结构,必须实现方向控制和电平读写回调。
- 设备树中provider需声明
gpio-controller和#gpio-cells,consumer通过gpios属性引用。 - 新版驱动应优先使用基于描述符的
gpiod_*API,避免使用已废弃的全局编号接口。 - 调试时善用
/sys/kernel/debug/gpio和libgpiod工具,快速定位引脚冲突和配置错误。