上一篇文章已经讲解过摄像头数据流动环节的第三部分——CIF驱动+SDITF驱动,接下来进行最后一部分的讲解,也就是ISP驱动加载过程的解析:
Sensor (OV13855) ➔ DPHY (物理层) ➔ CSI Host (控制器) ➔ VICAP(CIF) ➔ ISP
驱动源码解析:ISP驱动
一、设备树节点
内核会根据此节点生成一个platform_device。
&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函数)
drivers/media/platform/rockchip/isp/dev.c
大部分内容就是之前的套路,下面给出其中关键的一些功能函数,可以看到这里先注册了一个media子系统(/dev/media1),然后注册各类设备节点:
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 的函数原型:
/**
* 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"
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,所以此函数直接退出。
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。
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函数
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。
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。
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。
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 设备节点
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函数
此函数什么也没干,直接退出。
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所依赖的设备,最后注册它。
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)。
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



