2. Sensor驱动基础

在RK平台Camera系统中,Sensor驱动是连接硬件与上层ISP的桥梁。本章聚焦于驱动开发的核心基础:I2C通信协议、MIPI CSI-2接口、Sensor寄存器读写操作以及V4L2子设备驱动模型。掌握这些内容,是后续进行ISP图像调优联动的先决条件。

2.1 I2C通信协议

I2C(Inter-Integrated Circuit)是Sensor与主控SoC之间最常用的控制总线,用于配置Sensor内部寄存器,如曝光时间、增益、帧率、输出格式等。I2C采用主从架构,RK SoC作为主设备,Sensor作为从设备。

2.1.1 I2C总线物理层与协议层

特性 描述
信号线 SCL(时钟线)、SDA(数据线),均为开漏输出,需上拉电阻
地址 7位或10位从机地址,Sensor通常使用7位地址(如0x30)
速率 标准模式100kHz,快速模式400kHz,高速模式3.4MHz
传输方向 主设备控制时钟,主发从收(写)或主收从发(读)
起始条件 SCL高电平时,SDA由高变低
停止条件 SCL高电平时,SDA由低变高

2.1.2 I2C写操作时序

写操作是驱动中最常用的操作,用于向Sensor的指定寄存器地址写入配置值。标准写操作流程如下:

  1. 起始信号:主设备发送START条件。
  2. 从机地址+写位:发送7位从机地址,最后一位为0(写操作)。
  3. ACK应答:从机拉低SDA应答。
  4. 寄存器地址:发送16位或8位寄存器地址(高字节在前)。
  5. ACK应答:从机应答。
  6. 写入数据:发送8位数据。
  7. ACK应答:从机应答。
  8. 停止信号:主设备发送STOP条件。

关键点:多数Sensor支持连续写(burst write),即发送寄存器地址后连续发送多个字节数据,地址自动递增,可大幅提高配置效率。

2.1.3 I2C读操作时序

读操作用于读取Sensor的状态寄存器或当前配置值。由于I2C总线是半双工,读操作需要先写寄存器地址,再重新发起读传输:

  1. 起始信号:主设备发送START条件。
  2. 从机地址+写位:发送从机地址,写位为0。
  3. ACK应答:从机应答。
  4. 寄存器地址:发送要读取的寄存器地址。
  5. ACK应答:从机应答。
  6. 重复起始信号:主设备再次发送START条件。
  7. 从机地址+读位:发送从机地址,读位为1。
  8. ACK应答:从机应答。
  9. 读取数据:主设备读取8位数据,主设备发送NACK表示读取结束。
  10. 停止信号:主设备发送STOP条件。

驱动开发提示:在RK平台中,I2C读写操作通常通过Linux内核的I2C子系统API完成,如i2c_master_send()和i2c_master_recv(),或使用i2c_transfer()进行复合传输。

2.2 MIPI CSI-2接口

MIPI CSI-2(Camera Serial Interface 2)是移动设备中最主流的摄像头图像数据传输接口。Sensor通过CSI-2将原始图像数据(RAW Bayer、YUV等)传输到RK SoC的ISP或DVP模块。

2.2.1 CSI-2物理层与协议层

层级 说明
PHY层(D-PHY) 差分信号传输,包含时钟通道(CLK)和数据通道(DATA0~DATA3),每个通道一对差分线
Lane数量 1~4 Lane,常用2 Lane或4 Lane,带宽随Lane数线性增加
协议层 定义数据包格式:短包(帧起始/结束、行起始/结束)和长包(图像数据)
数据类型 RAW8/10/12/14、YUV422、RGB565等,通过Data Type(DT)字段标识
虚拟通道 支持4个虚拟通道(VC0~VC3),可复用同一物理链路传输多路数据

2.2.2 CSI-2数据包结构

长包(Long Packet)是传输图像数据的主要形式,其结构如下:

  • 包首(Packet Header):4字节,包含数据类型(DT)、虚拟通道(VC)、字计数(WC)和ECC校验。
  • 数据负载(Data Payload):图像像素数据,字节对齐。
  • 包尾(Packet Footer):2字节CRC校验。

常见问题:CSI-2链路不稳定通常表现为图像花屏、丢帧或无法出图。排查方向包括:时钟频率匹配、Lane数配置、差分信号质量(SI)、以及PHY的均衡和预加重设置。

