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结构体暴露操作函数集,由媒体控制器协调流水线。
V4L2子设备驱动模型架构 用户空间应用程序 / Camera HAL V4L2核心框架 / Media Controller Sensor Subdev v4l2_subdev_ops ISP Subdev v4l2_subdev_ops EIS Subdev v4l2_subdev_ops 硬件抽象层:I2C / SPI / MIPI CSI 总线 Camera Sensor ISP硬件 EIS硬件 说明:每个子设备通过 v4l2_subdev_ops 注册回调函数集,由 V4L2 核心统一调度

3.2 子设备注册与操作集绑定

Sensor驱动的入口函数中,需要完成以下关键步骤:

  1. 分配并初始化 v4l2_subdev 结构体
  2. 绑定 v4l2_subdev_ops 操作函数集
  3. 注册子设备到 V4L2 核心
  4. 关联媒体控制器 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启动过程中,三个回调函数的典型调用顺序如下:

Camera启动流程中回调函数调用顺序 1. s_power(on) 2. s_fmt(set_fmt) 3. s_parm(set_fps) 4. Stream On (DMA) 说明:关闭流程为逆序:Stream Off → s_parm(默认) → s_power(off) s_fmt 通常在 s_power(on) 之后立即调用,确保寄存器已可访问

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启动延迟或帧率抖动。