3. Linux内核Camera驱动框架:V4L2框架解析、Sensor驱动注册流程、DTS设备树配置

好,咱们进入第三章。这一章可以说是整个摄像头驱动开发的基石。你想想看,一个摄像头模组焊在主板上,Linux内核怎么知道它存在?怎么跟它通信?怎么把图像数据拿上来?这一切的背后,就是V4L2框架在调度。

我个人习惯把V4L2理解成「摄像头领域的字符设备驱动规范」。它定义了一套标准接口,让上层应用(比如相机App)不用关心底层是OV的Sensor还是索尼的Sensor。说白了,就是解耦。

3.1 V4L2框架整体脉络

V4L2的全称是Video for Linux 2。它不是一个驱动,而是一套驱动框架。在MTK平台上,这套框架被分成了三层:

  • V4L2核心层:内核自带,提供设备节点(/dev/videoX)、ioctl标准接口。
  • V4L2子设备层:把Sensor、Flash、Lens等抽象成subdev,方便管理。
  • 平台驱动层:MTK自己实现的ISP驱动、MIPI接收驱动等。

我在项目中遇到过一个问题:上层应用打开/dev/video0后,调用VIDIOC_QUERYCAP一直返回失败。查了半天,发现是V4L2核心层注册时,设备能力集(capabilities)没有正确设置。嗯,这里要注意,V4L2框架对设备能力检查非常严格,少一个flag都不行。

核心数据结构

  • struct video_device:代表一个视频设备节点。
  • struct v4l2_device:V4L2顶层设备,管理所有子设备。
  • struct v4l2_subdev:子设备抽象,Sensor、Flash都属于它。
  • struct vb2_queue:视频缓冲队列,负责数据搬运。

3.2 Sensor驱动注册流程

Sensor驱动是咱们最常写的。一个典型的Sensor驱动注册流程,我总结为五步走:

  1. 匹配设备树:驱动通过compatible属性找到对应的DTS节点。
  2. 初始化I2C/SPI通信:读取Sensor ID,确认硬件存在。
  3. 注册V4L2子设备:调用v4l2_i2c_subdev_init()v4l2_spi_subdev_init()
  4. 实现子设备操作函数:包括s_power(电源管理)、s_stream(开启/关闭流)、enum_mbus_code(枚举格式)等。
  5. 注册中断或DMA:如果Sensor支持VSYNC中断,需要注册中断处理函数。

我曾经踩过一个坑:Sensor的ID读取成功了,但注册子设备时一直报错。后来发现是v4l2_subdev_init()里没有设置owner字段。内核会检查这个字段,如果为NULL,直接拒绝注册。所以,写驱动时记得加上THIS_MODULE

避坑指南:我曾经在MT8678上调试一个GC5035 Sensor,发现注册成功后,上层应用无法设置分辨率。查了三天,最后发现是enum_mbus_code回调函数里返回的格式列表顺序不对。V4L2要求第一个格式必须是默认格式,否则上层会直接跳过。

下面是一个简化的Sensor驱动注册代码示例:

static int my_sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct v4l2_subdev *sd;
    int ret;

    // 1. 分配子设备结构体
    sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL);
    if (!sd)
        return -ENOMEM;

    // 2. 初始化子设备
    v4l2_i2c_subdev_init(sd, client, &my_sensor_ops);

    // 3. 设置子设备内部操作
    sd->internal_ops = &my_sensor_internal_ops;

    // 4. 注册到V4L2框架
    ret = v4l2_async_register_subdev(sd);
    if (ret < 0) {
        dev_err(&client->dev, "Failed to register subdev\n");
        return ret;
    }

    dev_info(&client->dev, "Sensor probe success\n");
    return 0;
}

3.3 DTS设备树配置

设备树(DTS)是Linux内核描述硬件的方式。在MTK平台上,Camera相关的DTS配置通常分布在三个地方:

DTS节点位置 作用 典型属性
sensor@xxx(I2C总线下) 描述Sensor本身 compatible, reg, clocks, power-gpios
camera-pinctrl 描述引脚复用 pinctrl-0, pinctrl-1, pinctrl-names
isp@xxx 描述ISP控制器 interrupts, reg, mipi-tx-speed

我个人习惯把Sensor的DTS节点写得尽量详细。因为后期调试时,改DTS比改驱动代码快得多。比如电源时序不对,我直接在DTS里调整powerdown-gpios的延时,不用重新编译内核。

下面是一个MTK平台上典型的Sensor DTS配置:

&i2c2 {
    status = "okay";
    clock-frequency = <400000>;

    sensor0: camera-sensor@3c {
        compatible = "ovti,ov5648";
        reg = <0x3c>;

        // 电源配置
        avdd-supply = <&mt_pmic_vcamio_ldo_reg>;
        dvdd-supply = <&mt_pmic_vcama_ldo_reg>;

        // GPIO控制
        reset-gpios = <&pio 20 GPIO_ACTIVE_LOW>;
        powerdown-gpios = <&pio 21 GPIO_ACTIVE_HIGH>;

        // MIPI配置
        port {
            sensor0_out: endpoint {
                remote-endpoint = <&csi2_0_in>;
                data-lanes = <1 2>;
                clock-noncontinuous;
            };
        };
    };
};

注意事项:DTS中的data-lanes属性非常关键。它决定了MIPI使用几路数据通道。如果Sensor支持4 lane,但DTS里只配了2 lane,图像数据会丢失一半。我曾经在MT8678上遇到过这个问题,画面出现条纹,排查了整整一天。

3.4 驱动与DTS的绑定过程

驱动怎么找到DTS节点?靠的是of_match_table。驱动里定义一个匹配表,内核在启动时遍历所有DTS节点,找到compatible属性匹配的节点,然后调用驱动的probe函数。

这个过程是自动的,但有个细节要注意:probe函数的调用顺序。如果Sensor依赖ISP的时钟,而ISP驱动还没加载,Sensor的probe就会失败。解决办法是在DTS里添加clocksclock-names属性,并在驱动里使用devm_clk_get()获取时钟,配合EPROBE_DEFER机制等待依赖就绪。

嗯,这里要提一下MTK平台的特殊之处。MTK的Camera驱动通常不是直接写V4L2子设备,而是通过一个叫做kd_camera_hw的中间层。这个层负责管理电源、时钟、GPIO等硬件资源。我建议初学者先理解标准V4L2流程,再看MTK的封装,否则容易一头雾水。

好了,这一章的内容就到这里。V4L2框架、Sensor注册、DTS配置,这三者是Camera驱动的三根支柱。下一章我们会深入MIPI CSI接口,看看数据是怎么从Sensor传到ISP的。