3. Sensor驱动框架:从V4L2到高通Camera子系统
好,咱们进入正题。这一章要聊的是Sensor驱动的骨架——V4L2框架和高通Camera子系统的分层结构。说白了,就是搞清楚你的Sensor驱动到底活在哪个世界里,跟谁打交道。
我刚开始接触高通平台时,也被这一堆缩写搞晕过:KMD、UMD、HAL、V4L2……后来调了几个Sensor才明白,这些层级就像公司的组织架构——各司其职,谁也别越级汇报。
3.1 V4L2框架简介
V4L2,全称Video for Linux 2,是Linux内核里处理视频设备的通用框架。你想想看,摄像头、电视卡、图像采集卡……这些设备如果各写各的驱动,那应用层得疯掉。V4L2就是定了个规矩:所有视频设备都按同一套接口来。
在Camera领域,V4L2主要干这几件事:
- 设备节点管理:每个Sensor对应一个
/dev/videoX节点 - 缓冲区管理:负责图像数据的DMA和内存映射
- 控制接口:曝光、增益、帧率这些参数通过
ioctl下发 - 事件机制:比如帧同步信号、错误通知等
核心概念:V4L2把Camera抽象成一个"视频采集设备",应用层通过open/ioctl/mmap这套标准接口来操作。你的Sensor驱动,本质上就是实现V4L2规定的那些回调函数。
我记得第一次调Sensor时,死活不出图。后来发现是V4L2的buffer队列没配好——嗯,这个坑后面会细讲。
3.2 高通Camera子系统的分层结构
高通的Camera架构,从上到下分三层:KMD(内核态驱动)→ UMD(用户态驱动)→ HAL(硬件抽象层)。我画了张图,你看一眼就明白了。
每一层都有明确的职责:
| 层级 | 位置 | 主要职责 | 典型代码/模块 |
|---|---|---|---|
| KMD | 内核空间 | Sensor寄存器读写、MIPI配置、V4L2设备注册 | msm_sensor.c、cam_sensor_core.c |
| UMD | 用户空间 | Stream on/off调度、参数协商、电源管理 | cam_sensor_dev.c、cam_sensor_io.c |
| HAL | 用户空间 | 向上提供Camera API、Session管理、Pipeline配置 | chi-cdk、camera_hal.cpp |
我的经验:调试时80%的问题出在KMD和UMD的接口对接上。比如KMD上报的format跟UMD预期的不一致,直接导致黑屏。建议先确认KMD层的s_probe是否成功,再往上查。
3.3 Sensor驱动注册流程
一个Sensor从上电到出图,驱动注册是第一步。流程大致如下:
- 设备树解析:读取dtsi中的Sensor节点信息(I2C地址、GPIO、电源时序等)
- I2C/SPI总线匹配:根据compatible字符串找到对应的驱动
- probe函数执行:分配资源、初始化V4L2子设备、注册控制接口
- 电源上电:按dtsi配置的时序给Sensor供电
- 初始化序列下发:通过I2C写入Sensor的寄存器配置
- V4L2设备注册:创建
/dev/videoX节点,等待应用层调用
这里有个关键点:probe成功不代表Sensor能出图。我遇到过probe返回0,但Sensor根本没起来的案例——后来发现是reset GPIO的极性搞反了。
3.4 platform_driver vs i2c_driver
写Sensor驱动时,第一个选择题就是:用platform_driver还是i2c_driver?
| 对比项 | platform_driver | i2c_driver |
|---|---|---|
| 总线类型 | 虚拟平台总线 | I2C物理总线 |
| 匹配方式 | 设备树compatible + 设备名 | I2C设备地址 + compatible |
| 适用场景 | Sensor集成在SoC内部(如ISP内置) | 外挂I2C接口的Sensor(绝大多数) |
| 资源获取 | 通过platform_get_resource |
通过i2c_client结构体 |
| 高通平台现状 | 较少使用,部分eSensor会用 | 主流方案,几乎所有外挂Sensor都用 |
避坑指南:我曾经在一个项目里误用了platform_driver来驱动I2C Sensor,结果probe函数根本不会被调用。因为platform_driver不会自动扫描I2C总线——你想想看,这就像拿着公交卡去坐地铁,闸机根本不认。
在高通平台上,99%的外挂Sensor都使用i2c_driver。注册方式如下:
static const struct i2c_device_id sensor_id[] = {
{ "imx586", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sensor_id);
static const struct of_device_id sensor_of_match[] = {
{ .compatible = "sony,imx586" },
{ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);
static struct i2c_driver sensor_i2c_driver = {
.driver = {
.name = "imx586",
.of_match_table = sensor_of_match,
},
.probe = sensor_probe,
.remove = sensor_remove,
.id_table = sensor_id,
};
module_i2c_driver(sensor_i2c_driver);
注意看,这里同时提供了i2c_device_id和of_match_table。前者用于老式板级配置,后者用于设备树。我个人习惯两个都写上,兼容性最好。
3.5 高通平台的特殊之处
高通在标准V4L2之上做了封装,叫Camera Subsystem(CAMSS)。它把Sensor驱动拆成了两部分:
- 底层驱动:直接操作硬件,放在KMD层
- 策略驱动:做流管理、电源管理,放在UMD层
这种拆分的好处是:换Sensor不用改上层代码。你只需要在KMD层实现新的cam_sensor_core接口,UMD层完全复用。
核心要点:在高通平台上,你的Sensor驱动最终会注册成一个cam_sensor_device,而不是直接暴露为V4L2设备。CAMSS框架会在UMD层帮你完成V4L2的封装。
嗯,这一章的内容就到这里。记住一句话:驱动框架是骨架,MIPI协议是血脉。骨架搭不对,后面调MIPI全是白费功夫。