4. Linux内核与V4L2框架

MIPI CSI-2 接收器,说白了就是摄像头和 SoC 之间的「翻译官」。物理层用 D-PHY 传差分信号,协议层把比特流拆成帧、行、像素。R-Car 的 CSI-2 模块集成度很高,但坑也不少。这一章我带你从物理层一路捅到协议层,最后聊聊怎么让系统在出错时自己缓过来。

4.1 D-PHY 配置:从时序到阻抗

D-PHY 配置是 CSI-2 驱动的第一道坎。R-Car 的 CSI-2 硬核支持 D-PHY v1.1,最高 1.5 Gbps/lane。但实际项目中,我很少跑满这个值——信号完整性才是瓶颈。

4.1.1 关键时序参数

参数 说明 典型值(1.2Gbps)
THS-SETTLE HS 传输建立时间 145 ns + 4 UI
THS-ZERO HS 传输前导零时间 105 ns + 6 UI
TCLK-ZERO 时钟通道前导零时间 300 ns
TCLK-POST 时钟通道后置时间 60 ns + 52 UI
注意:THS-SETTLE 配短了会导致数据采样窗口偏移,配长了浪费带宽。我建议用示波器实测 LP→HS 切换沿,反推实际 settle 时间,再回填寄存器。

4.1.2 阻抗匹配与偏置电压

R-Car 的 D-PHY 接收端内置 100Ω 差分终端电阻,但 PCB 走线阻抗必须控制在 100Ω ±10%。偏置电压(Vterm)通常设为 200 mV,与发送端共模电压匹配。

我曾经遇到一个案子:摄像头模组和 SoC 分属两块板,通过 FPC 连接。FPC 阻抗只有 85Ω,导致眼图闭合。最后在接收端并联了外部 120Ω 电阻,才把等效阻抗拉回 100Ω。

4.2 协议层解析:从字节到图像

CSI-2 协议层分三层:底层协议(Low-Level Protocol, LLP)、像素/字节打包层、应用层。R-Car 的硬件加速器能自动处理大部分 LLP 工作,但驱动得配好打包格式。

4.2.1 长包与短包

  • 短包(Short Packet):32 位,用于帧同步、行同步、ECC。驱动只需读取状态寄存器,判断帧起始/结束。
  • 长包(Long Packet):包含 32 位包头、可变长数据负载、16 位 CRC。R-Car 的 DMA 引擎可以直接把负载搬运到内存。

核心流程:

  1. 接收短包 FS(Frame Start)→ 触发 VSYNC 中断
  2. 接收长包行数据 → DMA 写入 buffer
  3. 接收短包 FE(Frame End)→ 触发帧完成中断

4.2.2 数据类型(DT)与格式映射

CSI-2 用 6 位 Data Type 字段标识像素格式。R-Car 的 CSI-2 模块支持 RAW8/10/12、YUV422、RGB888 等常见格式。驱动需要把 sensor 输出的 DT 映射到接收器的解包配置。

/* R-Car CSI-2 数据类型配置示例 */
struct rcsi2_dt_cfg {
    u8 data_type;       /* 0x2A = RAW10, 0x1E = YUV422 8bit */
    u8 bytes_per_pixel; /* 解包后每像素字节数 */
    u8 packing;         /* 0: 无打包, 1: RAW10 打包, 2: RAW12 打包 */
};

static const struct rcsi2_dt_cfg dt_table[] = {
    { 0x2A, 2, 1 },  /* RAW10 → 2字节/pixel, 10位打包 */
    { 0x1E, 2, 0 },  /* YUV422 → 2字节/pixel, 无打包 */
    { 0x2B, 3, 2 },  /* RAW12 → 3字节/pixel, 12位打包 */
};

4.3 VC(Virtual Channel)处理:多路复用的艺术

VC 是 CSI-2 的精髓。一条 MIPI 链路上最多 4 个 VC,每个 VC 可以传输独立的图像流。R-Car 的 CSI-2 接收器支持 4 个 VC 的独立解包和 DMA 通道。

4.3.1 VC 路由与过滤

驱动需要配置 VC 过滤寄存器,决定哪些 VC 的数据被接收。我习惯的做法:

  • VC0:主图像流(RAW10)
  • VC1:副图像流(YUV 预览)
  • VC2:元数据(AE/AWB 统计)
  • VC3:保留或调试

