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 属性由子系统自动解析并映射到对应的控制器。
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 的读写与方向控制:
常用操作 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 从设备地址。
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()),这些资源会在驱动卸载时自动释放,避免遗漏清理操作。上电时序的延时参数应严格遵循传感器数据手册,过短可能导致传感器初始化失败,过长则影响系统启动速度。