2. Sensor驱动框架解析:Linux V4L2框架核心、RK平台Camera驱动分层与设备树配置
做Camera驱动,绕不开V4L2。很多新人一上来就被这堆抽象层搞懵了。我刚开始接触时也差不多,对着代码看了三天,感觉每个函数都认识,连起来就不知道在干嘛。后来调了几个Sensor,踩了些坑,才慢慢摸清楚这套框架到底是怎么转起来的。
这一章,我们就从V4L2的核心概念讲起,然后深入到瑞芯微平台的分层设计,最后落到设备树和pinctrl的配置上。说白了,就是让你知道驱动怎么写、怎么配、怎么跟硬件对上。
2.1 Linux V4L2框架核心
V4L2,全称Video for Linux 2,是Linux内核里处理视频设备的统一框架。它定义了一套标准接口,让应用层可以通过open/ioctl/poll这些系统调用来操作Camera设备。
核心概念就三个:设备节点、Video Buffer、Control。
| 概念 | 说明 | 我遇到过的坑 |
|---|---|---|
| 设备节点 | /dev/videoX,用户空间操作Camera的入口 |
多个Sensor共用同一个video节点,导致打开失败 |
| Video Buffer | 用于存放图像数据的内存缓冲区,支持MMAP/DMABUF/USERPTR | DMABUF方式下,buffer对齐要求没满足,图像花屏 |
| Control | 曝光、增益、白平衡等参数的控制接口 | Control ID冲突,导致3A算法写不进去 |
V4L2的驱动模型是分层的。底层是具体的硬件驱动,比如Sensor驱动、ISP驱动;上层是V4L2核心框架,负责管理设备节点和buffer流转。中间通过v4l2_subdev和video_device这两个核心结构体来连接。
关键点:V4L2把Camera设备抽象成一个video_device,而Sensor、ISP这些子模块则抽象成v4l2_subdev。subdev之间通过media_entity和media_pad建立数据通路。这就是所谓的Media Controller框架。
举个例子,一个典型的Camera数据流是这样的:
Sensor (subdev) -> MIPI CSI (subdev) -> ISP (subdev) -> DMA Engine (video_device) -> 用户空间
每个箭头都对应一个media_link。你想想看,如果没有这套抽象,每次换一个Sensor或者改一下ISP配置,都得重写整个驱动,那得多痛苦。
2.2 RK平台Camera驱动分层
瑞芯微的Camera驱动,在V4L2框架基础上做了进一步分层。我个人习惯把它分成三层:
- Sensor驱动层:负责具体的Sensor初始化、寄存器配置、曝光/增益控制。通常由Sensor厂商提供,我们只需要做适配。
- CSI/ISP驱动层:负责MIPI接口的接收、ISP图像处理。这部分是RK平台的核心,由瑞芯微提供。
- DMA驱动层:负责将ISP处理完的图像数据搬运到内存,生成
/dev/videoX节点。也是RK平台提供的。
这三层通过v4l2_subdev和media_entity串联起来。我在项目中遇到过一个问题:Sensor驱动已经注册成功了,但应用层就是拿不到图像。查了半天,发现是CSI和ISP之间的link没有enable。嗯,这种问题在调试初期特别容易碰到。
调试技巧:用media-ctl工具可以查看当前的media graph拓扑,确认各个subdev之间的连接状态。命令示例:media-ctl -p -d /dev/media0
下面这张图展示了RK平台Camera驱动的分层结构和数据流:
2.3 设备树配置与pinctrl
设备树,说白了就是描述硬件信息的配置文件。在RK平台上,Camera相关的设备树配置主要涉及三个方面:
- Sensor节点:描述Sensor的I2C地址、复位/电源引脚、时钟频率等。
- CSI节点:描述MIPI接口的lane数、时钟极性、虚拟通道等。
- ISP节点:描述ISP的工作模式、输入源选择等。
我曾经在一个项目里,Sensor死活初始化不成功。用示波器量了I2C波形,发现SCL和SDA都是对的,但Sensor就是没响应。最后查设备树,发现reset-gpios的极性配反了。嗯,这种低级错误,排查起来最费时间。
注意:设备树中的pinctrl配置决定了GPIO的复用功能。如果pinctrl配错了,I2C或者MIPI信号可能根本出不来。我曾经遇到过CSI的时钟信号被复用成了普通GPIO,导致MIPI链路完全不通。
下面是一个典型的Sensor设备树配置示例:
// 设备树中Sensor节点的配置
&i2c4 {
status = "okay";
clock-frequency = <400000>; // I2C时钟400KHz
ov5645: ov5645@3c {
compatible = "ovti,ov5645";
reg = <0x3c>; // I2C地址
clocks = <&cru CLK_MIPI_CAMARAOUT_M4>;
clock-names = "xvclk";
power-domains = <&power RK3588_PD_VI>;
// pinctrl配置:将GPIO4_A5复用为I2C4_SDA
pinctrl-names = "default";
pinctrl-0 = <&i2c4m1_xfer>;
// 复位引脚,低电平有效
reset-gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>;
// 电源引脚
power-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_HIGH>;
// MIPI CSI配置
port {
ov5645_out: endpoint {
remote-endpoint = <&csi_in>;
data-lanes = <1 2>; // 使用2 lane MIPI
clock-noncontinuous;
link-frequencies = /bits/ 64 <480000000>;
};
};
};
};
// CSI接收端配置
&csi2_dphy0 {
status = "okay";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
csi_in: endpoint@0 {
reg = <0>;
remote-endpoint = <&ov5645_out>;
data-lanes = <1 2>;
};
};
};
};
这里有几个关键点,我特别提醒一下:
- compatible:必须和驱动里的
of_match_table匹配,否则驱动加载不了。 - data-lanes:MIPI的lane数量必须和硬件设计一致,多配或少配都会导致图像异常。
- reset-gpios:极性一定要确认清楚,我曾经因为
GPIO_ACTIVE_LOW和GPIO_ACTIVE_HIGH搞反,浪费了一整天。
经验之谈:调试设备树时,建议先用ls /sys/firmware/devicetree/base/查看设备树是否被正确解析。然后用cat /sys/kernel/debug/gpio检查GPIO的复用状态。这两个命令能帮你快速定位大部分配置问题。
pinctrl这块,说白了就是管脚复用。RK平台的pinctrl驱动会解析设备树中的pinctrl-0属性,然后配置对应的寄存器,把GPIO切换到需要的功能模式。比如I2C功能、MIPI功能、PWM功能等。
举个例子,如果Sensor的I2C引脚被复用成了普通GPIO,那I2C通信肯定失败。你想想看,这种情况下,Sensor驱动加载时执行i2c_transfer,返回的永远是-ENODEV。很多新手遇到这个问题,第一反应是I2C地址错了,或者Sensor坏了,很少有人会去查pinctrl。