1. MIPI 协议
继续分析上一篇文章中 cif 驱动的 mipi 内容
1. MIPI 协议简介
MIPI CSI-2(Mobile Industry Processor Interface Camera Serial Interface 2)是移动和嵌入式领域广泛使用的高速串行摄像接口协议。
我们可以用一个通俗的比喻来理解它们的关系:MIPI 是一个庞大的"技术家族(组织)",而 CSI-2 是这个家族里专门用来传输摄像头的"一种协议标准"。
1. 什么是 MIPI?
- 全称:Mobile Industry Processor Interface(移动产业处理器接口)。
- 它是什么 :它不是指某一个具体的接口,而是一个国际组织 (MIPI Alliance),同时也是由该组织制定的一系列移动设备接口标准规范的总称。
- 它包含什么 :这个家族非常庞大,里面有针对摄像头的,也有针对屏幕的、射频的、存储的等等。例如:
- CSI (Camera Serial Interface):专门用于摄像头。
- DSI (Display Serial Interface):专门用于屏幕显示。
- SoundWire / I3C:用于音频或传感器控制。
2. 什么是 CSI-2?(具体的摄像头协议标准)
- 全称:Camera Serial Interface version 2(第二代摄像头串行接口标准)。
- 它是什么 :它是 MIPI 组织制定并发布的 、专门用于连接图像传感器(Sensor)和应用处理器(Host)的官方协议标准。
- 它解决什么问题:它定义了数据包长什么样(包头、有效负载、帧起止信号等)、数据怎么格式化(比如 RAW8, RAW10, RGB888 等),以及上层控制逻辑该怎么跑。
- 物理层(D-PHY / C-PHY) :采用差分信号传输,包含时钟通道(Clock Lane)和多个数据通道(Data Lane),支持高带宽、低功耗的串行数据传输。
以下是关于 MIPI CSI-2、D-PHY 与 C-PHY 的完整梳理与整合:一、 概念定义与层级关系
- MIPI:移动产业处理器接口标准组织与技术大类名称。
- CSI-2(Camera Serial Interface 2):MIPI 组织制定的专门用于摄像头的上层协议标准,负责规定数据的打包格式、帧同步结构及虚拟通道。
- D-PHY / C-PHY :由 MIPI 联盟制定的两种不同的底层物理层(PHY)标准与硬件规范(本身属于硬件标准和通信协议,而非软件驱动)。它们负责规定物理通道的电信号传输形式。在 Linux 操作系统中,需由芯片厂商编写对应的 PHY 驱动程序来配置和控制这些底层硬件电路。
二、 物理层(D-PHY 与 C-PHY)的核心区别
- MIPI D-PHY(经典双线制) :
- 信号结构:采用差分信号传输,每条数据通道(Data Lane)由一对差分线(DP/DN)组成。
- 时钟机制:配备专用的时钟通道(Clock Lane),采用源同步时钟机制。
- 工作模式:支持高速模式(HS,用于传输图像数据)与低功耗模式(LP,用于传输控制指令或空闲降耗)的切换。
- MIPI C-PHY(多线三相编码制) :
- 信号结构:摒弃传统两根线对,采用三根线组成一个小组(Trio/Lane)。
- 时钟机制:无专用时钟通道,时钟信号直接嵌入在数据流中,通过三根线之间的电平状态跳变恢复时钟与同步信息。
- 编码与特点:采用三相符号编码技术,具有更高的带宽效率、引脚集成度和更省引脚的优势。
- 虚拟通道(Virtual Channels):MIPI CSI-2 支持多路虚拟通道,允许通过同一物理链路复用传输来自多个摄像头的视频流。
2. 为什么 DVP 没有单独的文件,而 MIPI 有?
- DVP 接口简单:DVP(Digital Video Port)是传统的并行接口,信号包含时钟(PCLK)、行同步(HREF/HSYNC)、场同步(VSYNC)以及并行数据线。其时序直接且固定,不需要复杂的协议解析或专门的物理层(PHY)配置,因此其控制逻辑通常直接集成在主驱动或核心控制模块中,无需独立的驱动文件。
- MIPI 协议复杂 :MIPI CSI-2 涉及复杂的差分物理层(D-PHY/C-PHY 初始化与校准)、通道管理、虚拟通道分发以及数据解包对齐。为了实现模块化解耦,Rockchip 独立编写了
mipi-csi2.c来专门处理这些复杂的协议桥接工作。
3. mipi-csi2.c 是什么?它是一个驱动吗?它做了什么?
- 它是什么及是否为驱动 :
mipi-csi2.c是一个标准的 Linux Platform 驱动 ,同时在 V4L2 框架中表现为一个子设备驱动(Subdev Driver),充当摄像头 Sensor 与后端 CIF 采集控制器之间的"桥接器(Bridge)"。 - 它做了什么 :
-
生命周期与资源管理 :通过
csi2_probe注册平台驱动,管理硬件时钟(csi_clk)、复位信号及寄存器映射。 -
V4L2 媒体拓扑与异步绑定:通过异步通知机制(Async Notifier)自动发现并绑定外部 Sensor,初始化包含 Sink 端和多个虚拟通道 Source 端的 Media Pad 拓扑。
-
数据流与链路控制 :响应上层传来的
s_stream指令,协同上下游开启或关闭视频流。 -
硬件寄存器配置:配置底层 CSI Host 寄存器(如 Lane 数量、工作模式),实现数据包的接收与解包。
-
中断与错误统计 :通过双中断处理函数(
rk_csirx_irq1_handler和rk_csirx_irq2_handler)实时监控传输同步错、CRC 校验错及 PHY 层异常,并通过通知链向外抛出状态。
-
1.1 PHY

