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驱动注册流程,我总结为五步走:
- 匹配设备树:驱动通过compatible属性找到对应的DTS节点。
- 初始化I2C/SPI通信:读取Sensor ID,确认硬件存在。
- 注册V4L2子设备:调用
v4l2_i2c_subdev_init()或v4l2_spi_subdev_init()。 - 实现子设备操作函数:包括
s_power(电源管理)、s_stream(开启/关闭流)、enum_mbus_code(枚举格式)等。 - 注册中断或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里添加clocks和clock-names属性,并在驱动里使用devm_clk_get()获取时钟,配合EPROBE_DEFER机制等待依赖就绪。
嗯,这里要提一下MTK平台的特殊之处。MTK的Camera驱动通常不是直接写V4L2子设备,而是通过一个叫做kd_camera_hw的中间层。这个层负责管理电源、时钟、GPIO等硬件资源。我建议初学者先理解标准V4L2流程,再看MTK的封装,否则容易一头雾水。
好了,这一章的内容就到这里。V4L2框架、Sensor注册、DTS配置,这三者是Camera驱动的三根支柱。下一章我们会深入MIPI CSI接口,看看数据是怎么从Sensor传到ISP的。