第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控制器,层层递进。

Camera Sensor驱动知识结构 物理层:通信协议 I2C协议(SCL + SDA,半双工) SPI协议(4线,全双工) 内核框架层:V4L2 video_device v4l2_subdev v4l2_ctrl media_entity 平台层:CCI控制器 cci_device结构体 cci_read / cci_write I2C频率配置 Sensor驱动最终实现 层层封装

3.5 调试实战:如何快速定位通信问题

调试Sensor驱动,80%的问题出在通信上。我总结了一套排查流程,分享给你:

  1. 确认硬件连接:用示波器量SCL和SDA,看有没有波形。没波形?查供电和GPIO配置。
  2. 确认I2C地址:用i2cdetect工具扫描总线,看Sensor地址对不对。命令:i2cdetect -y -r <bus_number>
  3. 确认寄存器读写:用i2cget/i2cset手动读写Sensor的CHIP_ID寄存器。能读到?说明通信通了。
  4. 确认CCI配置:检查CCI的I2C频率、地址类型、数据类型是否跟Sensor匹配。
  5. 确认V4L2注册:用media-ctl查看subdev是否注册成功,pipeline是否完整。

我的经验:第2步和第3步是最容易出问题的。很多Sensor的I2C地址有多个版本,同一个型号不同批次都可能不一样。我建议你拿到Sensor后,第一件事就是读CHIP_ID,确认通信正常再往下写代码。