3. 驱动框架概览:Linux V4L2框架、Sensor驱动在系统中的位置、驱动分层结构
好,咱们进入第三章。这一章我打算聊聊驱动框架的全貌。说白了,就是让你知道Sensor驱动在整个Linux系统里到底站在哪个位置,它跟谁说话,它听谁的指挥。
很多刚入行的兄弟,拿到一个Sensor的datasheet就开始撸寄存器,结果驱动写完了,插到板子上发现应用程序调不起来。为什么?因为他没搞懂V4L2这套游戏规则。嗯,咱们先把地图摊开,再动手挖坑。
3.1 Linux V4L2框架:视频设备的标准接口
V4L2,全称Video for Linux 2。它是Linux内核里给视频设备定义的一套标准接口。你可以把它理解成“视频世界的POSIX”——不管你是USB摄像头、MIPI Sensor还是HDMI采集卡,只要你想在Linux下被用户空间的应用(比如GStreamer、OpenCV)调用,你就得遵守V4L2的规矩。
我个人习惯把V4L2框架拆成三层来看:
- 字符设备层:用户空间通过
open/close/ioctl操作/dev/videoX节点。 - 核心层:内核里的
videodev.ko,负责管理设备节点、分配buffer、维护队列。 - 驱动层:咱们写的Sensor驱动,挂接到核心层提供的
v4l2_subdev或video_device接口上。
关键点:V4L2不只是“采集图像”,它还管了控制(亮度、曝光)、格式协商(分辨率、像素格式)、buffer流转(mmap/DMABUF)。你的Sensor驱动,本质上是在实现V4L2定义的一组回调函数。
举个例子,用户空间调了VIDIOC_S_FMT设置分辨率,这个ioctl会一路走到你的s_fmt回调里。你在回调里配Sensor的寄存器,把分辨率切过去。就是这么个流程。
3.2 Sensor驱动在系统中的位置
咱们画个图,看看Sensor驱动到底嵌在系统的哪个角落。
从这张图你能看到,Sensor驱动不是孤零零的一个文件。它夹在V4L2核心层和硬件之间。往上,它要注册成V4L2 subdev;往下,它要操作I2C总线读写Sensor寄存器,还要通过Platform驱动控制MIPI CSI-2的时钟和电源。
我的经验:调试的时候,我习惯先确认硬件层有没有问题——量一下MIPI clock有没有、I2C能不能ack。硬件没起来,驱动写得再漂亮也是白搭。我曾经在一个项目里花了三天查驱动,最后发现是Sensor的reset pin没拉高……
3.3 驱动分层结构:Platform / I2C / Media
现在咱们把驱动层拆开看。一个成熟的Sensor驱动,通常由三个模块协同工作:
| 模块 | 职责 | 典型接口/文件 |
|---|---|---|
| Platform Driver | 管理电源、时钟、MIPI D-PHY配置、reset/power-down GPIO | platform_driver, devicetree |
| I2C Driver | 通过I2C总线读写Sensor内部寄存器,配置分辨率、增益、曝光 | i2c_driver, regmap |
| Media Controller | 描述pipeline拓扑(Sensor→ISP→DMA),管理link和pad | media_entity, v4l2_subdev |
3.3.1 Platform Driver:管好Sensor的“水电煤”
Platform Driver不直接跟Sensor的寄存器打交道。它管的是Sensor的“生存环境”——供电、时钟、复位时序。这些信息通常写在设备树里:
// 设备树片段示例
sensor: camera-sensor@3c {
compatible = "sony,imx290";
reg = <0x3c>;
clocks = <&clk IMX290_CLK>;
clock-frequency = <37125000>;
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio1 16 GPIO_ACTIVE_HIGH>;
avdd-supply = <®_2v8>;
dvdd-supply = <®_1v2>;
ovdd-supply = <®_1v8>;
};
Platform Driver的probe函数里,会解析这些资源,然后:
- 申请GPIO并设置初始电平
- 使能时钟并等待稳定
- 控制电源轨的上电顺序
- 最后调用I2C Driver的初始化函数
避坑指南:我曾经遇到过上电时序不对,Sensor的模拟电源和数字电源间隔太短,导致内部PLL锁不住。后来在驱动里加了usleep_range才解决。记住,Sensor的datasheet里写的上电时序,一个字都不能省。
3.3.2 I2C Driver:真正跟Sensor“对话”的人
I2C Driver才是干细活的。它通过I2C总线往Sensor的寄存器里写值,配置分辨率、帧率、增益、曝光时间。一个典型的I2C写操作长这样:
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
struct i2c_msg msg;
u8 buf[3] = { reg >> 8, reg & 0xFF, val };
msg.addr = client->addr;
msg.flags = 0;
msg.len = 3;
msg.buf = buf;
return i2c_transfer(client->adapter, &msg, 1);
}
I2C Driver的核心工作就是实现V4L2 subdev的回调:
s_power:上电/下电s_stream:开始/停止输出图像s_fmt:设置分辨率、像素格式s_ctrl:设置曝光、增益、白平衡等控制参数
每个回调里,你都要去读写Sensor的寄存器。说白了,I2C Driver就是Sensor寄存器的翻译官。
3.3.3 Media Controller:画好pipeline这张图
Media Controller是V4L2框架里比较“新”的部分。它解决了一个问题:当系统里有多个Sensor、多个ISP、多个DMA引擎时,谁跟谁连?数据流怎么走?
Media Controller用三个概念来描述:
- Entity:一个硬件模块(Sensor、ISP、DMA引擎)
- Pad:Entity的输入/输出端口
- Link:Pad之间的连接关系
举个例子,一个典型的pipeline:
Sensor (pad0:输出) → ISP (pad0:输入, pad1:输出) → DMA引擎 (pad0:输入)
在驱动里,你要注册这些entity和link:
static const struct media_entity_operations sensor_media_ops = {
.link_setup = sensor_link_setup,
};
static int sensor_probe(struct i2c_client *client)
{
// 注册subdev
// 初始化media entity
// 创建pad
// 设置link
}
注意:不是所有平台都需要Media Controller。如果你的系统只有一个Sensor、一个ISP,直接用传统的V4L2 subdev就够了。但如果你在做多摄像头、多路复用的复杂系统,Media Controller几乎是必须的。
3.4 三个模块如何协作?
咱们用一个实际场景串起来:
- 系统启动:Platform Driver先probe,解析设备树,拉高reset、使能时钟、上电。
- I2C Driver probe:Platform Driver调用I2C Driver的初始化函数,读取Sensor ID确认通信正常。
- 用户设置格式:应用层调
VIDIOC_S_FMT,I2C Driver的s_fmt回调被调用,写入分辨率寄存器。 - 开始采集:应用层调
VIDIOC_STREAMON,I2C Driver的s_stream(1)被调用,Sensor开始输出MIPI数据。 - Media Controller管理:如果用了Media Controller,用户空间还需要通过
MEDIA_IOC_SETUP_LINK配置pipeline。
你看,三个模块各司其职,缺一个都不行。
我的习惯:调试新Sensor时,我会先单独验证I2C通信——写一个内核模块,只做i2c_transfer,读Sensor的chip ID。ID读对了,再往下走。这个习惯帮我省了不知道多少排查时间。