经验之谈:如果 sensor 同时输出 RAW 和 YUV,务必确认两个 VC 的帧同步是否对齐。我曾经遇到 VC0 帧率 30fps、VC1 帧率 15fps,结果 DMA 缓冲区互相踩踏。解决方案是给每个 VC 分配独立的环形 buffer。

4.3.2 VC 与 DMA 通道绑定

R-Car 的 CSI-2 模块有 4 个 DMA 请求通道,每个通道可以绑定一个 VC。驱动初始化时,需要建立 VC→DMA 通道的映射表。

/* VC 到 DMA 通道的绑定 */
struct rcsi2_vc_map {
    u8 vc_id;       /* 0-3 */
    u8 dma_chan;    /* 0-3 */
    dma_addr_t buf_phys; /* DMA buffer 物理地址 */
    u32 buf_size;   /* 每个 buffer 大小 */
};

/* 配置示例:VC0 → DMA0, VC1 → DMA1 */
struct rcsi2_vc_map vc_config[] = {
    { .vc_id = 0, .dma_chan = 0, .buf_size = 0x200000 },
    { .vc_id = 1, .dma_chan = 1, .buf_size = 0x100000 },
};

4.4 错误恢复机制:让系统自己站起来

MIPI 链路在汽车环境下很容易受干扰。R-Car 的 CSI-2 模块提供了丰富的错误检测和恢复能力,但驱动得会用。

4.4.1 常见错误类型

错误类型 检测方式 典型原因
ECC 错误 包头 3 位 ECC 校验失败 信号完整性差、走线过长
CRC 错误 长包 16 位 CRC 校验失败 数据负载被干扰
帧同步丢失 FS/FE 短包缺失或乱序 sensor 异常、时钟抖动
LP 状态异常 LP→HS 切换超时 D-PHY 时序配置错误

4.4.2 恢复策略

我总结了一套三级恢复策略:

  1. 软恢复:清错误标志位,重置 DMA 指针,丢弃当前帧。适用于偶发 ECC/CRC 错误。
  2. 硬恢复:复位 CSI-2 模块内部状态机,重新初始化 D-PHY。适用于帧同步丢失或 LP 状态异常。
  3. 链路重建:先复位 sensor(通过 I2C 或 GPIO),再复位 CSI-2 接收器。适用于连续错误或 sensor 无响应。

避坑指南:我曾经在硬恢复时忘了关闭 DMA,导致恢复后 DMA 从错误地址继续搬运数据,直接写穿了系统内存。正确的顺序是:停止 DMA → 复位 CSI-2 → 重新配置 VC/DMA → 启动传输。

4.4.3 错误统计与上报

驱动应该维护错误计数器,并在超过阈值时触发链路重建。我通常的做法:

/* 错误统计结构 */
struct rcsi2_err_stats {
    u32 ecc_err_cnt;
    u32 crc_err_cnt;
    u32 sync_loss_cnt;
    u32 lp_err_cnt;
    unsigned long last_reset_jiffies;
};

/* 错误处理回调 */
static void rcsi2_handle_error(struct rcsi2_device *rcsi2,
                               enum rcsi2_err_type type)
{
    struct rcsi2_err_stats *stats = &rcsi2->err_stats;

    switch (type) {
    case ECC_ERR:
        stats->ecc_err_cnt++;
        if (stats->ecc_err_cnt > 10)
            rcsi2_soft_reset(rcsi2); /* 软恢复 */
        break;
    case SYNC_LOSS:
        stats->sync_loss_cnt++;
        rcsi2_hard_reset(rcsi2); /* 硬恢复 */
        break;
    }
}

4.5 知识结构总览

下面这张图把本章的核心逻辑串起来了。从 D-PHY 物理层到 VC 数据分发,再到错误恢复闭环,你可以把它当作调试时的检查清单。

MIPI CSI-2 接收器驱动知识结构 MIPI CSI-2 接收器驱动知识结构 D-PHY 配置 时序 / 阻抗 / 偏置 协议层解析 长包 / 短包 / DT 映射 VC 处理 路由 / 过滤 / DMA 绑定 错误恢复机制 ECC/CRC 检测 → 软恢复 → 硬恢复 → 链路重建 调试与优化闭环 示波器验证 / 错误统计 / 性能调优 💡 调试要点 • 先用示波器测量 D-PHY 眼图,确认 THS-SETTLE 和 TCLK-POST 是否达标 • 错误恢复的阈值不要设太敏感,偶发 ECC 错误直接丢弃帧即可