1. GPIO 子系统回顾:GPIO 硬件基础、GPIO 控制器寄存器模型、Linux GPIO 子系统软件架构

在深入 GPIO 中断与内核编译调试之前,必须重新夯实 GPIO 子系统的三大支柱:硬件电气基础、控制器寄存器模型以及Linux 内核软件架构。这三者构成了驱动开发者从“点灯”到“中断响应”的认知阶梯。

1.1 GPIO 硬件基础:从引脚到电气特性

GPIO(General Purpose Input/Output)是芯片上最灵活的引脚,其核心能力在于通过软件配置实现输入、输出、中断等多种功能。理解以下硬件特性是驱动开发的前提:

特性 说明 驱动关注点
方向控制 寄存器位控制引脚为输入或输出 配置前需确保引脚功能复用正确
上拉/下拉 内部弱上拉/下拉电阻,防止浮空 输入模式时需明确上拉/下拉状态
驱动能力 输出电流能力(2mA/4mA/8mA 等) 驱动 LED 或外设时需匹配电流
施密特触发 输入信号去抖动,提高噪声容限 中断场景建议开启,减少误触发
复用功能 引脚可切换为 SPI/I2C/UART 等外设功能 GPIO 模式需先解除复用绑定
实战提示:在嵌入式 Linux 中,GPIO 的电气配置(上拉/下拉、驱动强度)通常由 Bootloader 或 pinctrl 子系统在设备树中完成,驱动层通过 pinconf 框架读取或覆盖这些配置。
GPIO 引脚内部硬件结构 GPIO 引脚内部硬件结构 引脚 Pad 输出驱动器 施密特触发器 上拉/下拉电阻 中断检测 软件通过寄存器选择路径:输出 / 输入 / 复用 / 中断

1.2 GPIO 控制器寄存器模型:以 IMX6ULL 为例

不同 SoC 的 GPIO 控制器寄存器布局差异较大,但核心寄存器组遵循相似逻辑。以 NXP i.MX6ULL 为例,其 GPIO 控制器包含以下关键寄存器:

寄存器 偏移 位宽 功能描述
DR 0x000 32 数据寄存器:读引脚电平,写输出值
GDIR 0x004 32 方向寄存器:0=输入,1=输出
PSR 0x008 32 引脚状态寄存器:只读,反映实际电平
ICR1 0x00C 32 中断控制寄存器 1:配置低电平/高电平触发
ICR2 0x010 32 中断控制寄存器 2:配置上升沿/下降沿触发
IMR 0x014 32 中断掩码寄存器:使能/禁止中断
ISR 0x018 32 中断状态寄存器:写 1 清除中断标志
寄存器操作要点:
  • 修改 GDIR 前必须确保引脚不在复用状态(由 IOMUXC 控制)。
  • 中断触发配置需同时设置 ICR1 和 ICR2,两者组合决定触发方式。
  • ISR 寄存器在中断服务程序中必须写 1 清标志,否则无法响应下一次中断。
GPIO 控制器寄存器模型 GPIO 控制器寄存器模型(IMX6ULL 风格) DR (数据寄存器) GDIR (方向寄存器) PSR (状态寄存器) ICR1/ICR2 (中断控制) IMR (中断掩码) ISR (中断状态) 寄存器操作流程 1. 配置 IOMUXC 选择 GPIO 功能 2. 设置 GDIR 确定方向 3. 输出:写 DR;输入:读 PSR 4. 中断:配置 ICR1/ICR2 触发方式 5. 设置 IMR 使能中断 6. 中断触发后读 ISR 判断来源 7. 处理完后写 1 清 ISR 8. 退出中断服务程序

1.3 Linux GPIO 子系统软件架构

Linux 内核 GPIO 子系统采用分层设计,从底向上分为 GPIO 控制器驱动、GPIO 核心层、GPIO 消费层 三层。这种架构隔离了硬件差异,为上层提供了统一的 API。

Linux GPIO 子系统软件架构 Linux GPIO 子系统软件架构 GPIO 消费层 用户空间 (sysfs / libgpiod) | 内核驱动 (LED/按键/GPIO 键盘) GPIO 核心层 gpiolib:gpiochip_add / gpio_request / gpiod_get / 中断映射 GPIO 控制器驱动层 chip->request / chip->get_direction / chip->set / chip->get / chip->irq_handler 硬件寄存器层 核心层提供 gpiod_* API,控制器驱动实现 struct gpio_chip 回调

1.4 核心数据结构:struct gpio_chip

每个 GPIO 控制器驱动都需要填充 struct gpio_chip,这是 gpiolib 与硬件交互的桥梁。关键成员如下:

struct gpio_chip {
    const char              *label;
    struct device           *parent;
    struct module           *owner;

    int                     base;           // 全局 GPIO 号起始值
    u16                     ngpio;          // 本控制器管理的引脚数

    // 方向控制
    int     (*direction_input)(struct gpio_chip *chip, unsigned offset);
    int     (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);

    // 读写电平
    int     (*get)(struct gpio_chip *chip, unsigned offset);
    void    (*set)(struct gpio_chip *chip, unsigned offset, int value);

    // 中断相关
    int     (*to_irq)(struct gpio_chip *chip, unsigned offset);
    void    (*irq_handler)(struct gpio_chip *chip, unsigned offset);

    // 调试与电源管理
    int     (*get_direction)(struct gpio_chip *chip, unsigned offset);
    int     (*init_valid_mask)(struct gpio_chip *chip,
                               unsigned long *valid_mask, unsigned ngpios);
};
常见陷阱:在实现 direction_output 回调时,必须同时设置输出值和方向寄存器,否则可能出现引脚电平不确定的毛刺。推荐先写 DR 再写 GDIR。

1.5 设备树中的 GPIO 描述

设备树是 GPIO 资源描述的标准方式。以下是一个典型节点示例:

gpio1: gpio@2090000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x0209c000 0x4000>;
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

/* 消费端示例:按键使用 GPIO1_IO03,上升沿触发中断 */
key-gpio {
    compatible = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    key-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
    interrupts-extended = <&gpio1 3 IRQ_TYPE_EDGE_RISING>;
};
解析要点:
  • #gpio-cells = <2> 表示每个 GPIO 描述需要 2 个 cell:引脚号和标志位。
  • GPIO_ACTIVE_LOW 表示低电平有效,驱动层会自动反转逻辑。
  • 中断触发类型在 interrupts-extended 中指定,与 GPIO 方向无关。

1.6 本章小结