4、Sensor驱动开发(上):I2C通信调试、Sensor寄存器读写、PDAF与OTP读取、驱动probe流程

各位同学,欢迎来到Sensor驱动开发的上半场。这节课内容很实在,全是咱们做车载影像时天天要打交道的硬功夫。I2C怎么调通?寄存器怎么读写?PDAF和OTP又是啥?驱动probe流程到底怎么走?别急,我一个一个给你拆开讲。

4.1 I2C通信调试:别让总线卡住你

做Sensor驱动,第一步就是让主控和Sensor能说上话。MTK平台上,I2C是最常用的通信方式。说白了,就是两根线:SCL(时钟)和SDA(数据)。

我个人习惯,拿到一块新Sensor模组,第一件事不是写驱动,而是先拿示波器抓I2C波形。为什么?因为硬件连接可能就有问题。我曾经在一个项目里,折腾了两天I2C读写总是超时,最后发现是Sensor那边的上拉电阻没焊,信号根本拉不起来。

调试I2C,你心里要有个谱:

  • 地址对不对? Sensor的I2C地址通常是7位或10位,MTK平台默认用7位。记得左移一位,因为Linux内核里地址是8位表示的。比如Sensor地址是0x30,你在驱动里要写0x60。
  • 时序对不对? 标准模式100kHz,快速模式400kHz。别上来就设400k,有些Sensor扛不住。我建议先从100k开始,调通了再提速度。
  • ACK信号有没有? 发送完地址字节后,Sensor会拉低SDA表示应答。如果没看到ACK,要么地址错了,要么Sensor没上电。
小技巧: MTK平台提供了i2c-tools工具包,在调试阶段可以直接用命令行读写Sensor寄存器。命令格式大概是:i2cget -y 2 0x60 0x00。先确保命令行能通,再写驱动代码,效率高很多。

4.2 Sensor寄存器读写:驱动里的基本功

I2C调通之后,接下来就是读写Sensor内部的寄存器。每个Sensor都有一堆寄存器,用来配置分辨率、帧率、增益、曝光时间等等。

在MTK平台上,我们通常用struct i2c_msg来构造读写消息。嗯,这里要注意,读操作和写操作的流程不一样:

  • 写操作: 先发寄存器地址,再发要写的数据。一次传输搞定。
  • 读操作: 先发寄存器地址(写操作),然后重新发起始条件,再读数据。这叫“重复起始条件”。

我见过不少新手,读寄存器时忘了发寄存器地址,结果读回来的数据全是乱的。你想想看,Sensor都不知道你要读哪个寄存器,它怎么给你数据?

下面是一个典型的寄存器读写函数示例:

static int sensor_i2c_write(struct i2c_client *client, u16 reg, u8 val)
{
    struct i2c_msg msg;
    u8 buf[3];
    int ret;

    buf[0] = (reg >> 8) & 0xFF;  // 寄存器高8位
    buf[1] = reg & 0xFF;          // 寄存器低8位
    buf[2] = val;                 // 要写入的数据

    msg.addr = client->addr;
    msg.flags = 0;                // 写标志
    msg.len = 3;
    msg.buf = buf;

    ret = i2c_transfer(client->adapter, &msg, 1);
    if (ret != 1) {
        dev_err(&client->dev, "I2C write failed, reg=0x%04x\n", reg);
        return -EIO;
    }
    return 0;
}

static int sensor_i2c_read(struct i2c_client *client, u16 reg, u8 *val)
{
    struct i2c_msg msg[2];
    u8 reg_buf[2];
    u8 data_buf[1];
    int ret;

    reg_buf[0] = (reg >> 8) & 0xFF;
    reg_buf[1] = reg & 0xFF;

    msg[0].addr = client->addr;
    msg[0].flags = 0;             // 写寄存器地址
    msg[0].len = 2;
    msg[0].buf = reg_buf;

    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;      // 读数据
    msg[1].len = 1;
    msg[1].buf = data_buf;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret != 2) {
        dev_err(&client->dev, "I2C read failed, reg=0x%04x\n", reg);
        return -EIO;
    }
    *val = data_buf[0];
    return 0;
}
避坑指南: 我曾经在一个项目中,Sensor的寄存器地址是16位的,但I2C从设备只支持8位地址模式。结果我按16位发,Sensor根本不认。后来仔细看数据手册才发现,需要把寄存器地址拆成两次发送。所以,拿到Sensor后,第一件事就是仔细看数据手册的I2C章节,确认地址长度和传输格式。

4.3 PDAF与OTP读取:相位对焦和出厂校准

