4. GPIO 子系统简介:GPIO 控制器、GPIO 描述符、GPIO 操作 API、GPIO 在摄像头控制中的应用

在摄像头驱动开发中,GPIO(General Purpose Input/Output)承担着电源使能、复位控制、中断检测等关键角色。Linux GPIO 子系统为驱动开发者提供了一套统一的抽象层,屏蔽了不同 SoC 的寄存器差异。本章将深入剖析 GPIO 子系统的核心概念、数据结构与操作接口,并结合摄像头场景讲解典型应用模式。

4.1 GPIO 子系统层次结构

Linux GPIO 子系统采用分层设计,从上到下依次为:GPIO 操作 API(供驱动调用)、GPIO 描述符管理层(抽象 GPIO 编号与属性)、GPIO 控制器驱动层(硬件寄存器操作)。

核心设计思想: 驱动开发者通过描述符(Descriptor)而非整数编号操作 GPIO,实现与硬件平台的解耦。设备树中定义的 GPIO 属性由子系统自动解析并映射到对应的控制器。
GPIO 子系统层次架构 驱动调用层(GPIO 操作 API) gpiod_get_value / gpiod_set_value / gpiod_direction_output GPIO 描述符管理层 struct gpio_desc / gpiod_* 系列函数 / 设备树映射 GPIO 控制器驱动层 struct gpio_chip / .request / .free / .set / .get / .direction_input / .direction_output 硬件寄存器层(SoC GPIO Bank) 设备树 gpios = <&gpio1 15 0>

4.2 GPIO 控制器:struct gpio_chip

每个 GPIO 控制器(通常对应 SoC 的一个 GPIO Bank)在内核中由 struct gpio_chip 表示。该结构体包含了控制器的属性、操作函数指针以及私有数据。

成员 类型 说明
label const char * 控制器名称,如 "GPIO1"
ngpio int 该控制器管理的 GPIO 数量
base int 全局 GPIO 编号起始值(已废弃,建议使用动态分配)
parent struct device * 父设备,用于电源管理和 sysfs
request int (*)(struct gpio_chip *, unsigned offset) 请求 GPIO 时的回调
free void (*)(struct gpio_chip *, unsigned offset) 释放 GPIO 时的回调
direction_input int (*)(struct gpio_chip *, unsigned offset) 设置 GPIO 为输入方向
direction_output int (*)(struct gpio_chip *, unsigned offset, int value) 设置 GPIO 为输出方向并设置初始电平
get int (*)(struct gpio_chip *, unsigned offset) 读取 GPIO 电平值
set void (*)(struct gpio_chip *, unsigned offset, int value) 设置 GPIO 输出电平
驱动开发提示: 在编写 GPIO 控制器驱动时,必须实现 .direction_input、.direction_output、.get 和 .set 四个核心回调。.request 和 .free 为可选,常用于电源管理或引脚复用配置。

4.3 GPIO 描述符:struct gpio_desc

GPIO 描述符是内核中操作 GPIO 的核心句柄。相比传统的整数编号方式,描述符模式具有以下优势:

  • 类型安全: 编译器可检查类型匹配,避免整数误用。
  • 引用计数: 自动管理 GPIO 的申请与释放。
  • 设备树集成: 通过 devm_gpiod_get() 系列函数自动解析设备树属性。
  • 活跃电平抽象: 支持 GPIO_ACTIVE_LOW 标志,驱动无需手动取反。

描述符的获取通常通过以下 API 完成:

#include <linux/gpio/consumer.h>

// 通过设备树属性名获取 GPIO 描述符
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                            enum gpiod_flags flags);

// 带资源管理的版本,驱动卸载时自动释放
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
                                 enum gpiod_flags flags);

// 获取可选的 GPIO(不存在时返回 NULL 而非错误)
struct gpio_desc *devm_gpiod_get_optional(struct device *dev,
                                          const char *con_id,
                                          enum gpiod_flags flags);

flags 参数用于指定初始状态,常用值包括:

标志 含义
GPIOD_ASIS 保持当前状态,不改变方向
GPIOD_IN 设置为输入方向
GPIOD_OUT_LOW 设置为输出方向,初始输出低电平
GPIOD_OUT_HIGH 设置为输出方向,初始输出高电平

4.4 GPIO 操作 API 详解

获取描述符后,驱动通过以下 API 完成 GPIO 的读写与方向控制:

GPIO 描述符操作 API 调用流程 devm_gpiod_get() 获取 GPIO 描述符 gpiod_direction_output() 设置方向与初始电平 gpiod_set_value() 设置输出电平 gpiod_get_value() 读取输入电平 gpiod_put() 释放 GPIO 描述符 devm_ 版本自动释放 无需手动调用 gpiod_put

常用操作 API 汇总:

函数 原型 说明
gpiod_get_value int gpiod_get_value(const struct gpio_desc *desc) 读取 GPIO 电平,返回 0 或 1(已处理活跃电平反转)
gpiod_set_value void gpiod_set_value(struct gpio_desc *desc, int value) 设置 GPIO 输出电平(已处理活跃电平反转)
gpiod_direction_input int gpiod_direction_input(struct gpio_desc *desc) 将 GPIO 设置为输入方向
gpiod_direction_output int gpiod_direction_output(struct gpio_desc *desc, int value) 将 GPIO 设置为输出方向并设置初始值
gpiod_is_active_low int gpiod_is_active_low(const struct gpio_desc *desc) 判断 GPIO 是否配置为活跃低电平
gpiod_to_irq int gpiod_to_irq(const struct gpio_desc *desc) 获取 GPIO 对应的中断号,用于注册中断处理
注意事项: 在摄像头驱动中,电源使能 GPIO 通常使用 GPIOD_OUT_LOW 或 GPIOD_OUT_HIGH 标志在获取时直接初始化。复位 GPIO 则先设置为输出低电平,保持一段时间后再拉高完成复位时序。中断检测 GPIO 应使用 GPIOD_IN 标志,并通过 gpiod_to_irq() 获取中断号。

