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 框架读取或覆盖这些配置。
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 清标志,否则无法响应下一次中断。
1.3 Linux GPIO 子系统软件架构
Linux 内核 GPIO 子系统采用分层设计,从底向上分为 GPIO 控制器驱动、GPIO 核心层、GPIO 消费层 三层。这种架构隔离了硬件差异,为上层提供了统一的 API。
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 方向无关。