3、Sensor驱动开发:Linux V4L2 Sensor驱动框架、s_power/s_g_mbus_config实现、常用Sensor寄存器配置
好,咱们进入正题。Sensor驱动开发,说白了就是让Linux内核认识你的摄像头传感器。我刚开始做这行的时候,总觉得V4L2框架很神秘,后来发现它其实就是一套标准接口——你按规矩填好回调函数,内核就知道怎么跟你家Sensor打交道了。
3.1 V4L2 Sensor驱动框架概览
Linux内核里,Sensor驱动通常挂在drivers/media/i2c/目录下。你打开看看,全是ov5640.c、imx219.c这种文件。它们都遵循同一个套路:
- 注册一个
i2c_driver结构体 - 实现
v4l2_subdev_ops里的回调函数 - 通过
v4l2_async_register_subdev挂到系统中
我个人习惯先把框架搭起来,再慢慢填函数。就像盖房子,先立柱子再砌墙。
static const struct v4l2_subdev_core_ops my_sensor_core_ops = {
.s_power = my_sensor_s_power,
.init = my_sensor_init,
// 其他回调...
};
static const struct v4l2_subdev_video_ops my_sensor_video_ops = {
.s_stream = my_sensor_s_stream,
.g_mbus_config = my_sensor_g_mbus_config,
// 其他回调...
};
static const struct v4l2_subdev_ops my_sensor_ops = {
.core = &my_sensor_core_ops,
.video = &my_sensor_video_ops,
// 其他...
};
static int my_sensor_probe(struct i2c_client *client) {
// 分配私有结构体
// 初始化i2c通信
// 读取Sensor ID确认硬件
// 注册subdev
v4l2_i2c_subdev_init(&sensor->sd, client, &my_sensor_ops);
v4l2_async_register_subdev(&sensor->sd);
return 0;
}
嗯,这里要注意:probe函数里千万别做耗时的初始化操作,比如配置寄存器序列。为什么?因为系统启动时,所有设备都是并行探测的,你卡太久会影响整体启动速度。
3.2 s_power 回调实现——电源管理的艺术
s_power是V4L2框架里最常用的回调之一。它负责控制Sensor的电源状态。说白了,就是告诉Sensor:「醒醒,要干活了」或者「睡吧,没你事了」。
我在项目中遇到过一个问题:某个Sensor在s_power(1)之后立刻读寄存器,结果返回全0。查了半天,发现是电源稳定需要时间。后来加了个msleep(10)才搞定。
核心要点:s_power(1) 要保证Sensor完全就绪,s_power(0) 要彻底断电省电。
static int my_sensor_s_power(struct v4l2_subdev *sd, int on) {
struct my_sensor *sensor = to_my_sensor(sd);
int ret;
if (on) {
// 1. 使能电源引脚
ret = regulator_enable(sensor->avdd_reg);
if (ret) return ret;
ret = regulator_enable(sensor->dvdd_reg);
if (ret) return ret;
ret = regulator_enable(sensor->iovdd_reg);
if (ret) return ret;
// 2. 等待电源稳定
usleep_range(5000, 10000); // 5-10ms
// 3. 拉高复位引脚
gpiod_set_value_cansleep(sensor->reset_gpio, 1);
usleep_range(1000, 2000);
// 4. 初始化寄存器序列
ret = my_sensor_init_regs(sensor);
if (ret) return ret;
sensor->power_on = true;
} else {
// 1. 拉低复位引脚
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
// 2. 关闭电源
regulator_disable(sensor->iovdd_reg);
regulator_disable(sensor->dvdd_reg);
regulator_disable(sensor->avdd_reg);
sensor->power_on = false;
}
return 0;
}
个人经验:电源时序很重要。有些Sensor要求先上AVDD再上DVDD,顺序反了可能烧芯片。我建议你在数据手册里找到「Power Up Timing」那张图,照着时序图写代码,别自己瞎猜。
3.3 s_g_mbus_config 回调——告诉ISP你的数据格式
s_g_mbus_config这个回调,名字有点绕口。其实它的作用很简单:告诉Camera ISP,你的Sensor输出数据是什么格式——几根数据线?同步信号怎么传?
你想想看,ISP那边要正确接收数据,必须知道这些信息。否则数据对不上,画面全是花的。
static int my_sensor_g_mbus_config(struct v4l2_subdev *sd,
struct v4l2_mbus_config *cfg) {
// 告诉ISP:我们用的是CSI-2接口,2条lane
cfg->type = V4L2_MBUS_CSI2_DPHY;
// 配置标志位
cfg->flags = V4L2_MBUS_CSI2_2_LANE |
V4L2_MBUS_CSI2_CHANNEL_0 |
V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
return 0;
}
常见的配置选项有:
| 标志位 | 含义 | 我遇到的坑 |
|---|---|---|
| V4L2_MBUS_CSI2_1_LANE / 2_LANE / 4_LANE | 数据通道数量 | 曾经有个项目,硬件只接了2条lane,我配成了4条,结果图像只有一半正常 |
| V4L2_MBUS_CSI2_CONTINUOUS_CLOCK | 时钟是否持续输出 | 非连续时钟模式省电,但有些ISP不支持,得看平台 |
| V4L2_MBUS_CSI2_CHANNEL_0 | 使用的虚拟通道 | 多Sensor共用CSI总线时,这个要区分开 |
注意:s_g_mbus_config返回的信息必须和硬件实际连接一致。我曾经见过一个案例,硬件工程师把D0和D1线序接反了,驱动里怎么配都不对。最后只能改驱动里的lane映射表。
3.4 常用Sensor寄存器配置——实战经验
每个Sensor都有自己的寄存器地图。但不管什么型号,有几类寄存器是必配的:
- 芯片ID寄存器:probe时读取,确认硬件型号
- 模式选择寄存器:设置分辨率、帧率
- 曝光/增益寄存器:控制图像亮度
- 输出格式寄存器:RAW8、RAW10、YUV等
- PLL配置寄存器:生成内部时钟
我一般会把寄存器配置写成结构体数组,方便维护:
struct regval_list {
uint16_t addr;
uint8_t val;
};
static const struct regval_list my_sensor_init_regs[] = {
// 软件复位
{0x0103, 0x01},
{0x0103, 0x00},
// 设置输出尺寸 1920x1080
{0x3808, 0x07}, // 宽度高字节
{0x3809, 0x80}, // 宽度低字节 (0x0780 = 1920)
{0x380A, 0x04}, // 高度高字节
{0x380B, 0x38}, // 高度低字节 (0x0438 = 1080)
// 设置输出格式 RAW10
{0x4300, 0x30}, // 格式选择
// PLL配置,目标帧率30fps
{0x3034, 0x1A}, // PLL分频
{0x3035, 0x21}, // PLL倍频
{0x3036, 0x46}, // PLL后分频
// 结束标记
{0xFFFF, 0xFF},
};
写寄存器配置时,我有个习惯:每配完一组关键寄存器,就读回来验证一下。有些Sensor的I2C总线不稳定,写进去的值可能不对。
避坑指南:我曾经在配置一个OV系列Sensor时,发现帧率怎么都调不对。后来用示波器抓MCLK时钟,发现晶振频率是24MHz,但驱动里写死了27MHz的PLL参数。嗯,这种低级错误,查了一整天。
3.5 调试技巧——让Sensor「开口说话」
Sensor驱动写好了,怎么验证它工作正常?我一般按这个顺序排查:
- 第一步:确认I2C通信——用
i2cdetect看设备地址对不对 - 第二步:读芯片ID——probe时打印出来,确认硬件型号
- 第三步:检查电源和时钟——用万用表测电压,示波器看MCLK
- 第四步:抓MIPI信号——用逻辑分析仪看CSI-2数据包
说实话,大部分问题都出在前三步。电源没上对、时钟频率不对、I2C地址写错了——这些基础问题占了80%的调试时间。
好了,Sensor驱动开发的核心框架就这些。下一章我们会讲怎么把Sensor数据流打通,让图像真正从Sensor走到ISP里去。