2.3 Sensor寄存器读写操作

Sensor的寄存器读写是驱动开发中最频繁的操作。RK平台通常通过I2C或SPI总线访问Sensor寄存器。下面以I2C为例,展示典型的读写实现。

2.3.1 寄存器读写函数实现

/* sensor_i2c.c - Sensor I2C读写示例 */
#include <linux/i2c.h>
#include <linux/delay.h>

/* 写16位寄存器地址,8位数据 */
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
    int ret;
    struct i2c_msg msg;
    u8 buf[3];

    buf[0] = (reg >> 8) & 0xFF;  /* 寄存器高字节 */
    buf[1] = reg & 0xFF;         /* 寄存器低字节 */
    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 < 0) {
        dev_err(&client->dev, "I2C write reg 0x%04x failed: %d\n", reg, ret);
        return ret;
    }

    return 0;
}

/* 读16位寄存器地址,8位数据 */
static int sensor_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
    int ret;
    struct i2c_msg msg[2];
    u8 reg_buf[2];
    u8 data_buf[1];

    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 reg 0x%04x failed: %d\n", reg, ret);
        return ret < 0 ? ret : -EIO;
    }

    *val = data_buf[0];
    return 0;
}

2.3.2 批量寄存器配置

Sensor初始化通常需要配置大量寄存器。驱动中常使用结构体数组定义初始化序列:

/* sensor_init.h - 寄存器配置表 */
struct sensor_regval {
    u16 reg;
    u8 val;
};

/* 初始化序列示例 */
static const struct sensor_regval sensor_init_regs[] = {
    {0x0103, 0x01},  /* 软件复位 */
    {0x0100, 0x00},  /* 进入待机模式 */
    {0x3034, 0x1A},  /* PLL配置 */
    {0x3035, 0x21},
    {0x3036, 0x69},
    {0x3037, 0x03},
    {0x3038, 0x00},
    {0x3039, 0x00},
    {0x303A, 0x00},
    {0x303B, 0x1C},
    {0x3002, 0x00},  /* 输出格式配置 */
    {0x3016, 0x08},  /* MIPI配置 */
    {0x3018, 0x44},
    {0x3020, 0x00},
    {0x3021, 0x23},
    {0x3022, 0x01},
    {0x3820, 0x46},  /* 裁剪与窗口配置 */
    {0x3821, 0x00},
    {0x3830, 0x00},
    {0x3836, 0x00},
    {0x3837, 0x00},
    {0x0100, 0x01},  /* 进入流模式 */
};

/* 批量写入函数 */
static int sensor_write_array(struct i2c_client *client,
                              const struct sensor_regval *regs, int size)
{
    int i, ret;

    for (i = 0; i < size; i++) {
        ret = sensor_write_reg(client, regs[i].reg, regs[i].val);
        if (ret < 0) {
            dev_err(&client->dev, "Failed to write reg 0x%04x\n", regs[i].reg);
            return ret;
        }
        /* 某些寄存器写入后需要延时 */
        if (regs[i].reg == 0x0103) /* 软件复位后等待 */
            msleep(10);
    }

    return 0;
}

最佳实践:在RK平台驱动中,建议将寄存器配置表与驱动代码分离,使用DTS(Device Tree)或单独的头文件管理,便于ISP调优阶段快速调整参数。

2.4 V4L2子设备驱动模型

V4L2(Video for Linux 2)是Linux内核中视频设备的标准框架。在RK平台中,Sensor驱动通常实现为V4L2子设备(subdev),通过Media Controller框架与ISP、MIPI DPHY等设备协同工作。

2.4.1 V4L2子设备核心结构

结构体/接口 作用
v4l2_subdev 子设备核心结构,包含名称、内部ops、控制句柄等
v4l2_subdev_ops 操作函数集,包含core、video、pad、sensor等ops
v4l2_subdev_video_ops 视频操作:s_stream(流开关)、g_mbus_config(总线配置)等
v4l2_subdev_pad_ops Pad操作:get/set_fmt(格式协商)、enum_mbus_code(枚举格式)等
v4l2_subdev_sensor_ops Sensor专用操作:g_skip_frames(跳帧数)、g_skip_status等
media_entity 媒体实体,用于Media Controller拓扑连接

