第3章 Sensor驱动基础:I2C/SPI通信协议、V4L2框架、CCI控制器驱动
做Camera驱动,说白了就是跟Sensor芯片打交道。怎么跟它说话?怎么让它干活?怎么把数据拿回来?这章咱们就把这些基础问题讲透。
我刚开始做高通平台时,也踩过不少坑。有一次调一颗OV的Sensor,死活读不到ID,折腾了两天才发现是I2C时序配错了。嗯,从那以后我再也不敢小看这些基础协议了。
3.1 通信协议:I2C vs SPI
Sensor跟主控之间,通常用两种方式通信:I2C和SPI。你想想看,一个Sensor芯片,它需要接收配置参数(比如曝光时间、增益),还要把图像数据送出来。配置通道用I2C,数据通道用MIPI CSI,这是高通平台最常见的组合。
| 对比项 | I2C | SPI |
|---|---|---|
| 线数 | 2线(SCL + SDA) | 4线(SCLK + MOSI + MISO + CS) |
| 速率 | 标准100kHz,快速400kHz,高速3.4MHz | 通常10MHz~50MHz |
| 寻址方式 | 7位/10位地址,多设备共用总线 | 片选信号,一主多从 |
| 通信方式 | 半双工 | 全双工 |
| 典型应用 | Sensor寄存器配置 | 部分高速Sensor或EEPROM |
我个人习惯,拿到一颗新Sensor,第一件事就是看它的通信接口。如果是I2C,查地址对不对;如果是SPI,看极性相位配没配错。这些细节,驱动里写错一个bit,Sensor就不理你。
3.1.1 I2C通信细节
I2C协议其实不复杂。起始条件、从机地址、读写位、应答、数据字节、停止条件。就这么几个要素。但实际调试时,问题往往出在时序上。
关键点:高通平台的CCI控制器,I2C频率默认配置在100kHz~400kHz之间。如果Sensor数据手册要求400kHz,你代码里配成100kHz也能用,但速度慢。反过来,如果Sensor只能跑100kHz,你硬上400kHz,就会出现应答超时。
我曾经遇到一个坑:某颗Sensor的I2C地址是0x6C,但数据手册写的是0x36。怎么回事?后来才发现,0x36是7位地址,左移一位变成8位地址就是0x6C。这种坑,新手很容易掉进去。
3.1.2 SPI通信细节
SPI用得少,但一旦用上,往往是因为Sensor寄存器多、配置量大。SPI的优势是速度快,而且可以全双工。不过要注意,SPI的四种模式(CPOL和CPHA的组合)必须跟Sensor匹配。
// SPI模式配置示例
// CPOL=0, CPHA=0: 空闲时SCLK为低,第一个边沿采样
// CPOL=0, CPHA=1: 空闲时SCLK为低,第二个边沿采样
// CPOL=1, CPHA=0: 空闲时SCLK为高,第一个边沿采样
// CPOL=1, CPHA=1: 空闲时SCLK为高,第二个边沿采样
static struct spi_board_info sensor_spi_info = {
.modalias = "sensor_spi",
.max_speed_hz = 10 * 1000 * 1000, // 10MHz
.bus_num = 1,
.chip_select = 0,
.mode = SPI_MODE_0, // CPOL=0, CPHA=0
};
3.2 V4L2框架:Camera驱动的骨架
V4L2是Linux内核里视频设备驱动的标准框架。高通平台的Camera驱动,本质上就是V4L2的一个子设备驱动。你想想看,没有V4L2,每个厂商自己搞一套接口,那上层应用得疯掉。
V4L2框架的核心概念就几个:
- video_device:代表一个视频设备节点,比如/dev/video0
- v4l2_subdev:代表一个子设备,比如Sensor、闪光灯、AF马达
- v4l2_ctrl_handler:控制句柄,管理曝光、增益、白平衡等参数
- media_entity:媒体实体,用于描述设备之间的拓扑关系
在高通平台上,Sensor驱动通常注册为一个v4l2_subdev。上层ISP驱动通过media controller框架找到这个subdev,然后调用它的ops来配置Sensor。
调试技巧:用media-ctl命令可以查看整个Camera pipeline的拓扑结构。比如 media-ctl -p -d /dev/media0,能看到Sensor、CSI、ISP之间的连接关系。这个命令我调试时几乎天天用。
3.2.1 Sensor驱动的核心ops
一个标准的Sensor subdev驱动,需要实现下面这些操作:
static const struct v4l2_subdev_core_ops sensor_core_ops = {
.s_power = sensor_s_power, // 电源管理
.ioctl = sensor_ioctl, // 自定义ioctl
};
static const struct v4l2_subdev_video_ops sensor_video_ops = {
.s_stream = sensor_s_stream, // 启动/停止数据流
.g_frame_interval = sensor_g_frame_interval,
.s_frame_interval = sensor_s_frame_interval,
};
static const struct v4l2_subdev_pad_ops sensor_pad_ops = {
.enum_mbus_code = sensor_enum_mbus_code,
.get_fmt = sensor_get_fmt,
.set_fmt = sensor_set_fmt,
};
static const struct v4l2_subdev_ops sensor_ops = {
.core = &sensor_core_ops,
.video = &sensor_video_ops,
.pad = &sensor_pad_ops,
};
这里我特别想强调s_stream这个回调。它负责启动Sensor的数据输出。很多驱动问题,都是因为s_stream里忘了配置MIPI时钟或者没等Sensor稳定就返回了。
3.3 CCI控制器驱动:高通平台的I2C加速器
CCI(Camera Control Interface)是高通平台专门为Camera设计的I2C控制器。它跟普通的I2C控制器不一样,做了很多针对Sensor配置的优化。
说白了,CCI就是高通帮你封装好的一套I2C读写接口。你不需要直接操作I2C适配器,而是通过CCI的API来读写Sensor寄存器。
3.3.1 CCI的核心数据结构
// CCI设备结构体
struct cci_device {
struct i2c_adapter *adapter; // I2C适配器
struct mutex lock; // 互斥锁
u32 i2c_freq; // I2C频率
u8 slave_addr; // Sensor从机地址
};
// CCI读写操作结构体
struct cci_data {
u16 reg_addr; // 寄存器地址
u32 reg_addr_type; // 地址类型:8位/16位/32位
u16 data; // 写入的数据
u32 data_type; // 数据类型:8位/16位/32位
u32 mask; // 掩码,用于位操作
};
3.3.2 CCI的读写API
高通平台提供了标准的CCI读写接口。我个人习惯把这些接口再封装一层,方便调试时加打印。
// 读取Sensor寄存器
int32_t sensor_i2c_read(struct cci_device *cci_dev,
uint16_t reg_addr, uint16_t *data)
{
int32_t rc = 0;
struct cci_data cci_cfg = {0};
cci_cfg.reg_addr = reg_addr;
cci_cfg.reg_addr_type = CCI_REG_ADDR_TYPE_16BIT;
cci_cfg.data_type = CCI_DATA_TYPE_8BIT;
rc = cci_read(cci_dev, &cci_cfg, 1);
if (rc < 0) {
pr_err("cci read failed: reg=0x%x, rc=%d\n", reg_addr, rc);
return rc;
}
*data = cci_cfg.data;
return 0;
}
// 写入Sensor寄存器
int32_t sensor_i2c_write(struct cci_device *cci_dev,
uint16_t reg_addr, uint16_t data)
{
int32_t rc = 0;
struct cci_data cci_cfg = {0};
cci_cfg.reg_addr = reg_addr;
cci_cfg.reg_addr_type = CCI_REG_ADDR_TYPE_16BIT;
cci_cfg.data = data;
cci_cfg.data_type = CCI_DATA_TYPE_8BIT;
rc = cci_write(cci_dev, &cci_cfg, 1);
if (rc < 0) {
pr_err("cci write failed: reg=0x%x, data=0x%x, rc=%d\n",
reg_addr, data, rc);
return rc;
}
return 0;
}
注意:CCI的读写操作是同步的,会阻塞等待I2C传输完成。如果在中断上下文或者原子上下文中调用,会导致内核panic。我见过有人把Sensor配置放在中断回调里,结果系统直接挂了。
3.4 知识结构总览
下面这张图,把本章的核心知识点串起来了。从物理层的I2C/SPI协议,到内核层的V4L2框架,再到高通特有的CCI控制器,层层递进。
3.5 调试实战:如何快速定位通信问题
调试Sensor驱动,80%的问题出在通信上。我总结了一套排查流程,分享给你:
- 确认硬件连接:用示波器量SCL和SDA,看有没有波形。没波形?查供电和GPIO配置。
- 确认I2C地址:用i2cdetect工具扫描总线,看Sensor地址对不对。命令:
i2cdetect -y -r <bus_number> - 确认寄存器读写:用i2cget/i2cset手动读写Sensor的CHIP_ID寄存器。能读到?说明通信通了。
- 确认CCI配置:检查CCI的I2C频率、地址类型、数据类型是否跟Sensor匹配。
- 确认V4L2注册:用media-ctl查看subdev是否注册成功,pipeline是否完整。
我的经验:第2步和第3步是最容易出问题的。很多Sensor的I2C地址有多个版本,同一个型号不同批次都可能不一样。我建议你拿到Sensor后,第一件事就是读CHIP_ID,确认通信正常再往下写代码。