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是否在总线上应答。
V4L2 Subdev 注册与回调关系图 V4L2 Subdev 注册与回调关系 主设备 (CSI/ISP) Subdev (Sensor) I2C 总线 v4l2_ctrl_handler s_power / s_stream 说明:主设备通过Subdev ops调用s_power/s_stream;控制句柄管理曝光/增益等V4L2控制项。 I2C总线用于实际读写Sensor寄存器。

3.2 Subdev注册流程

Sensor驱动的入口通常是I2C驱动的probe函数。在probe中完成Subdev的初始化、注册以及控制句柄的创建。标准注册步骤如下:

  1. 分配并初始化v4l2_subdev:使用v4l2_i2c_subdev_init()或手动填充。
  2. 设置Subdev ops:绑定core->s_power、video->s_stream等回调。
  3. 创建控制句柄:调用v4l2_ctrl_handler_init(),添加曝光、增益等控制项。
  4. 注册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寄存器确认通信正常,再配置输出。
s_power / s_stream 状态机 s_power / s_stream 状态机 Power Off Power On Stream On s_power(1) s_stream(1) s_stream(0) 回到Power On s_power(0) 说明:s_power控制Sensor的电源状态,s_stream控制数据流输出。 必须先s_power(1)才能s_stream(1);停止流后可以s_power(0)断电。 调试时使用示波器测量MIPI时钟和数据通道,确认流开关时序。

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 # 读取当前曝光值
控制集调试流程 控制集调试流程 v4l2-ctl -l 设置曝光/增益 读取寄存器值 采集raw图验证 调试步骤: 1. 使用v4l2-ctl -l确认控制项列表,检查曝光/增益是否出现。 2. 设置曝光值后,通过I2C工具读取Sensor寄存器,验证写入值。 3. 采集raw图,用工具(如raw2rgbpnm)查看亮度变化,确认控制生效。

3.6 本章小结