2.4.2 Sensor驱动注册流程

在RK平台中,Sensor驱动的注册通常遵循以下步骤:

  1. I2C驱动注册:通过module_i2c_driver()注册I2C驱动,在probe()中完成初始化。
  2. 分配子设备:使用v4l2_device_register_subdev()注册V4L2子设备。
  3. 设置操作函数:填充v4l2_subdev_ops,实现s_stream、set_fmt等回调。
  4. 媒体实体初始化:设置media_entity的Pad数量和类型(源端/接收端)。
  5. 电源与时钟管理:在probe()中获取regulator、clk、gpio等资源。
  6. 初始化Sensor:调用寄存器配置表,完成Sensor初始化。

2.4.3 关键回调函数实现示例

/* sensor_v4l2.c - V4L2子设备回调实现 */

/* s_stream: 开启/关闭视频流 */
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    struct sensor *sensor = to_sensor(sd);
    int ret;

    if (enable) {
        /* 配置MIPI时序 */
        sensor_set_mipi_timing(sensor);
        /* 写入流开启寄存器 */
        ret = sensor_write_reg(sensor->client, 0x0100, 0x01);
        if (ret < 0)
            return ret;
        sensor->streaming = true;
        dev_dbg(sensor->dev, "Stream ON\n");
    } else {
        /* 写入流关闭寄存器 */
        ret = sensor_write_reg(sensor->client, 0x0100, 0x00);
        if (ret < 0)
            return ret;
        sensor->streaming = false;
        dev_dbg(sensor->dev, "Stream OFF\n");
    }

    return 0;
}

/* set_fmt: 设置输出格式 */
static int sensor_set_fmt(struct v4l2_subdev *sd,
                          struct v4l2_subdev_state *state,
                          struct v4l2_subdev_format *fmt)
{
    struct sensor *sensor = to_sensor(sd);
    struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;

    /* 只支持RAW10格式 */
    if (mbus_fmt->code != MEDIA_BUS_FMT_SBGGR10_1X10)
        mbus_fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10;

    /* 限制分辨率范围 */
    mbus_fmt->width = clamp(mbus_fmt->width, 640U, 1920U);
    mbus_fmt->height = clamp(mbus_fmt->height, 480U, 1080U);

    /* 更新Sensor内部寄存器 */
    sensor_set_resolution(sensor, mbus_fmt->width, mbus_fmt->height);

    return 0;
}

/* 子设备操作函数集 */
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_subdev_ops = {
    .video = &sensor_video_ops,
    .pad = &sensor_pad_ops,
};

2.4.4 Media Controller拓扑

在RK平台中,完整的Camera数据通路涉及多个子设备:Sensor → MIPI DPHY → ISP → 视频节点。Media Controller框架通过media_create_pad_link()建立这些设备之间的连接。

调试技巧:使用media-ctl工具可以查看和修改Media Controller拓扑,是排查数据通路问题的利器。例如:media-ctl -p打印当前拓扑,media-ctl -l '"sensor":0->"rkisp-isp":0[1]'建立连接。

2.5 本章知识结构图

Sensor驱动基础知识结构 I2C通信协议 MIPI CSI-2接口 寄存器读写操作 V4L2子设备模型 子模块 • 物理层与协议层 • 写操作时序 • 读操作时序 子模块 • D-PHY物理层 • 数据包结构 • 虚拟通道 子模块 • 单寄存器读写 • 批量配置表 • 延时与依赖 子模块 • subdev结构 • 回调函数实现 • Media拓扑 RK平台Sensor驱动集成 ISP图像调优联动基础 说明:本章四大模块是Sensor驱动开发的基石,后续ISP调优联动将在此基础上展开。

2.6 本章小结

本章系统介绍了Sensor驱动开发的四大核心基础:

  • I2C通信协议:掌握了I2C的物理层、协议层以及读写操作时序,这是配置Sensor的基础。
  • MIPI CSI-2接口:理解了CSI-2的D-PHY物理层、数据包结构和虚拟通道概念,这是图像数据传输的通道。
  • 寄存器读写操作:通过代码示例学习了单寄存器读写和批量配置的实现方法,这是驱动开发的基本功。
  • V4L2子设备驱动模型:掌握了V4L2子设备的核心结构、注册流程和关键回调函数,这是驱动与内核框架对接的标准方式。