3. Sensor驱动框架:V4L2子设备驱动模型与核心回调实现
在骁龙平台Camera系统中,Sensor驱动遵循Linux内核的V4L2(Video for Linux 2)框架,并以子设备(subdev)的形式挂载到媒体控制器(Media Controller)拓扑中。本章深入剖析V4L2子设备驱动模型,并详细讲解s_power、s_parm、s_fmt三个核心回调函数的实现原理与编码规范。
3.1 V4L2子设备驱动模型概述
V4L2子设备模型将Camera Sensor、ISP、EIS等硬件模块抽象为独立的子设备节点,通过媒体控制器框架实现数据流与控制流的统一管理。Sensor驱动作为最底层的子设备,负责与硬件寄存器交互,向上层提供标准化的控制接口。
核心设计思想: 子设备模型实现了“控制分离、数据聚合”。每个硬件模块独立注册,通过
v4l2_subdev_ops结构体暴露操作函数集,由媒体控制器协调流水线。
3.2 子设备注册与操作集绑定
Sensor驱动的入口函数中,需要完成以下关键步骤:
- 分配并初始化
v4l2_subdev结构体 - 绑定
v4l2_subdev_ops操作函数集 - 注册子设备到 V4L2 核心
- 关联媒体控制器 pad 与 link
实战要点: 在骁龙平台上,Sensor子设备通常通过
v4l2_i2c_subdev_init() 或 v4l2_spi_subdev_init() 完成初始化,并调用 media_entity_pads_init() 初始化媒体实体 pad。
// Sensor驱动 probe 函数核心片段
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct sensor_dev *sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
// 1. 初始化 v4l2_subdev
v4l2_i2c_subdev_init(&sensor->sd, client, &sensor_subdev_ops);
// 2. 设置子设备内部操作
sensor->sd.internal_ops = &sensor_internal_ops;
// 3. 初始化媒体实体 pad
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
// 4. 注册子设备
ret = v4l2_async_register_subdev(&sensor->sd);
if (ret < 0) {
dev_err(&client->dev, "Failed to register subdev\n");
return ret;
}
dev_info(&client->dev, "Sensor subdev registered successfully\n");
return 0;
}
3.3 核心回调函数详解
v4l2_subdev_core_ops 和 v4l2_subdev_video_ops 中定义了Sensor驱动最常用的三个回调:s_power、s_parm、s_fmt。下表对比了它们的功能与调用时机:
| 回调函数 | 所属操作集 | 功能描述 | 典型调用时机 |
|---|---|---|---|
s_power |
core_ops | 控制Sensor电源状态(上电/下电) | Stream on/off、系统休眠唤醒 |
s_parm |
video_ops | 设置帧率、帧尺寸等流参数 | 预览/录像模式切换、帧率调整 |
s_fmt |
pad_ops | 配置输出格式(分辨率、像素编码) | 初始化、分辨率切换 |
3.3.1 s_power 回调实现
s_power 回调负责Sensor的电源管理,包括GPIO控制、时钟使能、I2C寄存器初始化序列。实现时必须考虑引用计数,避免重复上电/下电。
static int sensor_s_power(struct v4l2_subdev *sd, int on)
{
struct sensor_dev *sensor = to_sensor_dev(sd);
int ret = 0;
mutex_lock(&sensor->lock);
if (on && !sensor->power_count) {
// 上电序列:GPIO、时钟、复位
gpiod_set_value(sensor->reset_gpio, 1);
usleep_range(1000, 2000);
gpiod_set_value(sensor->power_gpio, 1);
usleep_range(5000, 6000);
// 初始化寄存器序列
ret = sensor_write_regs(sensor, sensor_init_regs);
if (ret < 0) {
dev_err(sd->dev, "Failed to init sensor registers\n");
goto err_power_off;
}
} else if (!on && sensor->power_count == 1) {
// 下电序列
gpiod_set_value(sensor->power_gpio, 0);
gpiod_set_value(sensor->reset_gpio, 0);
}
sensor->power_count += on ? 1 : -1;
mutex_unlock(&sensor->lock);
return 0;
err_power_off:
gpiod_set_value(sensor->power_gpio, 0);
gpiod_set_value(sensor->reset_gpio, 0);
mutex_unlock(&sensor->lock);
return ret;
}
避坑指南: 骁龙平台Sensor驱动中,
s_power 可能被多次调用(如HAL层和ISP驱动同时请求)。务必实现引用计数保护,否则会导致电源状态混乱,引发系统挂起或Sensor无响应。
3.3.2 s_parm 回调实现
s_parm 用于设置Sensor的帧率(FPS)和帧尺寸。参数通过 v4l2_streamparm 结构体传递,驱动需要解析并配置对应的PLL分频系数和行/帧长寄存器。
static int sensor_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parm)
{
struct sensor_dev *sensor = to_sensor_dev(sd);
struct v4l2_captureparm *cp = &parm->parm.capture;
u32 fps_num, fps_den;
u32 frame_length, line_length;
int ret;
if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
// 提取帧率
fps_num = cp->timeperframe.denominator;
fps_den = cp->timeperframe.numerator;
if (fps_num == 0 || fps_den == 0)
return -EINVAL;
// 计算帧长:frame_length = pixel_clock / (line_length * fps)
line_length = sensor->current_mode->line_length;
frame_length = sensor->pixel_clock / (line_length * fps_num / fps_den);
// 写入寄存器
ret = sensor_write_reg(sensor, REG_FRAME_LENGTH_H, (frame_length >> 8) & 0xFF);
ret |= sensor_write_reg(sensor, REG_FRAME_LENGTH_L, frame_length & 0xFF);
if (ret < 0) {
dev_err(sd->dev, "Failed to set frame length\n");
return ret;
}
// 更新当前帧率
sensor->current_fps = fps_num / fps_den;
cp->capability = V4L2_CAP_TIMEPERFRAME;
return 0;
}
3.3.3 s_fmt 回调实现
s_fmt 回调通过 v4l2_subdev_pad_ops 中的 set_fmt 实现,用于配置Sensor输出的图像格式(分辨率、像素格式)。驱动需要验证格式是否支持,并更新内部状态。
static int sensor_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt)
{
struct sensor_dev *sensor = to_sensor_dev(sd);
struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;
const struct sensor_mode *mode;
int ret;
// 查找匹配的Sensor模式
mode = sensor_find_mode(sensor, mbus_fmt->width, mbus_fmt->height);
if (!mode) {
// 如果不匹配,使用默认模式
mode = &sensor->supported_modes[0];
mbus_fmt->width = mode->width;
mbus_fmt->height = mode->height;
}
// 设置像素编码(通常为 RAW10/RAW12)
mbus_fmt->code = mode->mbus_code;
mbus_fmt->field = V4L2_FIELD_NONE;
mbus_fmt->colorspace = V4L2_COLORSPACE_RAW;
// 如果是主动设置(不是查询),则更新硬件
if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
ret = sensor_apply_mode(sensor, mode);
if (ret < 0) {
dev_err(sd->dev, "Failed to apply sensor mode\n");
return ret;
}
sensor->current_mode = mode;
}
return 0;
}
3.4 回调函数调用流程
在Camera启动过程中,三个回调函数的典型调用顺序如下:
3.5 操作函数集注册示例
完整的 v4l2_subdev_ops 定义如下,展示了如何将上述回调函数绑定到对应的操作集:
// 核心操作集
static const struct v4l2_subdev_core_ops sensor_core_ops = {
.s_power = sensor_s_power,
.ioctl = sensor_ioctl,
};
// 视频操作集
static const struct v4l2_subdev_video_ops sensor_video_ops = {
.s_parm = sensor_s_parm,
.g_parm = sensor_g_parm,
.s_stream = sensor_s_stream,
};
// Pad操作集
static const struct v4l2_subdev_pad_ops sensor_pad_ops = {
.enum_mbus_code = sensor_enum_mbus_code,
.enum_frame_size = sensor_enum_frame_size,
.set_fmt = sensor_set_fmt,
.get_fmt = sensor_get_fmt,
};
// 总操作集
static const struct v4l2_subdev_ops sensor_subdev_ops = {
.core = &sensor_core_ops,
.video = &sensor_video_ops,
.pad = &sensor_pad_ops,
};
3.6 常见问题与调试技巧
- s_power 调用顺序错误: 确保在调用
s_fmt或s_parm之前已经完成上电,否则I2C通信会失败。 - 帧率计算溢出: 骁龙平台Sensor的PLL配置复杂,计算帧长时注意32位整数溢出,建议使用
do_div()宏。 - 格式协商失败:
set_fmt中如果返回不支持的格式,上层会回退到默认格式。务必实现enum_frame_size和enum_mbus_code枚举回调。 - 调试手段: 在回调函数入口和出口添加
dev_dbg打印,配合trace-cmd跟踪调用时序。
性能指标参考: 在骁龙8 Gen2平台上,Sensor驱动
s_power 回调执行时间应控制在 5ms 以内(含GPIO操作和寄存器初始化),s_fmt 和 s_parm 应在 1ms 内完成。超过此阈值可能导致Camera启动延迟或帧率抖动。