3. 摄像头驱动框架:V4L2框架在MTK平台上的实现、Video Device注册流程、Subdev与Sensor驱动分层

好,咱们进入第三章。这一章可以说是整个摄像头接入的骨架部分。你想想看,摄像头硬件焊上去了,I2C能通,MIPI能跑,但系统怎么知道这是个摄像头?怎么把数据流送到应用层?答案就是——V4L2框架。

V4L2,全称Video for Linux 2。说白了,它就是Linux内核里给视频设备定的一套标准接口。我做了这么多年嵌入式,见过不少平台,有的用私有API,有的用Android的Camera HAL,但底层最终都得跟V4L2打交道。MTK平台也不例外,甚至可以说,MTK对V4L2的依赖比高通还要深一些。

核心观点:V4L2不是MTK独有的,它是Linux内核的标准。MTK只是在这个框架下做了自己的实现。你学会了V4L2,换任何平台都能快速上手。

3.1 V4L2框架在MTK平台上的实现

MTK平台上的V4L2实现,其实分两层:

  • 上层:标准的V4L2设备节点,比如/dev/video0,应用层通过open/ioctl/mmap来操作。
  • 下层:MTK自己的硬件抽象层,负责跟ISP、MIPI CSI、DMA等硬件打交道。

我个人习惯把MTK的V4L2驱动看成三明治结构:

  1. Video Device层——负责注册/dev/videoX,处理应用层的ioctl。
  2. V4L2 Subdev层——负责管理Sensor、MIPI桥片、ISP等子设备。
  3. 硬件驱动层——直接操作寄存器,控制MIPI、DMA、时钟等。

嗯,这里要注意:MTK的V4L2驱动通常不是直接写在drivers/media/platform/下,而是放在drivers/misc/mediatek/或者drivers/media/platform/mtk-isp/里。我第一次找MTK的摄像头驱动源码时,翻遍了整个kernel目录,最后发现它藏在vendor目录下。所以,如果你在标准路径找不到,别慌,去vendor/mediatek/proprietary里看看。

3.2 Video Device注册流程

Video Device的注册,说白了就是让内核知道:嘿,这里有个摄像头,你可以通过/dev/video0来访问它。流程其实不复杂,我拆成几步讲:

  1. 分配video_device结构体——调用video_device_alloc()
  2. 设置fops和ioctl ops——告诉内核怎么打开、关闭、设置格式、申请缓冲区。
  3. 设置v4l2_device——把video_device挂到v4l2_device下。
  4. 注册到内核——调用video_register_device()

代码示例大概长这样:

static struct video_device mtk_cam_vdev = {
    .name = "MTK Camera",
    .fops = &mtk_cam_fops,
    .ioctl_ops = &mtk_cam_ioctl_ops,
    .release = video_device_release,
};

static int mtk_cam_probe(struct platform_device *pdev)
{
    struct v4l2_device *v4l2_dev;
    struct video_device *vdev;

    // 1. 分配video_device
    vdev = video_device_alloc();
    if (!vdev)
        return -ENOMEM;

    // 2. 复制模板
    *vdev = mtk_cam_vdev;

    // 3. 设置v4l2_device
    v4l2_dev = dev_get_drvdata(pdev->dev.parent);
    vdev->v4l2_dev = v4l2_dev;

    // 4. 注册
    video_register_device(vdev, VFL_TYPE_VIDEO, -1);

    return 0;
}

避坑指南:我曾经在注册video_device时忘了设置v4l2_dev,结果内核直接panic。后来查了半天,发现是video_register_device内部会通过v4l2_dev来获取父设备信息。所以,注册前一定要确保v4l2_dev已经初始化好了。

注册成功后,你在/dev下就能看到video0video1这样的节点。每个节点对应一个摄像头。但这里有个坑:MTK平台通常会有多个video节点,比如video0是主摄,video1是广角,video2是深度。但有时候video0可能是ISP的raw输出,video1才是真正的摄像头。怎么区分?看v4l2-ctl --list-devices的输出,它会告诉你每个节点的名字和功能。

3.3 Subdev与Sensor驱动分层

好,Video Device注册完了,但摄像头数据怎么来的?这就涉及到Subdev和Sensor驱动了。

V4L2 Subdev是V4L2框架里用来管理子设备的。什么叫子设备?Sensor是一个子设备,MIPI桥片是一个子设备,ISP也是一个子设备。每个子设备都有自己的控制逻辑,比如Sensor要配置曝光、增益,MIPI桥片要配置lane数、时钟频率。

MTK平台的分层结构大概是这样的:

层级 职责 代码位置
Video Device 对外提供/dev/videoX接口 drivers/media/platform/mtk-isp/
V4L2 Subdev 管理Sensor、MIPI、ISP等子设备 drivers/media/i2c/ 或 vendor/mediatek/
Sensor驱动 控制Sensor的寄存器、初始化序列 drivers/media/i2c/
MIPI驱动 配置MIPI CSI的物理层参数 drivers/misc/mediatek/mipi/

Sensor驱动是最底层的。它通过I2C跟Sensor芯片通信,负责上电、初始化、配置分辨率、帧率、曝光时间等。我建议你写Sensor驱动时,先搞清楚三件事:

  • 上电时序:先给哪路电?等多久?我遇到过Sensor死活不出图,最后发现是上电时序反了。
  • 初始化序列:Sensor厂商通常会提供一串寄存器配置,直接写进去就行。但要注意,不同分辨率对应不同的序列。
  • 控制接口:曝光、增益、白平衡这些,通过V4L2的s_ctrl回调来实现。

Subdev的注册流程跟Video Device类似,但用的是v4l2_subdev_init()v4l2_device_register_subdev()。代码示例:

static const struct v4l2_subdev_ops mtk_sensor_subdev_ops = {
    .core = &mtk_sensor_core_ops,
    .video = &mtk_sensor_video_ops,
    .pad = &mtk_sensor_pad_ops,
};

static int mtk_sensor_probe(struct i2c_client *client)
{
    struct v4l2_subdev *sd;

    sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL);
    v4l2_subdev_init(sd, &mtk_sensor_subdev_ops);
    sd->owner = THIS_MODULE;
    sd->dev = &client->dev;

    // 注册到v4l2_device
    v4l2_device_register_subdev(v4l2_dev, sd);

    return 0;
}

注意:Subdev的pad操作非常重要。MTK平台的多摄像头拼接、全景影像,都是通过Subdev的pad来路由数据流的。如果你不配置pad,数据流可能根本走不通。我曾经在调试环视系统时,发现四个摄像头的数据只有两个能出来,查了两天,最后发现是Subdev的pad没有正确连接。

最后,我想强调一点:V4L2框架在MTK平台上的实现,虽然看起来复杂,但核心思想很简单——分层解耦。Video Device负责跟应用层打交道,Subdev负责管理硬件,Sensor驱动负责控制具体的芯片。每一层各司其职,出了问题也容易定位。

嗯,这一章的内容就到这里。下一章我们会深入MTK的ISP pipeline,看看数据是怎么从Sensor一路流到内存的。到时候我会分享一个我在环视项目中遇到的ISP tuning的坑,保证让你少走弯路。