3、驱动层开发基础:Linux V4L2框架入门、Sensor驱动注册流程、设备树配置要点
好,咱们今天聊聊驱动层。很多做图像的朋友,一开始都卡在驱动这块。觉得它又底层又复杂,跟应用层完全是两个世界。其实没那么玄乎。我刚开始接触V4L2的时候,也翻过不少车。今天就把我这些年踩过的坑,总结成三个核心点:V4L2框架、Sensor驱动注册、设备树配置。你把这三点吃透了,驱动层基本就通了。
3.1 Linux V4L2框架入门
V4L2,全称Video for Linux 2。说白了,它就是Linux内核里给视频设备准备的一套标准接口。你想想看,如果没有这个标准,每家芯片厂都搞一套自己的API,那应用层的工程师得疯掉。V4L2的作用,就是让上层应用可以用统一的方式,去操作摄像头、电视卡、编码器这些设备。
我个人习惯把V4L2框架理解成三层:
- 应用层:用户空间的程序,通过
open()、ioctl()这些系统调用跟驱动打交道。 - 核心层:内核里的V4L2框架代码,它负责管理设备节点、处理标准ioctl命令、维护视频缓冲队列。
- 驱动层:我们开发者要写的部分。具体到Sensor驱动,就是实现
v4l2_subdev相关的回调函数。
嗯,这里要注意。V4L2里有两个核心概念:video_device和v4l2_subdev。video_device对应的是/dev/videoX这个设备节点,用户空间直接操作它。v4l2_subdev则是子设备,比如Sensor、ISP、闪光灯这些。它们通过media controller框架连接起来。我在项目中遇到过,有人把Sensor驱动直接注册成video_device,结果上层应用怎么都调不通。后来才发现,Sensor应该注册成v4l2_subdev,然后由Bridge驱动(比如CSI控制器)来创建video_device。
核心要点:V4L2框架的本质,就是一套标准化的控制协议。它定义了设备怎么打开、怎么设置格式、怎么申请缓冲区、怎么启动/停止流。你只要按照这个协议来写驱动,上层应用就能无缝对接。
3.2 Sensor驱动注册流程
写一个Sensor驱动,到底要干哪些事?我把它拆成几个步骤,你照着这个流程走,基本不会错。
- 定义设备结构体:创建一个结构体,包含
v4l2_subdev、v4l2_subdev_ops、v4l2_ctrl_handler,以及Sensor私有的寄存器配置、电源控制等信息。 - 实现回调函数:这是最核心的部分。你需要实现
s_power(电源管理)、s_stream(启动/停止流)、enum_mbus_code(枚举支持的格式)、get_fmt/set_fmt(获取/设置格式)等回调。 - 注册子设备:在驱动的
probe函数里,调用v4l2_i2c_subdev_init或v4l2_spi_subdev_init来初始化子设备,然后调用v4l2_async_register_subdev注册到异步框架中。 - 控制处理:用
v4l2_ctrl_new_std来注册曝光、增益、白平衡这些标准控制。我建议你尽量用标准控制,这样上层应用可以直接用v4l2-ctl工具调试,省心很多。
我曾经在一个项目里,Sensor的s_stream回调里忘了加延时。结果每次启动流,Sensor输出都是花屏。查了两天才发现,Sensor从Standby切换到Active模式,需要至少10ms的稳定时间。从那以后,我每次写s_stream,都会在寄存器配置后面加一个msleep(20)。
个人经验:调试Sensor驱动时,先用v4l2-ctl --list-devices确认设备节点是否生成。然后用v4l2-ctl --list-formats-ext查看支持的格式。如果这两个命令能正常工作,说明驱动注册基本没问题了。
3.3 设备树配置要点
设备树,说白了就是描述硬件拓扑的配置文件。内核通过它来知道:你的Sensor接在哪个I2C总线上?用哪个CSI接口?电源怎么控制?时钟频率是多少?
一个典型的Sensor设备树节点,大概长这样:
sensor0: sensor@1a {
compatible = "sony,imx290";
reg = <0x1a>;
clocks = <&clk IMX290_CLK>;
clock-names = "xvclk";
clock-frequency = <37125000>;
vdd-supply = <®_2v8>;
vddio-supply = <®_1v8>;
port {
sensor0_out: endpoint {
remote-endpoint = <&csi0_ep>;
data-lanes = <1 2 3 4>;
link-frequencies = /bits/ 64 <450000000>;
};
};
};
这里有几个要点,我特别提醒一下:
- compatible属性:必须跟驱动里的
of_match_table完全一致。大小写、下划线都不能错。我见过有人把"imx290"写成了"imx290s",结果驱动死活加载不上。 - reg属性:这是I2C地址。注意,设备树里写的是7位地址,而数据手册里给的通常是8位地址。比如数据手册写0x35,设备树里要填0x1a(右移一位)。
- data-lanes:这个决定了MIPI接口用几根数据线。如果Sensor支持4 lane,但你的硬件只接了2 lane,这里就要改成
<1 2>。否则CSI控制器会一直等第3、4根线的数据,导致超时。 - link-frequencies:MIPI链路的频率。这个值必须跟Sensor的PLL配置匹配。如果配错了,图像会出现条纹或者根本不出图。
避坑指南:我曾经在一个项目里,设备树的clock-frequency写的是27MHz,但Sensor实际需要的是37.125MHz。结果Sensor输出帧率只有标称的一半。查了三天,最后用示波器量了时钟才发现。所以,设备树里的时钟频率,一定要跟Sensor数据手册严格对应。
另外,设备树里还有一个容易被忽略的点:电源时序。很多Sensor对电源上电顺序有严格要求,比如必须先上VDD,再上VDDIO,最后上时钟。如果顺序错了,Sensor可能无法初始化。我建议你在设备树里用regulator-boot-on和regulator-always-on来控制电源,或者在驱动里用gpiod_set_value手动控制。
嗯,最后总结一下。驱动层开发,说白了就是三件事:理解V4L2框架的套路、按照标准流程注册Sensor、把设备树配准确。你把这三点搞定了,剩下的就是对着数据手册填寄存器了。下一章,咱们聊聊ISP的初始化流程,那才是真正出图像的关键一步。