4. GPIO子系统概述:Linux GPIO子系统架构、GPIO控制器、GPIO consumer与provider模型

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。
Linux GPIO子系统分层架构 消费层(GPIO Consumer) LED驱动 | 按键驱动 | SPI/II2C片选 | 中断控制器 核心层(GPIO Core / gpiolib) gpiochip_add / gpiod_get / 全局编号管理 / sysfs接口 控制器驱动层(GPIO Controller Driver) struct gpio_chip | .request / .free / .direction_input / .direction_output / .get / .set 硬件层(SoC GPIO Registers) GPIO Bank 0..N | 数据寄存器 / 方向寄存器 / 中断寄存器

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()获取描述符。
GPIO Provider-Consumer 模型 GPIO Provider gpio-controller; #gpio-cells = <2>; struct gpio_chip GPIO Consumer enable-gpios = <&gpio1 15 0>; gpiod_get(dev, "enable", GPIOD_OUT_LOW); struct gpio_desc 设备树解析 GPIO Core(gpiolib) of_get_named_gpio_flags() → gpiochip_find() → gpiod_get() 设备树GPIO引用格式:<provider_phandle pin_number flags> flags: 0=高有效, 1=低有效, 2=开漏, 3=开源, 4=无上拉, 8=上拉, 16=下拉

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导出的GPIO
  • gpioinfo(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工具,快速定位引脚冲突和配置错误。