上一篇文章已经讲解过摄像头数据流动环节的第三部分------CIF驱动+SDITF驱动,接下来进行最后一部分的讲解,也就是ISP驱动加载过程的解析:
Sensor (OV13855) ➔ DPHY (物理层) ➔ CSI Host (控制器) ➔ VICAP(CIF) ➔ ISP
驱动源码解析:ISP驱动
一、设备树节点
内核会根据此节点生成一个platform_device。
cpp
&rkisp0_vir1 {
status = "disabled";
port {
#address-cells = <1>;
#size-cells = <0>;
isp0_vir1: endpoint@0 {
reg = <0>;
remote-endpoint = <&mipi2_lvds_sditf>;
};
};
};
rkisp0_vir1: rkisp0-vir1 {
compatible = "rockchip,rkisp-vir";
rockchip,hw = <&rkisp0>;
status = "disabled";
};
二、驱动代码解析(probe函数)
cpp
drivers/media/platform/rockchip/isp/dev.c
大部分内容就是之前的套路,下面给出其中关键的一些功能函数,可以看到这里先注册了一个media子系统**(/dev/media1),然后注册各类设备节点**:
cpp
static int rkisp_plat_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct v4l2_device *v4l2_dev;
struct rkisp_device *isp_dev;
int i, ret, mult = 1;
// ......
isp_dev->media_dev.dev = dev;
isp_dev->media_dev.ops = &rkisp_media_ops;
// ......
ret = v4l2_device_register(isp_dev->dev, &isp_dev->v4l2_dev); // 注册 v4l2_device 结构体
media_device_init(&isp_dev->media_dev); // 初始化 media 子系统
ret = media_device_register(&isp_dev->media_dev); // 注册 media 子系统
// ......
ret = rkisp_register_platform_subdevs(isp_dev); // 注册各种设备节点(重点!!!)
// ......
return 0;
// ......
}
下面是 rkisp_register_platform_subdevs 的函数原型:
cpp
/**
* rkisp_register_platform_subdevs - 注册 ISP 平台的所有子设备
* @dev: ISP 设备结构体
*
* 功能:初始化 ISP 驱动的所有功能模块
*
* ISP 架构概述:
* - ISP 子设备:核心图像处理单元
* - CSI 子设备:接收 MIPI CSI-2 数据
* - Bridge 子设备:连接不同数据源(CIF/VICAP)
* - Stream 设备:输出处理后的视频流(主路/自拍路/裁剪路)
* - DMARX 设备:从内存读取原始数据
* - Stats 设备:统计信息输出(3A 算法用)
* - Params 设备:参数配置输入
* - Luma 设备:亮度信息输出
*
* 返回值:0表示成功,负数表示错误码
*/
static int rkisp_register_platform_subdevs(struct rkisp_device *dev)
{
int ret;
/* ========== 1. 注册 ISP 核心子设备 ========== */
/*
* 注册 ISP 子设备(核心图像处理单元):
* - 负责图像信号处理(去噪、白平衡、色彩校正等)
* - 作为 V4L2 子设备,支持独立配置
* - 创建 /dev/v4l-subdevX 节点
*
* 功能模块:
* - Demosaic(去马赛克)
* - Noise Reduction(降噪)
* - Color Correction(色彩校正)
* - Gamma Correction(伽马校正)
* - Sharpening(锐化)
* - Tone Mapping(色调映射)
*/
ret = rkisp_register_isp_subdev(dev, &dev->v4l2_dev);
/* ========== 2. 注册 CSI 子设备 ========== */
/*
* 注册 CSI(Camera Serial Interface)子设备:
* - 接收 MIPI CSI-2 数据
* - 解析 CSI-2 协议(数据包、虚拟通道)
* - 支持多虚拟通道(VC0-VC3)
* - 转换为 ISP 可处理的格式
*
* 与 CIF 的 DPHY 类似,但位于 ISP 内部
*/
ret = rkisp_register_csi_subdev(dev, &dev->v4l2_dev);
/* ========== 3. 注册 Bridge 子设备 ========== */
/*
* 注册 Bridge(桥接)子设备:
* - 连接不同的数据源到 ISP
* - 支持从 CIF/VICAP 接收数据
* - 支持从内存读取数据(Offline 模式)
* - 数据格式转换和路由
*
* 数据源类型:
* - Online:直接从 Sensor 经 CIF 实时传输
* - Offline:从内存读取预先存储的数据
* - ReadBack:从 ISP 输出回读进行二次处理
*/
ret = rkisp_register_bridge_subdev(dev, &dev->v4l2_dev);
/* ========== 4. 注册视频流设备(核心输出)========== */
/*
* 注册多个视频流设备:
* - 主路(Mainpath):全分辨率输出,用于拍照/录像
* - 自拍路(Selfpath):缩小分辨率输出,用于预览
* - 裁剪路(Croppath,部分芯片):裁剪区域输出
*
* 类比 CIF:
* - CIF 的 stream[0-3] 对应 MIPI 虚拟通道
* - ISP 的 stream 对应不同的处理路径
*/
ret = rkisp_register_stream_vdevs(dev);
/* ========== 5. 注册 DMARX 设备(内存读取)========== */
/*
* 注册 DMA Read(从内存读取)设备:
* - 用于 Offline 模式
* - 从内存读取原始图像数据(Raw Bayer)
* - 送入 ISP 进行处理
*
* 应用场景:
* - 二次处理已保存的 Raw 数据
* - 调试和测试 ISP 算法
* - 回放模式
*
* 生成设备节点(例):/dev/video3
*/
ret = rkisp_register_dmarx_vdev(dev);
/* ========== 6. 注册 Stats 设备(统计信息输出)========== */
/*
* 注册统计信息设备:
* - 输出 ISP 统计数据,供 3A 算法使用
* - 3A = Auto Exposure(自动曝光)
* Auto White Balance(自动白平衡)
* Auto Focus(自动对焦)
*
* 统计信息包括:
* - 亮度直方图(Histogram)
* - 自动曝光统计(AE Stats)
* - 自动白平衡统计(AWB Stats)
* - 自动对焦统计(AF Stats)
*
* 生成设备节点:/dev/video4(例)
*
* 工作流程:
* ISP 处理 → 统计信息 → /dev/video4 → 用户空间 3A 库 → 计算新参数 → Params 设备
*/
ret = rkisp_register_stats_vdev(&dev->stats_vdev, &dev->v4l2_dev, dev);
/* ========== 7. 注册 Params 设备(参数配置输入)========== */
/*
* 注册参数配置设备:
* - 接收用户空间发送的 ISP 配置参数
* - 应用到 ISP 硬件寄存器
* - 实现动态调整(每帧可更新)
*
* 可配置参数:
* - 曝光时间、增益
* - 白平衡系数
* - 色彩矩阵
* - 降噪强度
* - 锐化强度
* - Gamma 曲线
* - 色调映射参数
*
* 生成设备节点(例):/dev/video5
*
* 工作流程:
* 用户空间 3A 库 → 新参数 → /dev/video5 → ISP 驱动 → 硬件寄存器
*/
ret = rkisp_register_params_vdev(&dev->params_vdev, &dev->v4l2_dev, dev);
/* ========== 8. 注册 Luma 设备(亮度信息输出)========== */
/*
* 注册亮度信息设备:
* - 输出简化的亮度统计信息
* - 用于快速 AE(自动曝光)调整
* - 比 Stats 设备更轻量级
*
* 应用场景:
* - 实时曝光控制
* - 降低 CPU 负担(不需要完整统计信息)
*
* 生成设备节点(例):/dev/video6
*/
ret = rkisp_register_luma_vdev(&dev->luma_vdev, &dev->v4l2_dev, dev);
/* ========== 9. 注册异步子设备通知器(核心机制)========== */
/*
* 启动 V4L2 异步通知机制:
* - 解析设备树,查找上游设备(Sensor/CIF/VICAP)
* - 注册 notifier,等待上游设备注册
* - 当上游设备就绪时,触发 bound 回调
* - 当所有依赖都就绪时,触发 complete 回调
*
* 等待的设备:
* - Sensor 子设备
* - CIF 设备(如果是 CIF → ISP 路径)
* - VICAP 设备(如果是 VICAP → ISP 路径)
* - Lens 控制器(可选)
*
* 这是我们之前讨论过的异步绑定机制!
*
* 回调流程:
* - bound:上游设备注册时 → 建立 media link
* - complete:所有依赖就绪 → 注册设备节点
*/
ret = isp_subdev_notifier(dev);
/* ========== 10. 其他初始化(省略部分)========== */
// ......
return ret;
}
2.1 rkisp_register_isp_subdev函数
分配、设置、注册一个subdev,subdev的名字为"-isp-subdev"
cpp
int rkisp_register_isp_subdev(struct rkisp_device *isp_dev,
struct v4l2_device *v4l2_dev)
{
struct rkisp_isp_subdev *isp_sdev = &isp_dev->isp_sdev;
struct v4l2_subdev *sd = &isp_sdev->sd;
int ret;
// ......
v4l2_subdev_init(sd, &rkisp_isp_sd_ops); // 初始化subdev
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
sd->entity.ops = &rkisp_isp_sd_media_ops;
sd->entity.function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
snprintf(sd->name, sizeof(sd->name), ISP_SUBDEV_NAME); // "-isp-subdev"
isp_sdev->pads[RKISP_ISP_PAD_SINK].flags =
MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
isp_sdev->pads[RKISP_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
isp_sdev->pads[RKISP_ISP_PAD_SOURCE_PATH].flags = MEDIA_PAD_FL_SOURCE;
isp_sdev->pads[RKISP_ISP_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&sd->entity, RKISP_ISP_PAD_MAX,
isp_sdev->pads);
// ......
sd->grp_id = GRP_ID_ISP;
ret = v4l2_device_register_subdev(v4l2_dev, sd); // 注册 subdev
// .......
}
2. 2 rkisp_register_csi_subdev函数
本例中,使用的芯片为rk3588s,isp的硬件版本为ISP_V30,所以此函数直接退出。
cpp
int rkisp_register_csi_subdev(struct rkisp_device *dev,
struct v4l2_device *v4l2_dev)
{
struct rkisp_csi_device *csi_dev = &dev->csi_dev;
struct v4l2_subdev *sd;
int ret;
// ......
if (dev->isp_ver == ISP_V20 || dev->isp_ver == ISP_V21) {
csi_dev->max_pad = CSI_PAD_MAX;
csi_dev->pads[CSI_SRC_CH1].flags = MEDIA_PAD_FL_SOURCE;
csi_dev->pads[CSI_SRC_CH2].flags = MEDIA_PAD_FL_SOURCE;
csi_dev->pads[CSI_SRC_CH3].flags = MEDIA_PAD_FL_SOURCE;
csi_dev->pads[CSI_SRC_CH4].flags = MEDIA_PAD_FL_SOURCE;
} else if (dev->isp_ver == ISP_V30 || dev->isp_ver == ISP_V32) { // ----走此分支,退出函数-----
return 0;
}
// ......
}
2.3 rkisp_register_bridge_subdev函数
同理,此函数没有注册subdev。
cpp
int rkisp_register_bridge_subdev(struct rkisp_device *dev,
struct v4l2_device *v4l2_dev)
{
struct rkisp_bridge_device *bridge = &dev->br_dev;
struct v4l2_subdev *sd;
struct media_entity *source, *sink;
int ret;
memset(bridge, 0, sizeof(*bridge));
if ((dev->isp_ver != ISP_V20 && dev->isp_ver != ISP_V30) || // ----走此分支,退出函数-----
check_remote_node(dev) < 0)
return 0;
// ......
}
2.4 rkisp_register_stream_vdevs函数
cpp
int rkisp_register_stream_vdevs(struct rkisp_device *dev)
{
struct rkisp_capture_device *cap_dev = &dev->cap_dev;
struct stream_config *st_cfg = &rkisp_mp_stream_config;
int ret = 0;
// ......
} else if (dev->isp_ver == ISP_V30) { // ----isp版本是V30,走此分支-----
st_cfg->max_rsz_width = dev->hw_dev->is_unite ?
CIF_ISP_INPUT_W_MAX_V30_UNITE : CIF_ISP_INPUT_W_MAX_V30;
st_cfg->max_rsz_height = dev->hw_dev->is_unite ?
CIF_ISP_INPUT_H_MAX_V30_UNITE : CIF_ISP_INPUT_H_MAX_V30;
ret = rkisp_register_stream_v30(dev); // 调用此函数
}
// ......
INIT_WORK(&cap_dev->fast_work, rkisp_stream_fast);
return ret;
}
下面的函数注册了4个video_device,分别是:mainpath、selfpath、fbcpath、iqtool。
cpp
int rkisp_register_stream_v30(struct rkisp_device *dev)
{
struct rkisp_capture_device *cap_dev = &dev->cap_dev;
int ret;
ret = rkisp_stream_init(dev, RKISP_STREAM_MP); // "_mainpath"
if (ret < 0)
goto err;
ret = rkisp_stream_init(dev, RKISP_STREAM_SP); // "_selfpath"
if (ret < 0)
goto err_free_mp;
ret = rkisp_stream_init(dev, RKISP_STREAM_FBC); // "_fbcpath"
if (ret < 0)
goto err_free_sp;
ret = rkisp_stream_init(dev, RKISP_STREAM_VIR); // "_iqtool"
if (ret < 0)
goto err_free_fbc;
return 0;
// ......
}
2.5 rkisp_register_dmarx_vdev函数
这个函数注册3个video_device,分别是:rawrd0_m、rawrd2_s、rawrd1_l。
cpp
int rkisp_register_dmarx_vdev(struct rkisp_device *dev)
{
struct rkisp_dmarx_device *dmarx_dev = &dev->dmarx_dev;
int ret = 0;
memset(dmarx_dev, 0, sizeof(*dmarx_dev));
dmarx_dev->ispdev = dev;
#ifdef RKISP_DMAREAD_EN
ret = dmarx_init(dev, RKISP_STREAM_DMARX);
if (ret < 0)
goto err;
#endif
if (dev->isp_ver == ISP_V20 ||
dev->isp_ver == ISP_V21 ||
dev->isp_ver == ISP_V30 ||
dev->isp_ver == ISP_V32) {
ret = dmarx_init(dev, RKISP_STREAM_RAWRD0); // 注册video_device:"_rawrd0_m"
if (ret < 0)
goto err_free_dmarx;
ret = dmarx_init(dev, RKISP_STREAM_RAWRD2); // 注册video_device:"_rawrd2_s"
if (ret < 0)
goto err_free_dmarx0;
}
if (dev->isp_ver == ISP_V20 || dev->isp_ver == ISP_V30) {
ret = dmarx_init(dev, RKISP_STREAM_RAWRD1); // 注册video_device:"_rawrd1_l"
if (ret < 0)
goto err_free_dmarx2;
}
return 0;
// ......
}
2.6 rkisp_register_stats_vdev函数
此函数注册1个video_device,即:statistic。
cpp
int rkisp_register_stats_vdev(struct rkisp_isp_stats_vdev *stats_vdev,
struct v4l2_device *v4l2_dev,
struct rkisp_device *dev)
{
int ret;
struct rkisp_vdev_node *node = &stats_vdev->vnode;
struct video_device *vdev = &node->vdev;
struct media_entity *source, *sink;
// ......
strlcpy(vdev->name, STATS_NAME, sizeof(vdev->name)); // "-statistics"
// ......
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); // 注册video_device
// ......
}
2.7 rkisp_register_params_vdev函数
注册 input-params 设备节点
cpp
int rkisp_register_params_vdev(struct rkisp_isp_params_vdev *params_vdev,
struct v4l2_device *v4l2_dev,
struct rkisp_device *dev)
{
int ret;
struct rkisp_vdev_node *node = ¶ms_vdev->vnode;
struct video_device *vdev = &node->vdev;
struct media_entity *source, *sink;
// ......
strlcpy(vdev->name, PARAMS_NAME, sizeof(vdev->name)); // "-input-params"
vdev->ioctl_ops = &rkisp_params_ioctl;
vdev->fops = &rkisp_params_fops;
vdev->release = video_device_release_empty;
// ......
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); // 注册video_device
// ......
}
2.8 rkisp_register_luma_vdev函数
此函数什么也没干,直接退出。
cpp
int rkisp_register_luma_vdev(struct rkisp_luma_vdev *luma_vdev,
struct v4l2_device *v4l2_dev,
struct rkisp_device *dev)
{
int ret;
struct rkisp_vdev_node *node = &luma_vdev->vnode;
struct video_device *vdev = &node->vdev;
struct media_entity *source, *sink;
luma_vdev->dev = dev;
if (dev->isp_ver != ISP_V20) // isp版本是V30,走此分支,结束函数
return 0;
// ......
}
2.9 isp_subdev_notifier函数
这个函数中,初始化了一个异步通知器,并解析了isp所依赖的设备,最后注册它。
cpp
static const struct v4l2_async_notifier_operations subdev_notifier_ops = {
.bound = subdev_notifier_bound,
.complete = subdev_notifier_complete,
.unbind = subdev_notifier_unbind,
};
static int isp_subdev_notifier(struct rkisp_device *isp_dev)
{
struct v4l2_async_notifier *ntf = &isp_dev->notifier;
struct device *dev = isp_dev->dev;
int ret;
v4l2_async_notifier_init(ntf); // 初始化异步通知器
ret = v4l2_async_notifier_parse_fwnode_endpoints( // 解析依赖设备
dev, ntf, sizeof(struct rkisp_async_subdev),
rkisp_fwnode_parse);
if (ret < 0)
return ret;
ntf->ops = &subdev_notifier_ops; // 绑定函数集
return v4l2_async_notifier_register(&isp_dev->v4l2_dev, ntf); // 注册异步通知器
}
当isp所有的依赖都准备就绪时,会回调subdev_notifier_ops的complete函数 ,在complete函数中,会为之前已注册的subdev创建设备节点(/dev/subdevX)。
cpp
static int subdev_notifier_complete(struct v4l2_async_notifier *notifier)
{
struct rkisp_device *dev;
int ret;
dev = container_of(notifier, struct rkisp_device, notifier);
mutex_lock(&dev->media_dev.graph_mutex);
ret = rkisp_create_links(dev);
if (ret < 0)
goto unlock;
ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev); // 创建设备节点(/dev/subdevX)
if (ret < 0)
goto unlock;
// ......
}
最终形成下图所示的拓扑机构:

至此为止,在rk3588开发板上使用OV13855摄像头的MIPI驱动程序讲解完毕,回顾以往的内容,我们首先探讨了摄像头采集图像后,数据的流向过程,总结出了以下的"流水线":
Sensor (OV13855) ➔ DPHY (物理层) ➔ CSI Host (控制器) ➔ VICAP(CIF) ➔ ISP