[驱动进阶——MIPI摄像头驱动(五)]rk3588+OV13855摄像头驱动加载过程详细解析第四部分——ISP驱动

上一篇文章已经讲解过摄像头数据流动环节的第三部分------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 = &params_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

接着具体讲解了这五个部分的驱动加载过程,主要以video设备、subdev子设备和media子系统等知识为核心进行解析,通过这几部分的配合,最终形成了rk3588芯片独特的"两段式"形式的数据流拓扑结构。

相关推荐
暮云星影4 小时前
四、linux系统 应用开发:UI开发环境配置概述 (一)
linux·ui·arm
a程序小傲5 小时前
得物Java面试被问:RocketMQ的消息轨迹追踪实现
java·linux·spring·面试·职场和发展·rocketmq·java-rocketmq
Ghost Face...5 小时前
i386 CPU页式存储管理深度解析
java·linux·服务器
LEEE@FPGA5 小时前
zynq 是不是有了设备树,再linux中不需要编写驱动也能控制
linux·运维·单片机
RisunJan5 小时前
Linux命令-less(分页查看器)
linux·运维
梁正雄5 小时前
linux服务-MariaDB 10.6 Galera Cluster+garbd
linux·运维·mariadb
Coder个人博客6 小时前
Linux6.19-ARM64 mm mem_encrypt子模块深入分析
linux·安全·车载系统·系统架构·系统安全·鸿蒙系统·安全架构
hweiyu006 小时前
Linux 命令:fold
linux·运维
搬砖者(视觉算法工程师)6 小时前
Ubuntu 24.04 LTS 系统上树莓派摄像头模块 v2.1(IMX219)的安装配置与故障排查
linux·数据库·ubuntu