现在车载摄像头越来越高级,PDAF(相位检测自动对焦)基本成了标配。PDAF说白了,就是Sensor上有一部分像素被用来检测相位差,从而快速算出对焦方向。

PDAF的校准数据,通常存在Sensor的OTP(一次性可编程)存储器里。OTP里不光有PDAF校准数据,还有镜头阴影校正、白平衡参数、坏点坐标等等。这些数据是模组厂在出厂时烧录进去的,我们驱动工程师要做的就是把它读出来,然后传给ISP(图像信号处理器)。

读取OTP的流程一般是:

  1. 向Sensor发送特定命令,进入OTP读取模式。
  2. 按地址读取OTP数据,通常是一页一页读,每页128字节或256字节。
  3. 读取完成后,退出OTP模式,恢复正常工作模式。
  4. 校验数据完整性,比如检查CRC校验和。

我记得有一次,读出来的OTP数据总是有几个字节不对。排查了半天,发现是读取时序问题——OTP读取需要等待一定时间(比如10ms),让Sensor内部准备好数据。我之前的代码里没加这个延时,读出来的就是乱码。

PDAF数据在OTP里的存储格式,各家Sensor厂商都不一样。有的用线性表,有的用网格表。你需要根据数据手册解析出每个相位检测点的校正值。这部分工作很繁琐,但必须做对,否则对焦精度会受影响。

核心要点: OTP数据是模组唯一的“身份证”。同一款Sensor,不同模组的OTP数据都不一样。所以驱动里一定要实现OTP读取和校验逻辑,不能写死任何校准参数。

4.4 驱动probe流程:从设备树到设备节点

终于到了驱动probe流程。这是Sensor驱动初始化的核心环节。在MTK平台上,probe函数是驱动加载的入口,它负责完成以下工作:

  • 解析设备树: 从设备树节点中获取I2C地址、复位GPIO、电源控制等信息。
  • 初始化硬件: 上电、复位、初始化I2C通信。
  • 读取Sensor ID: 读取Sensor的芯片ID寄存器,确认硬件是否正确。
  • 加载初始配置: 写入一组初始化寄存器序列,让Sensor进入工作状态。
  • 读取OTP数据: 读取校准数据并保存到驱动私有数据结构中。
  • 注册V4L2子设备: 将Sensor注册为V4L2(Video for Linux 2)子设备,供上层调用。
  • 创建media entity: 在media controller框架中创建实体,建立数据通路。

probe函数的执行顺序很关键。我见过一个案例,有人把OTP读取放在了Sensor初始化之前,结果读出来的全是0。为什么?因为Sensor还没初始化,OTP模块根本没上电。你想想看,这能读到数据吗?

下面是一个简化的probe流程伪代码:

static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct sensor_priv *priv;
    int ret;

    // 1. 分配私有数据结构
    priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    // 2. 解析设备树,获取GPIO和电源信息
    ret = sensor_parse_dt(&client->dev, priv);
    if (ret)
        return ret;

    // 3. 硬件上电和复位
    sensor_power_on(priv);
    sensor_reset(priv);
    msleep(20);  // 等待Sensor稳定

    // 4. 读取芯片ID,确认硬件
    ret = sensor_read_id(client);
    if (ret) {
        dev_err(&client->dev, "Sensor ID mismatch\n");
        goto power_off;
    }

    // 5. 加载初始化序列
    ret = sensor_init_seq(client);
    if (ret)
        goto power_off;

    // 6. 读取OTP数据
    ret = sensor_read_otp(client, priv);
    if (ret)
        dev_warn(&client->dev, "OTP read failed, use default\n");

    // 7. 注册V4L2子设备
    ret = sensor_register_subdev(priv);
    if (ret)
        goto power_off;

    i2c_set_clientdata(client, priv);
    return 0;

power_off:
    sensor_power_off(priv);
    return ret;
}
个人经验: 在probe函数里,我习惯在每个关键步骤后加一句打印日志,比如dev_info(&client->dev, "Step X: ...")。这样调试时,通过dmesg就能一眼看出probe卡在了哪一步。别小看这个习惯,它能帮你省下大量排查时间。

好了,这节课的内容就到这里。我们讲了I2C调试、寄存器读写、PDAF与OTP读取,还有驱动probe流程。这些都是Sensor驱动开发的基础,也是你后面做全景影像的基石。下节课,我们会继续深入,讲Sensor驱动开发的下半部分——包括流控、帧同步、以及如何对接MTK的ISP pipeline。到时候见。