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驱动看成三明治结构:
- Video Device层——负责注册
/dev/videoX,处理应用层的ioctl。 - V4L2 Subdev层——负责管理Sensor、MIPI桥片、ISP等子设备。
- 硬件驱动层——直接操作寄存器,控制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来访问它。流程其实不复杂,我拆成几步讲:
- 分配video_device结构体——调用
video_device_alloc()。 - 设置fops和ioctl ops——告诉内核怎么打开、关闭、设置格式、申请缓冲区。
- 设置v4l2_device——把video_device挂到v4l2_device下。
- 注册到内核——调用
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下就能看到video0、video1这样的节点。每个节点对应一个摄像头。但这里有个坑: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的坑,保证让你少走弯路。