4.5 GPIO 在摄像头控制中的应用

摄像头传感器通常需要以下 GPIO 控制信号:

  • 电源使能(PWDN / POWER_EN): 控制摄像头模组的电源开关,通常高电平使能。
  • 复位(RESET): 用于初始化传感器内部状态,通常低电平复位,拉高后进入正常工作模式。
  • 中断(INT / VSYNC): 传感器输出帧同步信号或事件中断,连接到 SoC 的 GPIO 中断引脚。
  • I2C 地址选择(ADDR_SEL): 部分传感器通过 GPIO 电平选择 I2C 从设备地址。
摄像头传感器 GPIO 控制连接图 SoC 主控 GPIO1_15 (POWER_EN) GPIO1_16 (RESET) GPIO1_17 (INT) I2C1_SCL/SDA 摄像头传感器 OV5640 / IMX219 PWDN (电源使能) RESET (复位) INT (中断输出) I2C 接口 POWER RST IRQ I2C 典型上电时序 1. 拉高 POWER_EN 2. 延时 1-5ms 3. 拉高 RESET

4.6 摄像头驱动 GPIO 操作示例

以下代码展示了在摄像头驱动中如何使用 GPIO 描述符 API 完成电源使能与复位操作:

#include <linux/gpio/consumer.h>
#include <linux/delay.h>

struct camera_device {
    struct device *dev;
    struct gpio_desc *power_gpio;   // 电源使能 GPIO
    struct gpio_desc *reset_gpio;   // 复位 GPIO
    struct gpio_desc *irq_gpio;     // 中断 GPIO
    int irq;
};

static int camera_power_on(struct camera_device *cam)
{
    int ret;

    // 1. 使能电源:拉高 POWER_EN
    gpiod_set_value(cam->power_gpio, 1);
    
    // 2. 等待电源稳定(传感器数据手册要求)
    msleep(5);
    
    // 3. 复位传感器:先拉低复位引脚,再拉高
    gpiod_set_value(cam->reset_gpio, 0);
    usleep_range(1000, 2000);  // 至少 1ms 低电平
    gpiod_set_value(cam->reset_gpio, 1);
    
    // 4. 等待传感器初始化完成
    msleep(20);
    
    return 0;
}

static int camera_probe(struct platform_device *pdev)
{
    struct camera_device *cam;
    int ret;

    cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL);
    if (!cam)
        return -ENOMEM;

    cam->dev = &pdev->dev;

    // 获取电源使能 GPIO,初始输出低电平
    cam->power_gpio = devm_gpiod_get(&pdev->dev, "power", GPIOD_OUT_LOW);
    if (IS_ERR(cam->power_gpio))
        return dev_err_probe(&pdev->dev, PTR_ERR(cam->power_gpio),
                             "Failed to get power GPIO\n");

    // 获取复位 GPIO,初始输出低电平(复位状态)
    cam->reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
    if (IS_ERR(cam->reset_gpio))
        return dev_err_probe(&pdev->dev, PTR_ERR(cam->reset_gpio),
                             "Failed to get reset GPIO\n");

    // 获取中断 GPIO,设置为输入
    cam->irq_gpio = devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN);
    if (IS_ERR(cam->irq_gpio))
        return dev_err_probe(&pdev->dev, PTR_ERR(cam->irq_gpio),
                             "Failed to get irq GPIO\n");

    // 获取中断号
    cam->irq = gpiod_to_irq(cam->irq_gpio);
    if (cam->irq < 0)
        return dev_err_probe(&pdev->dev, cam->irq,
                             "Failed to get IRQ number\n");

    // 执行上电序列
    ret = camera_power_on(cam);
    if (ret)
        return ret;

    // 注册中断处理函数
    ret = devm_request_threaded_irq(&pdev->dev, cam->irq, NULL,
                                    camera_irq_handler,
                                    IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                                    "camera", cam);
    if (ret)
        return dev_err_probe(&pdev->dev, ret,
                             "Failed to request IRQ\n");

    // ... 初始化 I2C 通信、配置传感器寄存器等

    return 0;
}
设备树节点对应关系: 上述代码中的 "power"、"reset"、"irq" 字符串对应设备树中 gpios 属性的 con-id 后缀。例如:power-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;。

4.7 GPIO 与 Regulator 的协同

在摄像头驱动中,GPIO 和 Regulator 经常配合使用:

  • Regulator 控制主电源: 通过 regulator_enable() 为摄像头模组提供稳定的核心电压(如 1.8V、2.8V)。
  • GPIO 控制使能信号: 在 Regulator 输出稳定后,通过 GPIO 拉高 PWDN 引脚完成模组上电。
  • 复位时序: 电源稳定后,通过 GPIO 控制 RESET 引脚完成传感器初始化。
控制对象 使用子系统 典型操作
核心电压(VDD_CORE) Regulator regulator_enable() / regulator_disable()
IO 电压(VDD_IO) Regulator regulator_set_voltage() + regulator_enable()
电源使能(PWDN) GPIO gpiod_set_value(power_gpio, 1)
复位(RESET) GPIO 先拉低再拉高,中间延时
帧同步中断 GPIO + IRQ gpiod_to_irq() + request_threaded_irq()
最佳实践: 推荐使用 devm_ 系列 API(如 devm_gpiod_get()、devm_regulator_get()),这些资源会在驱动卸载时自动释放,避免遗漏清理操作。上电时序的延时参数应严格遵循传感器数据手册,过短可能导致传感器初始化失败,过长则影响系统启动速度。