MIPI CSI-2 物理层(D-PHY 与 C-PHY)架构、带宽与硬件连接线
一、 核心架构与辅助控制链路
无论是 D-PHY 还是 C-PHY 架构,其顶层的通信双方和辅助控制机制均保持高度一致:
-
通信双方:
- Image Sensor(图像传感器):作为数据发送方,内部包含 CSI-2 TX 与物理层发送器。
- Application Processor(应用处理器):作为数据接收方,内部包含 CSI-2 RX 与物理层接收器。
-
辅助控制链路:
- 两套架构均配备 I 2 C I^2C I2C Compatible 2-wire Camera Control(双线相机控制总线),用于协同控制 AF(自动对焦)、Gyro(陀螺仪)、OIS(光学防抖)、Flash(闪光灯)以及 MEMS 等外设,并完成 Sensor 寄存器的基础配置。
二、 D-PHY 与 C-PHY 的核心区别及传输表现
在芯片接口或电路板上,它们可能采用相同的物理引脚数量(例如都支持 6-pins 配置),但在内部信号定义与带宽表现上有显著差异:
-
D-PHY 模式:
- 特点与传输:采用传统差分信令和显式时钟,拥有独立的时钟线和数据线。总带宽(Gross BW)为 5 Gbps,通道速率为 2.5 Gbps,有效可用带宽(Effective BW)为 5 Gbps(6-pins 引脚配置)。
-
C-PHY 模式:
- 特点与传输 :采用 3-phase 编码和嵌入式时钟,以 Gsps(符号速率) 传输多状态组合信号。通道符号率为 2.5 Gsps,总带宽与有效可用带宽高达 11.4 Gbps,带宽效率显著高于 D-PHY(同样为 6-pins 引脚配置)。
三、 硬件连接线与布线设计的深度差异
虽然引脚总数同为 6-pins,但两者的内部信号线定义与电路板布线要求完全不同:
-
D-PHY 的硬件连接线特点:
- 时钟与数据分离 :必须单独占用专用的差分时钟线(如
CLK+/CLK-)传输周期性同步时钟,剩余引脚组成独立的差分数据通道(Data Lanes,如DATA0+/DATA0-)。 - 以 6-pins 为例:通常分配 1 对差分线走时钟,另外 2 对差分线走数据。
- 布线要求:由于有时钟线的存在,硬件布线时需严格注意时钟线与数据线之间的长短匹配(等长布线),防止高速传输时出现严重的相位偏差和时钟抖动。
- 时钟与数据分离 :必须单独占用专用的差分时钟线(如
-
C-PHY 的硬件连接线特点:
- 无专用时钟线 :彻底摒弃传统时钟线,所有引脚全部用来传输编码后的复合信号(Embedded Clock and Data)。
- Trio(三线组)结构:将每 3 个引脚组成一个组合通道(Trio),通过 3 根线之间的电平状态组合来同时传递时钟和数据。
- 以 6-pins 为例:6 个引脚被划分为 2 个 Trio。
- 布线要求 :由于每组 Trio 内的 3 根线必须协同参与 3-phase 状态解码,对线间距、等长控制以及阻抗一致性的要求极高。
四、 硬件兼容性与全链路传输闭环
-
引脚兼容共存(Combo PHY):
- 现代主控芯片和 Sensor 的物理层内部集成了兼容电路(Combo C/D-PHY)。在电路板的同一个物理引脚焊盘上,既可以走 D-PHY 的时钟/数据差分线信号,也可以通过软件配置切换为 C-PHY 的 Trio 组合信令,无需更改硬件电路板的物理走线(即"Pin compatible coexistence supports CSI-2 over combo C/D-PHY solutions")。
-
从物理发送到接收的完整闭环:
- Sensor 发送端:内部的 CSI-2 TX 通过 D-PHY(以 Gbps 计量的独立时钟与数据线)或 C-PHY(以 Gsps 计量的 3-phase 编码嵌入式时钟)将像素流高速发射出去。
- 物理链路传输:高频信号通过硬件电路板上的物理引脚和走线,跨越链路传送到应用处理器侧。
- AP 接收端:数据最终到达应用处理器内部的物理层接收器与 CSI-2 RX,同时由双线相机控制总线协同管理各类外设,完成从底层物理传输到上层数据流接管的完整闭环。
1.2 CSI-2 协议栈

根据 MIPI CSI-2 协议栈结构图(分为发送端 Transmitter 和接收端 Receiver),CSI-2 从上到下由以下几个核心层级组成,各层的定义与职责如下:
1. 应用层(Application Layer)
- 定义:整个架构的最高层,通常对应图像传感器(Sensor)的图像输出端或应用处理器(AP)的图像接收端。
- 职责:处理原始的像素(Pixel)数据与控制(Control)信号。如图所示,它支持 6、7、8、10、12、14、15、16、18 或 24 位等多种像素位深格式。
2. 像素到字节打包/解包格式层(Pixel to Byte Packing / Unpacking Formats Layer)
- 定义:位于应用层与低层协议之间的数据格式转换层。
- 职责 :
- 发送端(Transmitter):将应用层输入的各种位深像素(如 10-bit、12-bit RAW 数据)按照规范打包(Packing)转换成统一的 8-bit 字节流(Data)。
- 接收端(Receiver):将接收到的 8-bit 字节流反向解包(Unpacking)还原成原始的像素格式。
3. 低层协议层(Low Level Protocol, LLP)
- 定义:基于包(Packet-Based Protocol)的核心传输协议层。
- 职责 :
- 负责将数据组织成具有特定包头、有效载荷和包尾的协议包。
- 提供对任意数据格式的支持(Arbitrary Data Support),管理数据的打包、分包、错误检测等底层通信协议逻辑。
4. 通道管理层(Lane Management Layer)
- 定义:负责多通道分发与合并的控制层。
- 职责 :
- 发送端(Transmitter) :进行通道分配(Lane Distribution),将打包好的 8-bit 数据流分发到多个并行的物理通道(Lane 1 到 Lane N)上。
- 接收端(Receiver) :进行通道合并(Lane Merging),将多个物理通道收到的碎片化数据重新拼装成连续的 8-bit 字节流。
5. 物理层(PHY Layer)
- 定义:整个架构的最底层,直接与硬件引脚和物理链路对接。
- 职责 :
- 负责生成或检测包的起始(Start)与停止(Stop)信令。
- 实现串行化(Serializer)与解串化(Deserializer)。
- 负责时钟生成与恢复(Clock Generation / Recovery,如 DDR 模式)。
- 处理电气特性(Electrical Layer)。
- 通过底部的硬件物理链路(包含高速单向时钟线
High Speed Unidirectional Clock以及多条高速单向数据线Lane 1至Lane N)实现数据的实际物理传输。
1.3 全链路数据流向与主控芯片处理机制
MIPI CSI-2 全链路架构 与主控芯片内部的 硬件流水线接收处理逻辑 梳理:
一、 什么是主控芯片(Application Processor, AP)?
在嵌入式系统和智能硬件中,主控芯片(通常指 SoC,如我们常说的瑞芯微 Rockchip RK3568、全志、高通、恩智浦等处理器的应用核心)是整个系统的"大脑"。
- 它是什么:它包含了 CPU、GPU、内存控制器以及专门用来处理图像的硬件模块(如 ISP、CIF 采集控制器、MIPI CSI 接收控制器)。
- 它在这里的角色:它是摄像头数据的终极消费者(接收端)。摄像头 Sensor 把拍到的画面打包送进主控芯片后,主控芯片内部的硬件和软件(如 Linux 的 V4L2 驱动框架)会负责把这些数据收下来,做进一步的图像显示、视频编码或 AI 算法处理。
二、 MIPI CSI-2 全链路数据流向与协议栈架构
在 MIPI CSI-2 架构中,数据流向遵循从上往下打包发送,再从下往上解包接收的对称分层模型。整个系统以摄像头 Sensor(发送端)和主控芯片(接收端)为两端,其完整的数据生命周期如下:
1. 发送端(Transmitter:通常在 Image Sensor 内部)的数据流向
-
应用层(Application):
- 定义与职责:指 Sensor 芯片内部最上层的源头------即感光像素阵列(Pixel Array)、自带的 ISP 或数字核心逻辑。它们负责产生最原始的像素(Pixel)和控制信号。
- 数据流转:生成的原始像素(如 10-bit 或 12-bit 的 RAW 图像)作为整个发送端协议栈的源头输入,向下送入打包层。
-
像素到字节打包层(Pixel to Byte Packing Formats):
- 数据流转:将应用层送来的各种非标位深像素数据,按照协议规范打包压缩并转换成标准的 8-bit 字节流。
-
低层协议层(Low Level Protocol, LLP):
- 数据流转:将 8-bit 字节流组织成带包头(Header)、有效载荷(Payload)和包尾(Footer)的协议数据包(Packet),并附加校验和等控制信息。
-
通道管理层(Lane Management Layer):
- 数据流转:进行通道分配(Lane Distribution),把打包好的数据流拆分,分发到多条平行的物理通道(Lane 1 到 Lane N)上去。
-
物理层(PHY Layer):
- 数据流转 :这是最底层的最后一步。PHY 层将通道管理层送来的并行字节流进行串行化(Serializer),并加上时钟信号,通过 Sensor 自身的物理引脚(如 D-PHY 的高速数据线和时钟线,或 C-PHY 的 3-phase 编码线)以 Gbps 或 Gsps 的速率发射出去。
2. 传输过程(Physical Link)
- 物理链路与引脚对接 :
- 这里的硬件引脚是双方各自独立但通过电路板物理对接的。Sensor 侧有自己的引脚(如
DATA0+,CLK+等),主控芯片(AP)侧也有对应的 CSI 硬件引脚。 - 两扇"大门"之间通过电路板上的铜皮走线(物理链路)焊死连在了一起。高频物理信号从 Sensor 的引脚出发,跨越电路板走线,最终流进主控芯片的接收引脚。
- 这里的硬件引脚是双方各自独立但通过电路板物理对接的。Sensor 侧有自己的引脚(如
3. 接收端(Receiver:即主控芯片内部的 CSI-2 RX 控制器)的数据流向
-
物理层(PHY Layer):
- 数据流转 :这是接收端的最底层第一步。PHY 层通过主控芯片自身的硬件引脚捕捉到高频物理信号,进行时钟恢复(Clock Recovery)和解串化(Deserializer),把串行信号还原成底层的并行字节流。
-
通道管理层(Lane Management Layer):
- 数据流转:进行通道合并(Lane Merging),把从多条物理 Lane 收到的碎片化数据重新拼装成连续的 8-bit 字节流。
-
低层协议层(Low Level Protocol):
- 数据流转:解析协议包的包头和包尾,检查数据包是否存在传输错误,并剥离协议封装。
-
像素到字节解包层(Byte to Pixel Unpacking Formats):
- 数据流转:将还原出来的 8-bit 字节流进行反向解包(Unpacking),恢复成最初的像素格式(如 10-bit、12-bit RAW 格式)。
-
应用层(Application / CIF / ISP,位于主控芯片内部):
- 定义与职责:与发送端对称,这里的"应用层"属于主控芯片(AP)这一侧,具体指代主控芯片内部的高度集成硬件流水线。
三、 主控芯片内部的硬件接收与处理流水线
当数据通过上述链路稳稳送入主控芯片后,主控芯片正是通过内部的硬件模块流水线对其进行深度接管和处理的:
- MIPI CSI 接收控制器:负责对接物理层引脚,跑通底层协议解析、Lane 合并与解包,将数据安全接进芯片内部。
- CIF 采集控制器:负责把解包出来的纯净像素流按帧、按行完整地采集和搬运到内存(DDR)中。
- ISP(图像信号处理器):对采集到的原始图像进行 3A(自动曝光、自动对焦、自动白平衡)、降噪、色彩校正等深度画质处理。
四、 核心总结
- 两端的"应用层"完全不同 :Sensor 的应用层是生产者(只管发) ;主控芯片的应用层(CIF/ISP)是消费者(只管收)。
- 物理引脚各归各位:信号发送时走 Sensor 的引脚,传输靠电路板铜皮,接收时进主控芯片的引脚。
- 软硬件协同闭环 :主控芯片不仅通过物理引脚和 CSI 控制器"把数据收下来",更是利用整套硬件流水线(CSI → \rightarrow → CIF → \rightarrow → ISP),将原始的物理电信号最终转化为系统可以使用的清晰图像或视频。
2. csi2_probe
c
static int csi2_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct device *dev = &pdev->dev;
struct device_node *node = pdev->dev.of_node;
struct csi2_dev *csi2 = NULL;
struct resource *res;
const struct csi2_match_data *data;
int ret, irq;
/* 从设备树的匹配表中查找与当前 platform_device 兼容的节点匹配项 */
match = of_match_node(csi2_dt_ids, node);
/* 如果匹配查找失败(返回错误指针),则直接返回对应的错误码 */
if (IS_ERR(match))
return PTR_ERR(match);
/* 获取匹配项中携带的平台特定数据(如不同芯片型号的寄存器差异配置) */
data = match->data;
/* 使用 devm_kzalloc 在内核空间为核心设备管理结构体 csi2_dev 分配内存并清零,设备卸载时会自动释放 */
csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL);
/* 如果内存分配失败,返回内存不足错误码 */
if (!csi2)
return -ENOMEM;
/* 将当前 platform 设备的设备指针保存到 csi2 结构体中 */
csi2->dev = &pdev->dev;
/* 将匹配获取的平台特定数据指针存入 csi2 结构体 */
csi2->match_data = data;
/* 初始化 V4L2 子设备(Subdev),并绑定子设备的操作函数集 csi2_subdev_ops */
v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops);
/* 将 platform 设备的私有数据指针关联到 V4L2 子设备的私有数据区域中 */
v4l2_set_subdevdata(&csi2->sd, &pdev->dev);
/* 设置 V4L2 媒体实体(media entity)的操作函数集 */
csi2->sd.entity.ops = &csi2_entity_ops;
/* 将设备指针赋给 V4L2 子设备的 dev 成员 */
csi2->sd.dev = &pdev->dev;
/* 将当前模块的拥有者标识赋给 subdev,防止模块在使用时被卸载 */
csi2->sd.owner = THIS_MODULE;
/* 为子设备设置标志位:支持生成设备节点(devnode)以及支持异步事件流 */
csi2->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
/* 将宏定义的设备名称复制到 subdev 的名称字符串中 */
ret = strscpy(csi2->sd.name, DEVICE_NAME, sizeof(csi2->sd.name));
/* 如果名称复制出错,记录一条错误日志 */
if (ret < 0)
v4l2_err(&csi2->sd, "failed to copy name\n");
/* 将 V4L2 子设备指针绑定到 platform 设备的驱动私有数据中,以便后续反向获取 */
platform_set_drvdata(pdev, &csi2->sd);
/* 批量获取设备树中定义的所有时钟资源,并保存到 csi2 的时钟批量结构中 */
csi2->clks_num = devm_clk_bulk_get_all(dev, &csi2->clks_bulk);
/* 如果获取时钟数量小于 0,说明获取失败,记录错误日志 */
if (csi2->clks_num < 0)
dev_err(dev, "failed to get csi2 clks\n");
/* 获取可选且排他的硬件复位控制数组资源 */
csi2->rsts_bulk = devm_reset_control_array_get_optional_exclusive(dev);
/* 检查获取复位资源是否出错 */
if (IS_ERR(csi2->rsts_bulk)) {
/* 如果错误不是因为"驱动需要推迟加载(EPROBE_DEFER)",则打印错误日志 */
if (PTR_ERR(csi2->rsts_bulk) != -EPROBE_DEFER)
dev_err(dev, "failed to get csi2 reset\n");
/* 返回获取复位资源时的错误码 */
return PTR_ERR(csi2->rsts_bulk);
}
/* 从 platform 设备中获取索引为 0 的内存类型资源(即寄存器基地址范围) */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/* 使用标准资源映射函数将物理寄存器地址映射为内核虚拟地址指针 */
csi2->base = devm_ioremap_resource(&pdev->dev, res);
/* 如果标准的 ioremap 资源映射失败 */
if (IS_ERR(csi2->base)) {
/* 获取资源的起始物理地址 */
resource_size_t offset = res->start;
/* 获取资源的内存大小 */
resource_size_t size = resource_size(res);
/* 打印警告日志,提示将尝试使用备用映射方式 */
dev_warn(&pdev->dev, "avoid secondary mipi resource check!\n");
/* 使用通用的 devm_ioremap 进行二次重试映射 */
csi2->base = devm_ioremap(&pdev->dev, offset, size);
/* 如果重试映射依然失败 */
if (IS_ERR(csi2->base)) {
/* 打印致命错误日志 */
dev_err(&pdev->dev, "Failed to ioremap resource\n");
/* 返回映射错误码 */
return PTR_ERR(csi2->base);
}
}
/* 根据中断名称 "csi-intr1" 获取对应的硬件中断号 */
irq = platform_get_irq_byname(pdev, "csi-intr1");
/* 如果中断号有效(大于 0) */
if (irq > 0) {
/* 请求并注册中断服务函数 rk_csirx_irq1_handler,绑定设备驱动名称与设备实例 */
ret = devm_request_irq(&pdev->dev, irq,
rk_csirx_irq1_handler, 0,
dev_driver_string(&pdev->dev),
&pdev->dev);
/* 如果中断请求失败,记录 V4L2 错误日志 */
if (ret < 0)
v4l2_err(&csi2->sd, "request csi-intr1 irq failed: %d\n",
ret);
} else {
/* 如果未找到该中断号,记录错误日志 */
v4l2_err(&csi2->sd, "No found irq csi-intr1\n");
}
/* 根据中断名称 "csi-intr2" 获取第二个硬件中断号 */
irq = platform_get_irq_byname(pdev, "csi-intr2");
/* 如果中断号有效(大于 0) */
if (irq > 0) {
/* 请求并注册第二个中断服务函数 rk_csirx_irq2_handler */
ret = devm_request_irq(&pdev->dev, irq,
rk_csirx_irq2_handler, 0,
dev_driver_string(&pdev->dev),
&pdev->dev);
/* 如果中断请求失败,记录错误日志 */
if (ret < 0)
v4l2_err(&csi2->sd, "request csi-intr2 failed: %d\n",
ret);
} else {
/* 如果未找到第二个中断号,记录错误日志 */
v4l2_err(&csi2->sd, "No found irq csi-intr2\n");
}
/* 初始化互斥锁 csi2->lock,用于后续多线程并发访问时的临界区保护 */
mutex_init(&csi2->lock);
/* 初始化 V4L2 媒体拓扑(创建输入输出 Pad 及实体链接) */
ret = csi2_media_init(&csi2->sd);
/* 如果媒体拓扑初始化失败,跳转到错误处理分支释放锁 */
if (ret < 0)
goto rmmutex;
/* 配置异步通知机制(Notifier),用于在后台自动监听并绑定外部的摄像头 Sensor 驱动 */
ret = csi2_notifier(csi2);
/* 如果异步通知配置失败,跳转到错误处理分支释放锁 */
if (ret)
goto rmmutex;
/* 执行底层的硬件复位函数,使 CSI 接收控制器寄存器恢复到默认状态 */
csi2_hw_do_reset(csi2);
/* 将当前初始化的 csi2 设备指针赋值给全局静态设备指针变量,方便其他函数引用 */
g_csi2_dev = csi2;
/* 打印 V4L2 信息日志,提示 probe 成功以及对应的父 v4l2_dev 名称 */
v4l2_info(&csi2->sd, "probe success, v4l2_dev:%s!\n", csi2->sd.v4l2_dev->name);
/* 成功完成探测,返回 0 */
return 0;
rmmutex:
/* 错误处理标签:销毁之前初始化的互斥锁 */
mutex_destroy(&csi2->lock);
/* 返回在初始化过程中捕获的错误码 */
return ret;
}
该函数 csi2_probe 是 mipi-csi2.c 驱动的核心初始化入口(探测函数)。当 Linux 内核启动、加载模块或在设备树(Device Tree)中匹配到对应的硬件节点时,内核就会自动调用该函数。
其核心作用可以概括为:为 MIPI CSI-2 接收控制器完成全套软件框架的搭建、硬件资源的申请以及上下游链路的预备。
具体分步作用如下:
-
设备识别与配置分配 :解析设备树,获取芯片型号匹配数据,并在内存中创建并初始化驱动的核心管理结构体
csi2_dev。 -
注册 V4L2 子设备(Subdev):将当前模块初始化为一个标准的 V4L2 子设备,设置其操作函数集并注册设备节点,使其能够接入 Linux 的 Media(媒体)框架。
-
获取底层硬件资源:
- 批量获取并准备好硬件运行所需的时钟 与复位控制。
- 将摄像控制器的物理寄存器地址映射为内核虚拟地址 (
csi2->base),以便代码后续读写寄存器。 - 申请并注册两个硬件中断(
csi-intr1和csi-intr2),绑定中断处理函数,用于实时监控传输同步、CRC 校验错及 PHY 层异常。
-
构建媒体拓扑与异步绑定:
- 初始化媒体实体(Entity)和数据通道的输入输出端口(Pad)。
- 配置异步通知机制(Notifier),用于在后台自动等待并绑定外部的摄像头 Sensor 驱动,打通上下游链路。
-
硬件重置与收尾:对硬件执行一次复位,使控制器寄存器恢复到初始状态,宣告整个驱动加载成功。
核心内容:
-
csi2->sd.entity.ops = &csi2_entity_ops;- 这是什么:这是 V4L2 媒体框架(Media Controller)对实体(Entity)的操作集接口。
- 它管什么:当上层需要对这个 CSI 桥接器进行链路连接、配置验证或者获取属性时,就会通过这里注册的函数来回调。它定义了这个实体在整个媒体流水线(Pipeline)里的行为规范。
-
媒体拓扑初始化:
csi2_media_init()- 这是什么 :在
csi2_probe里被调用的初始化函数。 - 它管什么 :它负责在内核里为这个 CSI 模块创建 Pad(输入输出端口) 。通常会包含一个接收来自 Sensor 数据的 Sink Pad ,以及一个将解包后数据传给后端采集控制器(如 CIF)的 Source Pad。这就像是在硬件电路上画好了"接口插槽",为后续连线做准备。
- 这是什么 :在
-
异步绑定:
csi2_notifier(csi2)- 这是什么 :整个摄像头驱动中最巧妙的机制------异步通知与绑定(Async Notifier)。
- 它管什么:因为在 Linux 中,Sensor 驱动和 MIPI-CSI 驱动加载的先后顺序是不确定的。这个函数的作用是让 CSI 驱动先"挂一个号(注册 Notifier)"在后台默默等待。一旦外挂的摄像头 Sensor 驱动加载成功并向系统报到,异步机制就会自动触发回调,把 Sensor 和 CSI 的 Pad 牢牢地"连(Link)"在一起,打通整条视频数据通路。
-
中断注册与处理:
rk_csirx_irq1_handler和rk_csirx_irq2_handler- 这是什么 :在
csi2_probe里用devm_request_irq挂载的两个硬件中断。 - 它管什么:它们是监控底层硬件健康状况的"哨兵"。当发生丢包、CRC 校验错误、行/帧同步错(Sync Error)或 PHY 层状态异常时,硬件会立刻拉起这两个中断,执行对应的处理函数来记录错误或向上抛出状态。
- 这是什么 :在
2.1 csi2_entity_ops
c
/* 定义一个名为 csi2_entity_ops 的媒体实体操作集结构体,用于配置和管理 V4L2 媒体实体(Media Entity)的行为 */
static const struct media_entity_operations csi2_entity_ops = {
/* 链路建立回调函数:当用户空间或框架将当前 CSI 实体与上下游实体(如 Sensor 或 CIF)进行连线(Link)时触发,用于校验和配置链路状态 */
.link_setup = csi2_link_setup,
/* 链路验证回调函数:直接使用 V4L2 框架提供的标准验证函数,在流开启(stream on)前校验上下游 Pad 的像素格式、分辨率等参数是否匹配 */
.link_validate = v4l2_subdev_link_validate,
};
2.1.1 csi2_link_setup
c
/* 定义一个名为 csi2_link_setup 的静态函数,用于处理 V4L2 媒体实体(Media Entity)之间的连线(Link)建立或断开请求 */
static int csi2_link_setup(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags)
{
/* 通过媒体实体指针反向获取其对应的 V4L2 子设备结构体指针 */
struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
/* 通过子设备指针获取自定义的 CSI 驱动核心控制结构体指针 */
struct csi2_dev *csi2 = sd_to_dev(sd);
struct v4l2_subdev *remote_sd;
int ret = 0;
/* 通过远端实体的 media_entity 指针获取其对应的远端 V4L2 子设备结构体指针 */
remote_sd = media_entity_to_v4l2_subdev(remote->entity);
/* 使用互斥锁保护对 csi2 结构体内链接状态变量的并发修改 */
mutex_lock(&csi2->lock);
/* 判断当前操作的本地 Pad(local pad)是源端(SOURCE,即向后传输)还是宿端(SINK,即从前端接收) */
if (local->flags & MEDIA_PAD_FL_SOURCE) {
/* 如果是 Source 端,判断当前操作是要启用(ENABLED)这条链路还是断开它 */
if (flags & MEDIA_LNK_FL_ENABLED) {
/* 检查对应的 Source 虚拟通道是否已经被其他后端的接收实体占用(即是否已经连过线了) */
if (csi2->sink_linked[local->index - 1]) {
/* 如果已经被占用,返回设备忙错误码(EBUSY) */
ret = -EBUSY;
goto out;
}
/* 将该虚拟通道对应的后端链接状态标记为已连接(true) */
csi2->sink_linked[local->index - 1] = true;
} else {
/* 如果是断开链路操作,将对应的虚拟通道链接状态标记为未连接(false) */
csi2->sink_linked[local->index - 1] = false;
}
} else {
/* 如果本地 Pad 是 Sink 端(意味着远端 remote 应该是摄像头 Sensor) */
/* 判断当前操作是要启用还是断开这条输入链路 */
if (flags & MEDIA_LNK_FL_ENABLED) {
/* 检查当前 CSI 控制器是否已经绑定/连接了其他的远端 Sensor 驱动 */
if (csi2->src_sd) {
/* 如果已经连接了别的 Sensor,返回设备忙错误码(EBUSY) */
ret = -EBUSY;
goto out;
}
/* 将远端的 Sensor 子设备指针记录到 csi2 的 src_sd 变量中,确立绑定关系 */
csi2->src_sd = remote_sd;
} else {
/* 如果是断开输入链路,清空远端 Sensor 子设备指针记录 */
csi2->src_sd = NULL;
}
}
out:
/* 解锁互斥锁 */
mutex_unlock(&csi2->lock);
/* 返回操作结果(0 表示成功,负数表示错误) */
return ret;
}
例子背景 :
在一个嵌入式 Linux 开发板(如基于 Rockchip 芯片的平台)上,连接了一个单目摄像头 Sensor(例如 IMX415)。当系统启动、驱动加载完成后,用户空间(User Space)通过 V4L2 媒体框架工具(如 media-ctl 命令)去配置管道链路,将 Sensor 输出端(Source Pad)与 MIPI CSI-2 接收控制器的输入端(Sink Pad)进行连线(Enable Link)。此时,内核会触发执行 csi2_link_setup 函数。
具体执行过程举例:
-
当用户空间执行连线操作(
flags & MEDIA_LNK_FL_ENABLED为真)时:-
本地 Pad 判断:
-
如果
local是 Sink 端(即 CSI 接收端准备接收来自 Sensor 的数据):- 代码会检查
csi2->src_sd是否已经有值。 - 假设此时没有任何其他 Sensor 占用,
csi2->src_sd为NULL,于是代码将该远端子设备(IMX415 传感器驱动的v4l2_subdev指针)赋值给csi2->src_sd。 - 连线成功,返回
0。
- 代码会检查
-
异常情况 :如果此前已经连接了另一个摄像头 A,现在又尝试把摄像头 B 连到这个 Sink 端,
csi2->src_sd不为空,代码就会判定冲突并返回-EBUSY(设备忙),拒绝这次连线。
-
-
本地 Pad 判断:
- 如果
local是 Source 端 (即 CSI 准备把解包后的数据传给后端的 CIF 或 ISP):- 代码会检查对应虚拟通道的
csi2->sink_linked[local->index - 1]状态。 - 如果该通道未被占用,则将其设为
true,建立后端传输通道。
- 代码会检查对应虚拟通道的
- 如果
-
-
当用户空间执行断开操作(
flags未包含MEDIA_LNK_FL_ENABLED)时:- 如果操作的是 Sink 端,代码会将
csi2->src_sd清空(设为NULL),解除与 IMX415 的绑定关系。 - 如果操作的是 Source 端,代码会将对应的
csi2->sink_linked[...]状态重置为false。
- 如果操作的是 Sink 端,代码会将
2.1.2 v4l2_subdev_link_validate
c
int v4l2_subdev_link_validate(struct media_link *link)
{
struct v4l2_subdev *sink;
struct v4l2_subdev_format sink_fmt, source_fmt;
int rval;
/* 获取链路源端(source pad,通常是前端 Sensor)的图像格式配置信息(如分辨率、像素格式等),保存到 source_fmt 中 */
rval = v4l2_subdev_link_validate_get_format(
link->source, &source_fmt);
/* 如果获取源端格式失败,直接返回 0(或按框架约定处理) */
if (rval < 0)
return 0;
/* 获取链路宿端(sink pad,通常是当前 MIPI CSI 接收端)的图像格式配置信息,保存到 sink_fmt 中 */
rval = v4l2_subdev_link_validate_get_format(
link->sink, &sink_fmt);
/* 如果获取宿端格式失败,直接返回 0 */
if (rval < 0)
return 0;
/* 通过链路宿端实体指针反向获取其对应的 V4L2 子设备结构体指针 */
sink = media_entity_to_v4l2_subdev(link->sink->entity);
/* 调用宿端子设备自定义的 link_validate 校验回调函数,将链路对象、源端格式和宿端格式传入,检查两端参数是否匹配 */
rval = v4l2_subdev_call(sink, pad, link_validate, link,
&source_fmt, &sink_fmt);
/* 如果宿端子设备的自定义校验函数返回的结果不是"未实现该命令(ENOIOCTLCMD)",则直接返回该校验结果 */
if (rval != -ENOIOCTLCMD)
return rval;
/* 如果宿端没有自定义校验函数,则执行 V4L2 框架提供的默认链路验证函数,对比双方的宽度、高度、像素格式等核心参数是否完全一致 */
return v4l2_subdev_link_validate_default(
sink, link, &source_fmt, &sink_fmt);
}
例子背景 :
在一个嵌入式 Linux 开发板(基于 Rockchip 平台)中,摄像头 Sensor(如 IMX415)通过 MIPI 接口连接到 MIPI CSI-2 接收控制器。在应用程序下发开启视频流指令(如执行 v4l2-ctl --stream-on)之前,V4L2 媒体框架必须先调用链路验证函数 v4l2_subdev_link_validate,检查前端 Sensor 输出的图像格式(如 RAW10、分辨率 1920x1080)与后端 MIPI CSI-2 接收端配置的接收格式是否完全一致。如果参数不匹配,系统会拒绝开启视频流。
具体执行过程举例:
-
获取源端格式(Source Format):
- 代码调用
v4l2_subdev_link_validate_get_format(link->source, &source_fmt)。 - 实际背景 :框架向前端的 Sensor 子设备查询当前配置。假设查询结果返回:Sensor 当前输出的是
RAW10格式,图像宽度为1920,高度为1080。这些参数被存入source_fmt中。
- 代码调用
-
获取宿端格式(Sink Format):
- 代码调用
v4l2_subdev_link_validate_get_format(link->sink, &sink_fmt)。 - 实际背景 :框架向后端的 MIPI CSI-2 接收子设备查询当前配置。假设查询结果返回:CSI 接收端设置的接收格式也是
RAW10,宽度为1920,高度为1080。这些参数被存入sink_fmt中。
- 代码调用
-
子设备自定义校验(Custom Validate):
- 代码通过
v4l2_subdev_call(sink, pad, link_validate, ...)尝试调用宿端(CSI 接收端)自己实现的校验函数。 - 实际背景 :如果 CSI 驱动实现了专用的校验逻辑(例如还要额外检查 MIPI 的 Lane 速率或数据包标识符),则执行该自定义函数。如果驱动没有实现(返回
-ENOIOCTLCMD),则进入下一步的默认校验。
- 代码通过
-
默认参数比对与结果返回:
-
代码执行
v4l2_subdev_link_validate_default。 -
实际背景 :内核框架会逐项比对
source_fmt和sink_fmt中的核心参数:- 像素格式是否相同(RAW10 == RAW10)?
- 图像宽度是否相同(1920 == 1920)?
- 图像高度是否相同(1080 == 1080)?
-
成功情况 :各项参数完全一致,函数返回
0,链路验证通过,允许后续启动视频流。 -
失败情况 :如果用户空间此前用命令把 Sensor 改成了 RAW12,但 CSI 接收端还停留在 RAW10,比对发现不一致,该函数会返回一个负数错误码(如
-EPIPE),内核随即报错并终止开启视频流。
-
2.2 csi2_media_init(媒体拓扑初始化)
c
static int csi2_media_init(struct v4l2_subdev *sd)
{
/* 通过 V4L2 子设备指针获取自定义的 CSI 驱动核心控制结构体指针 */
struct csi2_dev *csi2 = sd_to_dev(sd);
int i = 0, num_pads = 0;
/* 从当前平台的匹配数据(match_data)中获取该型号芯片支持的 Media Pad 总数量 */
num_pads = csi2->match_data->num_pads;
/* 循环遍历所有 Pad,根据索引初始化它们的默认方向属性:如果是指定的 Sink 索引则设为输入端,否则设为输出端 */
for (i = 0; i < num_pads; i++) {
csi2->pad[i].flags = (i == CSI2_SINK_PAD) ?
MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
}
/* 强制将特定的源端 Pad(虚拟通道 0 对应的 Source)标记为"必须连接(MUST_CONNECT)",确保拓扑完整性 */
csi2->pad[RK_CSI2X_PAD_SOURCE0].flags =
MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;
/* 强制将输入端 Pad(Sink)也标记为"必须连接(MUST_CONNECT)" */
csi2->pad[RK_CSI2_PAD_SINK].flags =
MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
/* 设置一个默认的媒体总线像素格式:8位 YUV422(UYVY)格式 */
csi2->format_mbus.code = MEDIA_BUS_FMT_UYVY8_2X8;
/* 设置扫描场格式为无场(逐行扫描) */
csi2->format_mbus.field = V4L2_FIELD_NONE;
/* 设置默认的图像宽度(使用平台宏定义的默认值) */
csi2->format_mbus.width = RKCIF_DEFAULT_WIDTH;
/* 设置默认的图像高度(使用平台宏定义的默认值) */
csi2->format_mbus.height = RKCIF_DEFAULT_HEIGHT;
/* 初始化硬件裁剪(Crop)窗口的起点纵坐标为 0 */
csi2->crop.top = 0;
/* 初始化硬件裁剪窗口的起点横坐标为 0 */
csi2->crop.left = 0;
/* 初始化硬件裁剪窗口的宽度为默认值 */
csi2->crop.width = RKCIF_DEFAULT_WIDTH;
/* 初始化硬件裁剪窗口的高度为默认值 */
csi2->crop.height = RKCIF_DEFAULT_HEIGHT;
/* 调用媒体框架的初始化函数,将定义好的 Pad 注册并挂载到 V4L2 子设备的媒体实体(Entity)上 */
return media_entity_pads_init(&sd->entity, num_pads, csi2->pad);
}
例子背景 :
在一个嵌入式 Linux 开发板(基于 Rockchip 平台)上,MIPI CSI-2 驱动在 csi2_probe 阶段执行初始化。为了让用户空间的媒体控制工具(如 media-ctl)能够识别这个 CSI 接收控制器拥有哪些输入输出接口(即 Pad),并给它设定一套初始的图像格式兜底,驱动程序调用了 csi2_media_init 函数。
具体执行过程举例:
-
获取 Pad 总数:
- 代码读取
csi2->match_data->num_pads。 - 实际背景 :假设当前 Rockchip 芯片型号的 CSI 控制器支持 5 个 Pad(包含 1 个输入 Sink Pad 用于接摄像头,以及 4 个输出 Source Pad 用于对应 4 个虚拟通道 VC0~VC3),
num_pads的值即为5。
- 代码读取
-
配置 Pad 的方向与强制连接属性:
- 循环中将索引为
CSI2_SINK_PAD的端口设为MEDIA_PAD_FL_SINK(接收端)。 - 其余端口设为
MEDIA_PAD_FL_SOURCE(发送端)。 - 特别地,将主输入端(
RK_CSI2_PAD_SINK)和主输出端(RK_CSI2X_PAD_SOURCE0)打上MEDIA_PAD_FL_MUST_CONNECT标记。 - 实际背景:这告诉内核框架,这个 CSI 模块"必须"同时连上游的 Sensor 和下游的采集控制器,否则整个视频传输链路不合法,系统不允许启动流。
- 循环中将索引为
在数字电路、多媒体框架(如 Linux 的 V4L2 媒体框架)以及各种通信协议中,Sink 和 Source 是非常经典的命名规范。它们分别代表了"数据流向的终点/接收方"与"数据流向的起点/发送方"。
- Sensor 模块 内部的尽头是数据流发源地 → \rightarrow → 叫 Source Pad。
- CSI-2 接收端 用来接外部数据的那头 → \rightarrow → 叫 Sink Pad 。
* (驱动在连线时,必须把 Sensor 的 Source 和 CSI 的 Sink 连在一起)- CSI-2 接收端 处理完、要把数据送给下游 CIF 的那一头 → \rightarrow → 叫 Source Pad。
- CIF 采集控制器 用来接 CSI 送来数据的那头 → \rightarrow → 叫 Sink Pad。
-
初始化默认图像参数(Format 与 Crop):
- 驱动为该实体赋了一套初始的图像配置:像素格式为
UYVY8_2X8,分辨率为平台宏定义的默认大小(例如1920x1080),裁剪窗口坐标从(0,0)开始、宽高同分辨率。 - 实际背景:如果在应用层下发具体配置前,有其他模块去读取该设备的媒体格式,就能直接读到这套默认兜底参数,防止空指针或未初始化导致的异常。
- 驱动为该实体赋了一套初始的图像配置:像素格式为
-
注册实体 Pad:
- 执行
media_entity_pads_init(&sd->entity, num_pads, csi2->pad)。 - 实际背景 :内核的 Media Framework 正式在内存中为该 CSI 子设备创建了 5 个标准的接口桩(Pad),并在系统的
/sys/kernel/debug/media或media-ctl -p拓扑图中呈现出来,供上层工具连线使用。
- 执行
2.3 Async Notifier(异步通知与绑定)
1. 核心目的与解决的痛点
在嵌入式 Linux 系统(如基于 Rockchip RK3568 平台的摄像头硬件系统)中,视频捕获链路通常由两个独立的硬件和驱动组成:
- MIPI-CSI 接收控制器(主机端):集成在 SoC 内部,负责协议解析。
- 图像传感器 Sensor(外设端):挂载在外部 I2C 总线上,负责采集图像并通过物理引脚发送给 SoC。
在传统驱动模型中,主机端驱动必须在初始化时直接调用外设端驱动的接口。这带来了以下三个严重痛点:
- 初始化时序死锁:Linux 内核采用多线程并行加载驱动。如果 MIPI-CSI 驱动先加载,此时挂在 I2C 总线上的 Sensor 驱动还没初始化,主机端会因为找不到外设设备而直接报错退出(Probe 失败)。
- 硬件紧耦合风险:SoC 的 CSI 控制器是通用的,而外部摄像头 Sensor 是多变的(如 OV13850、IMX415 等)。如果让主机端驱动去适配和死等某一个具体的 Sensor 驱动,会导致驱动代码失去通用性。
- 热插拔与电源管理失效:外部摄像头在运行过程中可能由于省电断电(Power Off)或物理拔插导致子设备短暂消失。如果缺乏动态解绑机制,主机会因访问失效内存而导致内核崩溃(Kernel Panic)。
V4L2 异步匹配机制的根本目的 :就是通过内核的异步通知框架(Async Notifier)作为中介,让主机端只负责"发布监听需求",外设端只负责"注册自身组件",由内核在后台完成动态匹配,从而彻底解耦初始化时序,实现多组件驱动的动态热插拔与安全闭合。
2. 三大匹配要素的深层解析
异步框架能精准把"正确的摄像头"连到"正确的 CSI 接口"上,完全依赖以下三个要素的精密配合:
① 异步通知器(Async Notifier)------ 主机端的"监听代理"
- 宿主 :由 SoC 的 MIPI-CSI 驱动程序在
probe过程中创建。 - 核心成员 :包含一个子设备链表(描述它在等谁)以及一组回调函数操作集
v4l2_async_notifier_operations。 - 职责 :它在内核中"挂号"。它根据设备树信息,明确告诉内核:"我占用了 SoC 的第 X 个 MIPI 接口,我正在等待一个能够向我这个接口输入信号的外部摄像头,请内核帮我盯着。一旦它来了,请立刻调用我的
.bound函数。"
② 异步子设备(Async Subdev)------ 外设端的"身份证明"
- 宿主:由外部摄像头 Sensor 驱动程序(如红外摄像头驱动)在 I2C 探测成功后创建。
- 核心成员 :包装了 V4L2 子设备结构体
v4l2_subdev。 - 职责:它在内核中"报到"。当 Sensor 完成上电和基础 I2C 寄存器初始化后,它将自己注册为标准子设备,并向内核宣告:"我是某某型号的摄像头,我已经就绪,正在寻找我的宿主控制器。"
③ 匹配令牌(fwnode / 固件节点)------ 唯一的"匹配依据"
- 这是匹配能够成功的唯一技术依据 ,由内核解析设备树(DTS)中的
port和endpoint节点获得。 - 机制 :在设备树中,MIPI-CSI 节点内部会有一个指向 Sensor 节点的
remote-endpoint指针;同样,Sensor 节点内部也会有一个指向 MIPI-CSI 节点的remote-endpoint指针。 - 内核异步框架会提取这两者的固件节点指针(fwnode)进行比对。只有当双方的物理拓扑连接完全指向对方时,内核才判定"匹配成功"。这保证了即使系统挂载了多个摄像头,也不会发生接口接错的现象。
3. 实例说明(以 RK3568 挂载外部摄像头为例)
为了更直观地理解,以下用设备树配置和内核的实际行为来完整模拟这一全过程。
步骤一:设备树(DTS)建立物理拓扑信物
在硬件设计上,外部摄像头 Sensor 接在 SoC 的 i2c4 总线上,数据线接在 mipi_csi2 接口上。在设备树中,它们通过 remote-endpoint 互相对指:
c
// 1. SoC 内部的 MIPI-CSI 控制器节点
&mipi_csi2 {
status = "okay";
ports {
mipi_csi2_in: port@0 {
reg = <0>;
// 信物:指向外部摄像头的输出端点
csi2_in_sensor: endpoint {
remote-endpoint = <&sensor_out_mipi>;
};
};
};
};
// 2. I2C 总线上的摄像头 Sensor 节点
&i2c4 {
status = "okay";
camera_sensor: ovi@36 {
compatible = "ov,ov13850";
reg = <0x36>;
port {
// 信物:指向 SoC 内部 MIPI-CSI 的输入端点
sensor_out_mipi: endpoint {
remote-endpoint = <&csi2_in_sensor>;
};
};
};
};
步骤二:CSI 驱动先加载(初始化 Notifier)
- 系统启动,
mipi_csi2驱动首先被加载。 - 驱动解析自己的设备树节点,发现
csi2_in_sensor引用了外部的一个节点(即sensor_out_mipi的 fwnode 令牌)。 - 驱动调用
v4l2_async_notifier_parse_fwnode_endpoints_by_port,把这个令牌存入自己的notifier.asd_list链表中。此时,通知器(Notifier)明确了它在等这个特定的令牌。 - 调用注册函数,
notifier在后台挂起监听。此时,因为摄像头驱动还没加载,数据通路一片空白。
步骤三:Sensor 驱动后加载(触发异步匹配)
- 几秒后,
i2c4总线上的ov13850传感器驱动被系统加载,执行其probe函数。 - 传感器完成上电,调用
v4l2_async_register_subdev将自己注册为异步子设备(Subdev)。 - 注册的瞬间,内核异步框架介入,拿着这个新上市的 Subdev 的设备树令牌(
sensor_out_mipi),去系统所有的监听链表中去比对。 - 成功配对 :框架发现,之前挂号的
mipi_csi2通知器正好在死等这个令牌!
步骤四:回调激活,拓扑链路正式闭合
-
内核框架立刻自动触发
csi2_async_ops结构体中的.bound回调函数,即执行csi2_notifier_bound。 -
csi2_notifier_bound拿到了已经就绪的 Sensor 的v4l2_subdev指针,开始在内核的 Media Framework 拓扑图里连线:- 找引脚 :代码在 Sensor 内部找到了带有
MEDIA_PAD_FL_SOURCE标记的引脚(即 Sensor 的硬件输出 Pad)。 - 建链路 :调用
media_create_pad_link(..., RK_CSI2_PAD_SINK, ...)。在拓扑图中,将该输出 Pad 连向主机端的RK_CSI2_PAD_SINK(接收引脚)。 - 通路使能 :调用
media_entity_setup_link(..., MEDIA_LNK_FL_ENABLED),将这条链路激活。
- 找引脚 :代码在 Sensor 内部找到了带有
-
最终结果 :此时,在用户层使用
media-ctl工具查看拓扑图,会清晰地看到ov13850的输出端和rk_csi2的输入端已经有一条实线相连,视频流数据通路正式打通,应用层可以正常打开/dev/video0进行采集。
2.4 中断注册与处理
下面去除了所有修辞与修饰性描述,严格按照代码的执行逻辑和内核API的功能,对这两个中断处理函数进行逐行标准工程注释:
c
/**
* rk_csirx_irq1_handler - CSI-2 接收控制器 1 号中断处理函数
* @irq: 中断号
* @ctx: 传递的设备上下文指针(指向 struct device)
*
* 主要职责:处理 MIPI CSI-2 链路中的帧同步、顺序、以及数据校验(CRC)等重度错误。
*/
static irqreturn_t rk_csirx_irq1_handler(int irq, void *ctx)
{
struct device *dev = ctx;
/* 通过设备结构体反向获取 MIPI CSI-2 驱动的核心控制结构体指针 */
struct csi2_dev *csi2 = sd_to_dev(dev_get_drvdata(dev));
struct csi2_err_stats *err_list = NULL;
unsigned long err_stat = 0;
u32 val;
/* 读取寄存器 CSIHOST_ERR1 的值,获取当前触发的错误状态位 */
val = read_csihost_reg(csi2->base, CSIHOST_ERR1);
if (val) {
/* 写清(Write-Clear)机制:向寄存器写入 0x0,清除当前中断状态,防止重复触发中断 */
write_csihost_reg(csi2->base, CSIHOST_ERR1, 0x0);
/* 检查是否触发 SoT(Start of Transmission)同步失败错误 */
if (val & CSIHOST_ERR1_PHYERR_SPTSYNCHS) {
err_list = &csi2->err_list[RK_CSI2_ERR_SOTSYN];
err_list->cnt++; /* 错误计数器自增 */
v4l2_err(&csi2->sd,
"ERR1: start of transmission error(no synchronization achieved), reg: 0x%x,cnt:%d\n",
val, err_list->cnt);
}
/* 检查是否触发帧开始(Frame Start)与帧结束(Frame End)不匹配错误 */
if (val & CSIHOST_ERR1_ERR_BNDRY_MATCH) {
err_list = &csi2->err_list[RK_CSI2_ERR_FS_FE_MIS];
err_list->cnt++; /* 错误计数器自增 */
v4l2_err(&csi2->sd,
"ERR1: error matching frame start with frame end, reg: 0x%x,cnt:%d\n",
val, err_list->cnt);
}
/* 检查是否触发帧序列号错误(Frame Sequence Error) */
if (val & CSIHOST_ERR1_ERR_SEQ) {
err_list = &csi2->err_list[RK_CSI2_ERR_FRM_SEQ_ERR];
err_list->cnt++; /* 错误计数器自增 */
v4l2_err(&csi2->sd,
"ERR1: incorrect frame sequence detected, reg: 0x%x,cnt:%d\n",
val, err_list->cnt);
}
/* 检查是否触发单次数据包 CRC 校验错误(调试级别记录) */
if (val & CSIHOST_ERR1_ERR_FRM_DATA) {
err_list = &csi2->err_list[RK_CSI2_ERR_CRC_ONCE];
err_list->cnt++; /* 错误计数器自增 */
v4l2_dbg(1, csi2_debug, &csi2->sd,
"ERR1: at least one crc error, reg: 0x%x\n,cnt:%d", val, err_list->cnt);
}
/* 检查是否触发持续性数据包 CRC 校验错误(错误级别记录) */
if (val & CSIHOST_ERR1_ERR_CRC) {
err_list = &csi2->err_list[RK_CSI2_ERR_CRC];
err_list->cnt++; /* 错误计数器自增 */
v4l2_err(&csi2->sd,
"ERR1: crc errors, reg: 0x%x, cnt:%d\n",
val, err_list->cnt);
}
/* 累计当前控制器的总错误次数 */
csi2->err_list[RK_CSI2_ERR_ALL].cnt++;
/* 拼接错误状态码:高 8 位存储帧不匹配错误数,低 8 位存储总错误数 */
err_stat = ((csi2->err_list[RK_CSI2_ERR_FS_FE_MIS].cnt & 0xff) << 8) |
((csi2->err_list[RK_CSI2_ERR_ALL].cnt) & 0xff);
/* 触发原子通知链,将错误状态码广播给订阅了 g_csi_host_chain 的其他内核模块(如 VICAP/ISP) */
atomic_notifier_call_chain(&g_csi_host_chain,
err_stat,
NULL);
}
/* 返回中断处理完成标志,告知内核该中断已得到妥善处理 */
return IRQ_HANDLED;
}
c
/**
* rk_csirx_irq2_handler - CSI-2 接收控制器 2 号中断处理函数
* @irq: 中断号
* @ctx: 传递的设备上下文指针(指向 struct device)
*
* 主要职责:处理 MIPI CSI-2 物理层(D-PHY)状态异常及包头纠错(ECC)通知。
*/
static irqreturn_t rk_csirx_irq2_handler(int irq, void *ctx)
{
struct device *dev = ctx;
/* 通过设备结构体反向获取 MIPI CSI-2 驱动的核心控制结构体指针 */
struct csi2_dev *csi2 = sd_to_dev(dev_get_drvdata(dev));
u32 val;
/* 读取寄存器 CSIHOST_ERR2 的值,获取当前触发的物理层错误状态位 */
val = read_csihost_reg(csi2->base, CSIHOST_ERR2);
if (val) {
/* 检查是否触发超低功耗状态(ULPM)切换相关的转义模式错误 */
if (val & CSIHOST_ERR2_PHYERR_ESC)
v4l2_err(&csi2->sd, "ERR2: escape entry error(ULPM), reg: 0x%x\n", val);
/* 检查是否触发 SoT 传输错误,但此状态下硬件内部硬件仍可保持数据同步 */
if (val & CSIHOST_ERR2_PHYERR_SOTHS)
v4l2_err(&csi2->sd,
"ERR2: start of transmission error(synchronization can still be achieved), reg: 0x%x\n",
val);
/* 检查是否触发包头数据单比特错误,且该错误已被硬件内建的 ECC 电路自动纠正 */
if (val & CSIHOST_ERR2_ECC_CORRECTED)
v4l2_dbg(1, csi2_debug, &csi2->sd,
"ERR2: header error detected and corrected, reg: 0x%x\n",
val);
/* 检查是否触发了未识别或未实现的数据类型(Data Type)错误 */
if (val & CSIHOST_ERR2_ERR_ID)
v4l2_err(&csi2->sd,
"ERR2: unrecognized or unimplemented data type detected, reg: 0x%x\n",
val);
/* 检查是否触发物理层高速接收错误码 */
if (val & CSIHOST_ERR2_PHYERR_CODEHS)
v4l2_err(&csi2->sd, "ERR2: receiv error code, reg: 0x%x\n", val);
}
/* 返回中断处理完成标志 */
return IRQ_HANDLED;
}
这两个中断处理函数的核心作用是作为 MIPI CSI-2 硬件控制器的"错误诊断与状态上报哨兵"。它们在视频流传输过程中实时监控物理链路和协议层的健康状况,负责将硬件产生的异常信号转化为内核日志,并通过内核通知链向整个摄像头子系统广播错误状态。
一、 函数核心作用
1. rk_csirx_irq1_handler(重度数据与帧级错误监控)
- 作用 :主要监控和记录数据包和数据帧层面的严重破坏 (如数据被干扰、数据流丢失或错位)。它负责累加错误次数,打印错误日志,并负责通过
atomic_notifier_call_chain将拼装好的错误码err_stat动态广播给下游模块(如 VICAP 或 ISP),通知其进行丢帧或硬件复位处理。
2. rk_csirx_irq2_handler(物理层与协议头状态监控)
- 作用 :主要监控 MIPI D-PHY 物理层的底层状态切换异常 (如超低功耗模式异常、高速传输前导码错误)以及包头数据的合法性。它还负责记录被硬件 ECC 电路成功自动修复的轻微包头单比特错误。
二、 实际工程调试案例说明
这两个函数在实际开发板(如基于 RK3568 的自研硬件)调试中,能指导工程师精准定位以下三类硬件或驱动故障:
案例 1:排查物理布线受干扰或阻抗不匹配故障(物理缺陷)
-
现象:硬件打通后能出图,但画面出现大面积绿线、花屏或严重的图像撕裂。
-
函数触发:此时,MIPI 信号在高速传输线路上受到电磁干扰,导致数据包数据损坏。硬件校验失败,直接拉起 1 号中断。
-
日志表现:内核日志中会高频闪烁:
textrk_csi2: ERR1: crc errors, reg: 0x00000010, cnt:1425 -
工程结论:I2C 寄存器控制完全正常,但 MIPI 数据线受干扰导致大量的 CRC 校验错。工程师根据该日志应停止软件调试,直接去用示波器测量 MIPI 引脚的波形,或者优化硬件的走线阻抗和屏蔽。
案例 2:排查 Sensor 输出格式配置不匹配故障(软件配置错)
-
现象:摄像头 Sensor 驱动成功 Probe 并且上电开流(Stream On),但是应用层通过 V4L2 接口死活抓不到任何图像数据,阻塞在读取状态。
-
函数触发:外部摄像头 Sensor 已经被配置为输出 RAW12 格式的数据,但主控端 RK3568 的 MIPI CSI-2 控制器在初始化时只注册了支持 RAW10 或 YUV422 接收,导致硬件无法识别数据流头部的标志位,直接拉起 2 号中断。
-
日志表现:内核日志打印:
textrk_csi2: ERR2: unrecognized or unimplemented data type detected, reg: 0x00000008 -
工程结论 :
CSIHOST_ERR2_ERR_ID状态位置 1。说明主控和 Sensor 两端协商的 Data Type(数据格式) 没对上,软件工程师需要去核对 Sensor 寄存器手册,修改驱动使两端的像素格式保持一致。
案例 3:排查 Sensor 物理供电不稳定或掉包故障(供电/时序缺陷)
-
现象:采集视频流时,画面偶尔发生极其短暂的卡顿,但随后能自行恢复。
-
函数触发:当摄像头 Sensor 的电源由于瞬时大负荷发生电压抖动时,可能导致其短暂停止数据发送。这会导致当前一帧图像的结束信号(Frame End)丢失,主控直接检测到帧起止对不上或帧序列错,触发 1 号中断。
-
链式反应 :函数内部执行
atomic_notifier_call_chain,将错误上报给下游的 VICAP 捕获模块。VICAP 收到通知后,知道当前这帧数据已经损坏,自动将其丢弃并复位硬件 DMA 计数器,强行等待下一帧正确的开始信号(Frame Start),从而避免了系统死锁。 -
日志表现:内核日志打印:
textrk_csi2: ERR1: error matching frame start with frame end, reg: 0x00000002, cnt:5 -
工程结论 :系统存在零星的丢帧和帧边界失配(
ERR_BNDRY_MATCH),但内核通知链机制成功起到了保护作用,防止了驱动崩溃,接下来需要重点排查摄像头的供电稳定性。
3. v4l2_subdev_video_ops
c
static const struct v4l2_subdev_video_ops csi2_video_ops = {
.s_stream = csi2_s_stream,
.g_mbus_config = csi2_g_mbus_config,
};
1. 结构体定义与本质
static const struct v4l2_subdev_video_ops csi2_video_ops 是 V4L2(Video for Linux Two)子设备框架中标准定义的视频操作回调函数集结构体 。它以常量(const)形式存在,存储了指向当前驱动自定义实现的函数指针。
2. 成员变量与函数映射关系
该结构体将 V4L2 框架的标准接口与当前 MIPI CSI-2 驱动的具体实现函数进行了静态绑定:
.s_stream:指向csi2_s_stream函数。该接口由内核或后级驱动调用,用以控制 MIPI CSI-2 硬件接收控制器视频流的开启(Stream ON)与关闭(Stream OFF)。.g_mbus_config:指向csi2_g_mbus_config函数。该接口用以获取媒体总线(Media Bus)的硬件物理配置参数(如 MIPI 占用的 Lane 数量、通道模式等)。
3. 在驱动架构中的核心作用
① 实现多态接口解耦
Linux 内核 V4L2 框架与下游驱动(如 ISP 或视频捕获通用驱动)仅通过标准宏 v4l2_subdev_call(sd, video, s_stream, enable) 来下发流控制指令。后级驱动无需感知具体芯片平台的私有函数命名。内核会通过 csi2_video_ops 结构体中的映射关系,自动将通用指令路由至对应的 csi2_s_stream 或 csi2_g_mbus_config 函数。
② 提供操作函数的注册入口
该结构体随后会被挂载到更高级别的子设备总操作集 struct v4l2_subdev_ops 中,并在 csi2_probe 阶段伴随整个子设备注册流程提交给内核,使当前驱动的视频流控制函数在系统全局生效。
3.1 csi2_s_stream
c
/**
* csi2_s_stream - 控制 MIPI CSI-2 控制器视频流的开启与关闭
* @sd: 指向当前 V4L2 子设备(v4l2_subdev)的指针
* @enable: 流控制标志,1 表示开启(Stream ON),0 表示关闭(Stream OFF)
*
* 返回值:0 表示操作成功,负数表示底层硬件配置出错
*/
static int csi2_s_stream(struct v4l2_subdev *sd, int enable)
{
/* 通过 v4l2_subdev 指针反向获取 MIPI CSI-2 驱动的核心控制结构体指针 */
struct csi2_dev *csi2 = sd_to_dev(sd);
int ret = 0;
/* 获取互斥锁,保护 csi2->stream_count 等临界区变量,防止多线程并发操作 */
mutex_lock(&csi2->lock);
/* 打印当前开流/关流的状态信息,以及绑定的上游 Sensor(src_sd)的指针和名称 */
dev_err(csi2->dev, "stream %s, src_sd: %p, sd_name:%s\n",
enable ? "on" : "off",
csi2->src_sd, csi2->src_sd->name);
/*
* 引用计数状态机控制逻辑:
* 只有当 stream_count 从 0 变 1(开启)或者从 1 变 0(关闭)时,才需要真正配置硬件。
*
* 逻辑条件拆解:
* 1. 当 enable = 1 时,!enable = 0。若 stream_count != 0,说明硬件已处于运行状态,直接跳转更新计数。
* 2. 当 enable = 0 时,!enable = 1。若 stream_count != 1,说明还有其他用户在使用,不能关闭硬件,直接跳转更新计数。
*/
if (csi2->stream_count != !enable)
goto update_count;
dev_err(csi2->dev, "stream %s\n", enable ? "ON" : "OFF");
/* 根据输入参数,真正调用底层函数来操作硬件寄存器 */
if (enable)
ret = csi2_start(csi2); /* 开启 MIPI CSI-2 控制器硬件与时钟 */
else
csi2_stop(csi2); /* 关闭 MIPI CSI-2 控制器硬件,进入休眠状态 */
if (ret)
goto out; /* 若硬件配置失败,跳过计数器更新,直接退出 */
update_count:
/* 更新引用计数:enable为1则加1,enable为0则减1 */
csi2->stream_count += enable ? 1 : -1;
/* 防止计数器减到负数进行下溢保护 */
if (csi2->stream_count < 0)
csi2->stream_count = 0;
out:
/* 释放互斥锁 */
mutex_unlock(&csi2->lock);
return ret;
}
函数作用说明
csi2_s_stream 函数是 MIPI CSI-2 控制器驱动中负责管理硬件生命周期与视频流控制的总开关。其核心职责包括:
- 管理硬件控制状态机(引用计数) :
由于 Linux 用户空间可能存在多个应用同时读取摄像头数据(或者后级 ISP/VICAP 模块有多次调用请求),底层的硬件寄存器配置只能在"纯关闭 → \rightarrow → 首次开启"和"最后一次关闭 → \rightarrow → 纯关闭"时执行。该函数通过内部的stream_count变量实现硬件操作的去重。 - 执行底层硬件的唤醒与休眠 :
通过调用csi2_start(csi2)和csi2_stop(csi2),在正确的时间点对 MIPI D-PHY 进行上电、初始化物理通道(Lane)、激活硬件时钟,或者在关闭视频流时安全断电以降低功耗。
实际工程调试案例说明
案例一:利用引用计数机制支持多路数据流(正常业务流程)
- 背景:硬件平台上的摄像头正在被一个后台预览进程打开采集(数据流正常传输)。此时,另一个图像算法进程尝试同时读取该摄像头的图像。
- 执行逻辑 :
- 进程 A 首次开启视频流,后级驱动调用
csi2_s_stream(sd, 1)。此时csi2->stream_count为 0,满足条件,执行csi2_start(csi2),硬件启动。随后stream_count自增为 1。 - 进程 B 尝试读取,后级驱动再次调用
csi2_s_stream(sd, 1)。此时csi2->stream_count为 1,由于1 != 0条件成立,触发goto update_count;。 - 最终表现 :驱动跳过了重复配置底层寄存器的步骤,避免了因二次初始化硬件导致当前视频传输中断,仅将
stream_count更新为 2。这保证了底层硬件运行的稳定连续性。
- 进程 A 首次开启视频流,后级驱动调用
案例二:排查"应用层采集假死且内核无报错"故障(软件调试流程)
1. 故障现象
在开发板上运行应用程序读取 /dev/video0,程序未发生崩溃或显式报错,但始终阻塞在数据读取接口,无法获取任何图像数据,应用层界面呈现卡死状态。
2. 排查过程(利用 csi2_s_stream 的日志输出)
通过 dmesg 查看内核日志,重点观察 csi2_s_stream 内部的两处 dev_err 打印输出(注:代码中虽然使用 dev_err 级别,但在此驱动中被用作流程追踪日志)。
场景 A:日志仅打印了第一处,第二处缺失
内核日志输出:
text
stream on, src_sd: 0xffffffc0xxxx, sd_name:ov13850
(此处无后续"stream ON"日志)
- 逻辑推导 :第一行日志正常输出了摄像头子设备名称
ov13850,证明异步绑定机制正常 ,CSI 控制器已锁定了正确的数据源。但第二行大写的stream ON缺失,说明代码在中间触发了if (csi2->stream_count != !enable)条件,直接执行了goto update_count;。 - 诊断结论 :此为非首次开流 ,底层 MIPI 硬件在本次调用中被直接跳过,未执行任何物理初始化。若此时系统确实无图,需向前追溯首次开流(即
stream_count从 0 变 1 的那次调用)时硬件是否启动失败。
场景 B:两处日志均正常打印,但后续无图
内核日志输出:
text
stream on, src_sd: 0xffffffc0xxxx, sd_name:ov13850
stream ON
- 逻辑推导 :两行日志均顺利打印,证明驱动判定这是首次开流 ,且执行流已穿过引用计数条件,正式调用了底层的硬件配置函数
ret = csi2_start(csi2);。 - 诊断结论 :若此时
ret返回 0(即无报错)但应用层依然拿不到图,说明软件逻辑和寄存器下发全部正常,故障点位于物理信号层。此时需要使用示波器等硬件工具,进一步排查摄像头 Sensor 芯片在开流后,其 MIPI PHY 物理层是否未正确输出 CLK 时钟信号或 Data 数据信号。
3.1.1 "前级/后级"与"上游/下游"
在嵌入式 Linux 图像子系统(V4L2 与 Media Framework)中,"前级/后级"与"上游/下游"是指硬件设备在视频数据流向链路中所处的物理位置与逻辑关系。
这两个概念的划分依据完全由视频数据流(Data Flow)的传递方向决定。
1. 概念的定义
- 视频数据流向的物理事实:摄像头 Sensor 采集到光电信号并转化为数字像素,通过物理走线发送给主控芯片的 MIPI 接收器,MIPI 接收器解包后送入 CIF/ISP 控制器,最后通过 DMA 写入系统内存。
- 上游 / 前级(Source / Upstream) :靠近视频数据产生源头的一端。在链路上,每一个模块的输入端所对接的模块,就是它的前级或上游。
- 下游 / 后级(Sink / Downstream):靠近视频数据最终目的地(内存/应用层)的一端。在链路上,每一个模块的输出端所去往的下一站,就是它的后级或下游。
2. 结合具体函数的链路结构说明
以研究的 RK3568 拓扑为例,数据流向与层级关系如下:
text
数据物理流向 ───► ───► ───► ───► ───► ───► ───► ───► ───► ───► ───► ───► ───►
[ 摄像头 Sensor ] ──► [ MIPI CSI-2 接收器 ] ──► [ RKCIF / ISP 捕获器 ]
(最上游 / 最前级) (居中:既是下游也是上游) (最下游 / 最后级)
针对具体的驱动与函数,其角色定位为:
① 针对 MIPI CSI-2 驱动(csi2_s_stream 所在模块)
- 它的上游/前级 是 摄像头 Sensor 驱动。因为数据是从 Sensor 流向 MIPI 接收器的。
- 它的下游/后级 是 RKCIF 驱动(
rkcif_start_streaming所在模块)。因为 MIPI 接收器解析后的数据必须递交给 RKCIF。
② 针对 RKCIF 驱动(rkcif_start_streaming 所在模块)
- 它的上游/前级 是 MIPI CSI-2 驱动。
- 由于它直接通过 DMA 将数据写入内存并生成
/dev/video0供用户空间读取,在硬件拓扑中,它就是整个摄像头的最下游/最后级驱动。
3. 数据流向与控制流向的对立统一
在驱动架构中,这两个词还决定了控制命令的传递方向。
- 数据流(自上而下):像素数据从最前级(Sensor)出发,流经中游(MIPI CSI-2),最后到达最后级(RKCIF)写入内存。
- 控制流(自下而上反向传导) :当开启视频流时,应用程序首先对最后级(RKCIF)的
rkcif_start_streaming发令,最后级驱动再反向调用其上游(MIPI CSI-2)的csi2_s_stream,MIPI 驱动最后调用最前级(Sensor)的开流接口。
3.1.2 rkcif_start_streaming 与 csi2_s_stream 的关系
1. 函数所属的驱动模块与层级
rkcif_start_streaming:属于 RKCIF(Rockchip Camera Interface)驱动 ,是后级(下游)捕获驱动。它直接对接 V4L2 核心层的 VB2(Videobuf2)内存管理框架,负责生成/dev/videoX设备节点。csi2_s_stream:属于 MIPI CSI-2 接收控制器驱动,是前级(上游)子设备驱动。它负责管理 MIPI 物理层(D-PHY)及协议层的控制器状态。
2. 调用顺序与因果关系
在视频流启动过程中,这两个函数存在严格的先后调用顺序与因果关系,执行流自下而上反向传递:
text
【应用层】 ioctl(fd, VIDIOC_STREAMON)
│
▼
【RKCIF 驱动】 执行 rkcif_start_streaming() ──► 配置 DMA 内存缓冲区并激活 CIF 控制器
│
▼ (通过 v4l2_subdev_call 向上传递)
【MIPI CSI-2 驱动】 执行 csi2_s_stream() ────► 激活 D-PHY 物理层并开启时钟接收
│
▼ (继续通过 v4l2_subdev_call 向上传递)
【Sensor 驱动】 执行 sensor_s_stream() ──────► 触发摄像头外设寄存器开始输出信号
- 引发源(因) :应用程序下发开流指令后,内核首先调用
rkcif_start_streaming。该函数在后端开辟 DMA 内存空间,并使能 RKCIF 硬件的捕获功能。 - 传导结果(果) :后级配置完成后,
rkcif_start_streaming内部会通过 V4L2 子设备调用接口v4l2_subdev_call(..., video, s_stream, 1)向前级通知。该调用经由框架路由,最终触发了csi2_s_stream的执行。
3. 核心职责差异
rkcif_start_streaming解决的是数据能否写入系统内存的问题。它的核心是控制 DMA 控制器、管理队列中的内存块(Buffer),属于数据流的终点控制。csi2_s_stream解决的是物理链路能否互通和解析的问题。它的核心是控制 MIPI D-PHY 的上电时序、Lane 数量映射以及协议层解包,属于数据流的中道控制。
3.2 csi2_g_mbus_config
c
/**
* csi2_g_mbus_config - 获取 MIPI CSI-2 子设备的媒体总线(Media Bus)配置参数
* @sd: 指向当前 MIPI CSI-2 子设备(v4l2_subdev)的指针
* @mbus: 指向 V4L2 媒体总线配置结构体(v4l2_mbus_config)的指针,用于传回配置数据
*
* 返回值:始终返回 0,表示配置获取流程执行完成
*/
static int csi2_g_mbus_config(struct v4l2_subdev *sd,
struct v4l2_mbus_config *mbus)
{
/* 通过 v4l2_subdev 指针反向获取 MIPI CSI-2 驱动的核心控制结构体指针 */
struct csi2_dev *csi2 = sd_to_dev(sd);
/* 调用拓扑查找函数,获取当前在管道中与 MIPI 接收器对接的远端摄像头 Sensor 子设备指针 */
struct v4l2_subdev *sensor_sd = get_remote_sensor(sd);
int ret;
/*
* 向上游传令:优先调用前级摄像头 Sensor 驱动的 g_mbus_config 接口。
* 尝试直接获取 Sensor 自身输出端所设定的 MIPI 总线硬件参数(如 Lane 数量、通道映射等)。
*/
ret = v4l2_subdev_call(sensor_sd, video, g_mbus_config, mbus);
/*
* 如果上游 Sensor 驱动没有实现 g_mbus_config 接口(返回非 0 错误码),
* 则由当前 MIPI CSI-2 驱动提供硬件自身的默认配置作为保底策略。
*/
if (ret) {
/* 显式声明媒体总线类型为 MIPI CSI-2 标准 */
mbus->type = V4L2_MBUS_CSI2;
/* 复制设备树中解析出的 MIPI 控制器硬件属性标志位 */
mbus->flags = csi2->bus.flags;
/*
* 根据设备树配置的实际数据通道(Data Lanes)数量,通过位运算动态生成对应的标志位。
* 例如:若 num_data_lanes = 4,则将第 (4-1)=3 位(即 BIT(3))置 1,通告后级当前使用 4 通道传输。
*/
mbus->flags |= BIT(csi2->bus.num_data_lanes - 1);
}
return 0;
}
函数作用说明
csi2_g_mbus_config 函数的核心职责是在视频流打通前,向后级捕获驱动报告当前 MIPI 链路的物理硬件配置信息。其核心作用包括:
- 链路物理参数握手 :
后级驱动(如 RKCIF)在配置其硬件捕获寄存器前,必须知晓数据流是通过几条线(Lanes)传过来的、采用的是什么协议。该函数负责填充并返回这些关键的物理总线参数。 - 前级配置优先与保底兼容 :
该函数体现了跨驱动的协商机制。它首先询问前级 Sensor:"你当前配置输出几条 Lane?",如果 Sensor 驱动支持并返回了参数,则直接沿用 Sensor 的物理配置;如果 Sensor 驱动为老旧驱动或未实现该接口,则读取自身驱动在设备树(DTS)中解析出的硬件默认参数进行兜底上报,确保管道逻辑不中断。
实际工程调试案例说明
案例:排查"因 Lane 数量配置不匹配导致后级捕获硬件报错或不出图"故障
-
硬件背景 :在开发板上接入了一款 4 通道(4 Lanes)的红外摄像头 Sensor,且主控 RK3568 的设备树中也将 MIPI 接收器配置为了
num-data-lanes = <4>;。 -
软件调用流程:
- 应用程序发起开流,后级 RKCIF 驱动 在初始化捕获管道时,必须知道前级传过来的数据总线规格。
- RKCIF 驱动向发送调用请求:
v4l2_subdev_call(csi2_sd, video, g_mbus_config, &my_mbus_config);。 - 此请求路由进入
csi2_g_mbus_config。 - 函数首先调用
get_remote_sensor(sd)找到了你的红外摄像头子设备,并尝试执行v4l2_subdev_call(sensor_sd, ...)。
-
异常排查逻辑:
- 分支情况 A(Sensor 驱动完备):如果 Sensor 驱动正确返回了自身的配置,MIPI 驱动透传该配置给 RKCIF。RKCIF 拿到物理参数后,据此配置自身的硬件寄存器开辟 4 通道数据接收通道。
- 分支情况 B(Sensor 驱动未实现此接口) :调用 Sensor 报错(
ret非 0)。代码进入if (ret)分支,MIPI 驱动读取自身结构体内的csi2->bus.num_data_lanes(数值为 4),通过BIT(4 - 1)即BIT(3)写入mbus->flags,将"4 通道"这一硬件物理事实强行上报给 RKCIF。 - 故障定位价值 :如果在调试中发现 RKCIF 报错提示总线宽度不匹配,你可以在
if (ret)内外加打印,观察csi2->bus.num_data_lanes的实际值。如果此处由于设备树写错解析成了 1 或 2,即使外部硬件接了 4 根线,后级 RKCIF 也会因为拿到了错误的通道配置而关闭多余的接收总线,导致硬件产生丢包或不出图故障。