3. Sensor驱动框架:V4L2 Subdev注册、s_power/s_stream回调、控制集实现
本章是Camera驱动实战的核心环节。Sensor驱动作为V4L2框架中的子设备(Subdev),负责与硬件寄存器直接交互,完成初始化、上下电、流开关以及曝光/增益等控制。理解Subdev的注册流程、回调机制和控制集实现,是采集高质量raw图的前提。
3.1 V4L2 Subdev驱动模型
在Linux内核中,Camera Sensor被抽象为一个V4L2子设备(Subdev),挂载在I2C总线上。Subdev通过v4l2_subdev结构体描述,并注册到V4L2框架中,供主设备(如CSI控制器)调用。
核心数据结构:
struct v4l2_subdev — 子设备核心,包含ops、ctrl_handler、priv等。struct v4l2_subdev_ops — 回调函数集,包含core、video、pad等操作组。struct v4l2_ctrl_handler — 控制句柄,管理曝光、增益等用户控制项。
调试要点:注册Subdev前,务必确保I2C设备地址正确、通信正常。使用
i2cdetect工具验证Sensor是否在总线上应答。
3.2 Subdev注册流程
Sensor驱动的入口通常是I2C驱动的probe函数。在probe中完成Subdev的初始化、注册以及控制句柄的创建。标准注册步骤如下:
- 分配并初始化v4l2_subdev:使用
v4l2_i2c_subdev_init()或手动填充。 - 设置Subdev ops:绑定
core->s_power、video->s_stream等回调。 - 创建控制句柄:调用
v4l2_ctrl_handler_init(),添加曝光、增益等控制项。 - 注册Subdev:调用
v4l2_async_register_subdev(),等待主设备绑定。
常见错误:未调用
v4l2_async_register_subdev()导致主设备无法发现Sensor;控制句柄未关联到Subdev导致用户空间无法调节参数。
以下是一个典型的probe函数代码片段:
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct v4l2_subdev *sd;
struct sensor_priv *priv;
int ret;
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
sd = &priv->sd;
v4l2_i2c_subdev_init(sd, client, &sensor_subdev_ops);
/* 初始化控制句柄 */
v4l2_ctrl_handler_init(&priv->ctrl_handler, 2);
priv->exposure = v4l2_ctrl_new_std(&priv->ctrl_handler,
&sensor_ctrl_ops,
V4L2_CID_EXPOSURE_ABSOLUTE,
0, 0xffff, 1, 0x100);
priv->gain = v4l2_ctrl_new_std(&priv->ctrl_handler,
&sensor_ctrl_ops,
V4L2_CID_GAIN,
0, 0xff, 1, 0x40);
sd->ctrl_handler = &priv->ctrl_handler;
if (priv->ctrl_handler.error) {
ret = priv->ctrl_handler.error;
goto err_free_handler;
}
/* 注册异步子设备 */
ret = v4l2_async_register_subdev(sd);
if (ret)
goto err_free_handler;
return 0;
err_free_handler:
v4l2_ctrl_handler_free(&priv->ctrl_handler);
return ret;
}
3.3 s_power / s_stream 回调实现
s_power和s_stream是Sensor驱动中最关键的两个回调,分别控制Sensor的上下电和数据流启停。
| 回调 | 触发时机 | 典型操作 | 调试关注点 |
|---|---|---|---|
| s_power(on) | 主设备打开视频节点时 | 使能时钟、拉高复位、配置PLL、初始化寄存器序列 | 电源时序、时钟稳定时间、I2C通信是否正常 |
| s_power(off) | 主设备关闭视频节点时 | 关闭时钟、拉低复位、进入低功耗模式 | 是否完全断电、漏电电流 |
| s_stream(on) | 用户调用VIDIOC_STREAMON时 | 配置输出格式、使能MIPI/LVDS输出、开始帧同步 | MIPI CSI时钟、数据通道极性、帧同步信号 |
| s_stream(off) | 用户调用VIDIOC_STREAMOFF时 | 停止输出、关闭MIPI通道 | 停止后是否残留数据、总线状态 |
实战经验:在s_power(on)中,建议加入
msleep(10)等待Sensor内部PLL稳定;在s_stream(on)中,先读取Sensor的ID寄存器确认通信正常,再配置输出。
3.4 控制集实现:Exposure / Gain
曝光(Exposure)和增益(Gain)是Camera最核心的两个控制项。在V4L2框架中,它们通过v4l2_ctrl实现,用户空间通过VIDIOC_S_CTRL或VIDIOC_S_EXT_CTRLS调节。
3.4.1 控制项定义
在probe中,使用v4l2_ctrl_new_std()创建标准控制项。曝光通常对应V4L2_CID_EXPOSURE_ABSOLUTE,增益对应V4L2_CID_GAIN。需要定义控制操作集v4l2_ctrl_ops中的s_ctrl回调:
static int sensor_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct sensor_priv *priv = container_of(ctrl->handler,
struct sensor_priv, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_EXPOSURE_ABSOLUTE:
/* 写入曝光寄存器 */
sensor_write(priv, REG_EXPOSURE, ctrl->val);
break;
case V4L2_CID_GAIN:
/* 写入增益寄存器 */
sensor_write(priv, REG_GAIN, ctrl->val);
break;
default:
return -EINVAL;
}
return 0;
}
static const struct v4l2_ctrl_ops sensor_ctrl_ops = {
.s_ctrl = sensor_s_ctrl,
};
调试技巧:在s_ctrl中加入
dev_dbg()打印控制ID和值,配合v4l2-ctl -c exposure=100验证控制通路是否正常。
3.4.2 控制集与硬件寄存器映射
不同Sensor的曝光和增益寄存器布局差异很大。以下是一个典型的映射关系表:
| 控制项 | 寄存器地址 | 位宽 | 单位/含义 | 典型范围 |
|---|---|---|---|---|
| Exposure | 0x3500 (高字节) / 0x3501 (低字节) | 16 bit | 行数(Line Count) | 1 ~ 0xFFFF |
| Gain (Analog) | 0x3508 | 8 bit | 模拟增益倍数 | 0x00 ~ 0xFF |
| Gain (Digital) | 0x350A | 8 bit | 数字增益倍数 | 0x00 ~ 0xFF |
注意:曝光和增益的调节顺序很重要。通常先调曝光(增加亮度),再调增益(放大信号)。增益过高会引入噪声,建议限制最大增益值。
3.5 调试与验证
完成Subdev注册和回调实现后,通过以下步骤验证驱动正确性:
- 检查dmesg:确认probe成功,Subdev注册信息无错误。
- 使用media-ctl:查看拓扑结构,确认Sensor Subdev与CSI主设备连接正确。
- 使用v4l2-ctl:列出控制项,调节曝光/增益,观察返回值。
- 抓取raw图:使用
v4l2-ctl --stream-mmap --stream-to=raw.raw采集一帧raw数据,检查图像亮度是否随控制值变化。
实战命令示例:
v4l2-ctl -d /dev/v4l-subdev2 -l # 列出所有控制项v4l2-ctl -d /dev/v4l-subdev2 -c exposure=200 # 设置曝光值v4l2-ctl -d /dev/v4l-subdev2 -C exposure # 读取当前曝光值