[驱动进阶——MIPI摄像头驱动(三)]rk3588+OV13855摄像头驱动加载过程详细解析第二部分——DPHY驱动+CSI驱动

上一篇文章已经讲解过摄像头数据流动环节的第一部分------sensor驱动,接下来进行剩余部分的讲解,本篇文章进行DPHY驱动和CSI驱动加载过程的解析:

Sensor (OV13855) ➔ DPHY (物理层) ➔ CSI Host (控制器) ➔ VICAP(CIF) ➔ ISP


驱动源码解析:DPHY(物理层)驱动

一、设备树节点:

arch/arm64/boot/dts/rockchip/rk3588s.dtsi

arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-camera1.dtsi

这个 csi2_dphy0 节点属于普通节点(非I2C、SPI节点等),因此内核在启动时,会根据其生成一个platform_device结构体

cpp 复制代码
&csi2_dphy0 {
    status = "disabled";
    ports {
        #address-cells = <1>;
        #size-cells = <0>;
        port@0 {
            reg = <0>;
            #address-cells = <1>;
            #size-cells = <0>;
​
            mipi_in_ucam0: endpoint@0 {
                reg = <0>;
                remote-endpoint = <&ov13850_out2>;
                data-lanes = <1 2>;
            };
​
            mipi_in_ucam1: endpoint@1 {
                reg = <1>;
                remote-endpoint = <&ov13855_out2>;
                data-lanes = <1 2>;
            };
        };
        port@1 {
                reg = <1>;
                #address-cells = <1>;
                #size-cells = <0>;
                csidphy0_out: endpoint@0 {
                        reg = <0>;
                        remote-endpoint = <&mipi2_csi2_input>;
                };
        };
    };
};
​
/* dphy0 full mode */
csi2_dphy0: csi2-dphy0 {
    compatible = "rockchip,rk3568-csi2-dphy";
    rockchip,hw = <&csi2_dphy0_hw>;
    status = "disabled";
};

二、驱动代码详细解析

drivers/phy/rockchip/phy-rockchip-csi2-dphy.c

2.1 驱动注册、匹配流程

cpp 复制代码
static const struct of_device_id rockchip_csi2_dphy_match_id[] = {
    {
        .compatible = "rockchip,rk3568-csi2-dphy",                      // 设备树与此项匹配
        .data = &rk3568_dphy_drv_data,
    },
    {
        .compatible = "rockchip,rk3588-csi2-dcphy",     
        .data = &rk3588_dcphy_drv_data,
    },
    {
        .compatible = "rockchip,rv1106-csi2-dphy",
        .data = &rv1106_dphy_drv_data,
    },
    {}
};
​
struct platform_driver rockchip_csi2_dphy_driver = {
    .probe = rockchip_csi2_dphy_probe,                                  // driver 和 driver 匹配时调用
    .remove = rockchip_csi2_dphy_remove,
    .driver = {
        .name = "rockchip-csi2-dphy",
        .pm = &rockchip_csi2_dphy_pm_ops,
        .of_match_table = rockchip_csi2_dphy_match_id,                  // 匹配表
    },
};
​
static int __init rockchip_csi2_dphy_init(void)
{
    return platform_driver_register(&rockchip_csi2_dphy_driver);        // 注册 platform_driver 
}

程序在入口函数中注册了一个platform_driver结构体 ,当platform_driver结构体的 ".of_match_table" 与设备树节点的 "compatible" 匹配时,probe函数会被调用

2.2 probe函数详解

详细的注释已经添加在函数中了:

cpp 复制代码
/**
 * rockchip_csi2_dphy_probe - Rockchip CSI2 DPHY 驱动初始化函数
 * @pdev: 平台设备指针
 * 
 * 功能:初始化 MIPI CSI-2 物理层(DPHY)接口,用于连接摄像头传感器
 * 返回值:0表示成功,负数表示错误码
 */
static int rockchip_csi2_dphy_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;              // 获取设备指针
    const struct of_device_id *of_id;             // 设备树匹配ID
    struct csi2_dphy *csi2dphy;                   // DPHY设备结构体
    struct v4l2_subdev *sd;                       // V4L2子设备
    const struct dphy_drv_data *drv_data;         // 驱动私有数据(不同芯片的配置)
    int ret;                                      // 返回值
​
    /* ========== 1. 分配内存并保存设备指针 ========== */
    // 为 DPHY 设备结构体分配内存(驱动卸载时会自动释放)
    csi2dphy = devm_kzalloc(dev, sizeof(*csi2dphy), GFP_KERNEL);
    if (!csi2dphy)
        return -ENOMEM;  // 内存不足错误
    
    csi2dphy->dev = dev;  // 保存设备指针,方便后续使用
​
    /* ========== 2. 匹配设备树并获取驱动配置数据 ========== */
    // 从设备树中查找匹配的设备(根据 compatible 属性)
    of_id = of_match_device(rockchip_csi2_dphy_match_id, dev);
    if (!of_id)
        return -EINVAL;  // 设备树中没有匹配的节点
    
    // 获取该设备的私有配置数据(如寄存器地址、时钟名称等)
    drv_data = of_id->data;
    csi2dphy->drv_data = drv_data;
​
    /* ========== 3. 获取 PHY 索引编号 ========== */
    // 从设备树别名中获取 PHY 编号(本节点为 csi2-dphy0,因此取到0)
    csi2dphy->phy_index = of_alias_get_id(dev->of_node, drv_data->dev_name);
    // 如果索引无效(小于0或超出最大值),默认设为0
    if (csi2dphy->phy_index < 0 || csi2dphy->phy_index >= PHY_MAX)
        csi2dphy->phy_index = 0;
​
    /* ========== 4. 根据厂商类型连接硬件资源 ========== */
    // 判断 DPHY 的厂商类型,采用不同的初始化方法,获取硬件资源(本节点是非Samsung的dphy,因此走else分支)
    if (csi2dphy->drv_data->vendor == PHY_VENDOR_SAMSUNG) {
        /* Samsung DCPHY(双模PHY,支持CSI和DSI) */
        ret = rockchip_csi2_dphy_attach_samsung_phy(csi2dphy);
        // 设置 Samsung 特定的 DPHY 参数
        csi2dphy->dphy_param = rk3588_dcphy_param;
    } else {
        /* Rockchip 标准 MIPI DPHY */
        ret = rockchip_csi2_dphy_attach_hw(csi2dphy);
    }
    
    // 如果硬件资源获取失败(如寄存器映射、时钟获取失败)
    if (ret) {
        dev_err(dev,
            "csi2 dphy hw can't be attached, register dphy%d failed!\n",
            csi2dphy->phy_index);
        return -ENODEV;  // 设备不存在错误
    }
​
    /* ========== 5. 初始化 V4L2 子设备 ========== */
    sd = &csi2dphy->sd;  // 获取子设备指针
    
    // 初始化互斥锁,防止多线程并发访问冲突
    mutex_init(&csi2dphy->mutex);
    
    // 初始化 V4L2 子设备,注册操作函数集
    v4l2_subdev_init(sd, &csi2_dphy_subdev_ops);
    
    // 标记该设备有独立的设备节点(可在 /dev/ 下访问)
    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
    
    // 生成设备名称,如 "rockchip-csi2-dphy0"
    snprintf(sd->name, sizeof(sd->name),
         "rockchip-csi2-dphy%d", csi2dphy->phy_index);
    
    sd->dev = dev;  // 关联设备指针
​
    /* ========== 6. 保存平台设备私有数据 ========== */
    // 将 V4L2 实体指针存到平台设备的私有数据中
    // 后续可通过 platform_get_drvdata() 获取
    platform_set_drvdata(pdev, &sd->entity);
​
    /* ========== 7. 初始化 Media Controller ========== */
    // Media Controller 用于管理视频数据流(摄像头→PHY→ISP→内存)
    ret = rockchip_csi2dphy_media_init(csi2dphy);
    if (ret < 0)
        goto detach_hw;  // 失败则跳转到资源清理
​
    /* ========== 8. 启用运行时电源管理 ========== */
    // 允许设备在不使用时自动进入省电模式
    pm_runtime_enable(&pdev->dev);
​
    /* ========== 9. 打印成功信息 ========== */
    dev_info(dev, "csi2 dphy%d probe successfully!\n", csi2dphy->phy_index);
​
    return 0;  // 初始化成功
​
/* ========== 错误处理:清理已分配的资源 ========== */
detach_hw:
    // 销毁互斥锁
    mutex_destroy(&csi2dphy->mutex);
    
    // 根据厂商类型调用对应的硬件资源释放函数
    if (csi2dphy->drv_data->vendor == PHY_VENDOR_SAMSUNG)
        rockchip_csi2_dphy_detach_samsung_phy(csi2dphy);  // 释放Samsung PHY资源
    else
        rockchip_csi2_dphy_detach_hw(csi2dphy);  // 释放标准DPHY资源
​
    return 0;  // 注意:这里应该返回ret(错误码),但代码写的是0(可能是bug)
}

2.3 重点解析

如果看过前面的sensor驱动程序解析,就会很熟悉这里的三大步了,即:分配、设置、注册。这里就不详细解释了,快速过一下。

1. 分配subdev
cpp 复制代码
csi2dphy = devm_kzalloc(dev, sizeof(*csi2dphy), GFP_KERNEL);

分配csi2dphy结构体,顺带分配sundev结构体。

2. 设置subdev
cpp 复制代码
sd = &csi2dphy->sd;
mutex_init(&csi2dphy->mutex);
v4l2_subdev_init(sd, &csi2_dphy_subdev_ops);
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
snprintf(sd->name, sizeof(sd->name),
     "rockchip-csi2-dphy%d", csi2dphy->phy_index);
sd->dev = dev;

这里重点设置了subdev的操作函数集 csi2_dphy_subdev_ops。

3. 注册subdev
cpp 复制代码
ret = rockchip_csi2dphy_media_init(csi2dphy);

以下是函数原型:

cpp 复制代码
/**
 * rockchip_csi2dphy_media_init - 初始化 DPHY 的 Media Controller 接口
 * @dphy: DPHY 设备结构体指针
 * 
 * 功能:
 * 1. 配置 media pads(数据端口)
 * 2. 注册异步通知器,等待摄像头传感器连接
 * 3. 注册为 V4L2 子设备
 * 
 * 返回值:0表示成功,负数表示错误码
 */
static int rockchip_csi2dphy_media_init(struct csi2_dphy *dphy)
{
    int ret;
​
    /* ========== 1. 配置 Media Pads(数据端口) ========== */
    
    /* 配置 SOURCE pad(数据输出端)*/
    dphy->pads[CSI2_DPHY_RX_PAD_SOURCE].flags =
        MEDIA_PAD_FL_SOURCE |         // 标记为数据源(输出数据)
        MEDIA_PAD_FL_MUST_CONNECT;    // 必须连接到下一级设备(如ISP)
    
    /* 配置 SINK pad(数据接收端)*/
    dphy->pads[CSI2_DPHY_RX_PAD_SINK].flags =
        MEDIA_PAD_FL_SINK |           // 标记为数据接收端(接收摄像头数据)
        MEDIA_PAD_FL_MUST_CONNECT;    // 必须连接到上一级设备(摄像头传感器)
    
    /* 设置设备功能类型:视频接口桥接器(连接摄像头和ISP) */
    dphy->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
    
    /* 初始化 media entity 的 pads(注册2个端口:SINK和SOURCE) */
    ret = media_entity_pads_init(&dphy->sd.entity,
                CSI2_DPHY_RX_PADS_NUM,  // pads数量(通常是2)
                dphy->pads);             // pads数组
    if (ret < 0)
        return ret;  // 初始化失败
​
    /* ========== 2. 初始化异步通知器 ========== */
    
    /* 初始化异步通知器(用于等待摄像头传感器设备就绪) */
    v4l2_async_notifier_init(&dphy->notifier);
​
    /* 
     * 解析设备树中的 endpoint,查找连接的摄像头传感器
     * - 从设备树的 port 0 中解析 endpoint
     * - 为每个 endpoint 分配一个 sensor_async_subdev 结构体
     * - 调用 rockchip_csi2_dphy_fwnode_parse 解析具体配置
     */
    ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(
        dphy->dev,                              // 设备指针
        &dphy->notifier,                        // 通知器
        sizeof(struct sensor_async_subdev),     // 每个子设备的结构体大小
        0,                                      // 解析 port 0(摄像头输入端口)
        rockchip_csi2_dphy_fwnode_parse);       // 解析回调函数
    if (ret < 0)
        return ret;  // 解析失败(可能设备树没配置摄像头)
​
    /* ========== 3. 注册异步通知器 ========== */
    
    /* 将通知器关联到子设备 */
    dphy->sd.subdev_notifier = &dphy->notifier;
    
    /* 设置通知器的操作函数(当摄像头设备就绪时会调用这些函数) */
    dphy->notifier.ops = &rockchip_csi2_dphy_async_ops;
    
    /* 注册异步子设备通知器 */
    ret = v4l2_async_subdev_notifier_register(&dphy->sd, &dphy->notifier);
    if (ret) {
        dev_err(dphy->dev,
            "failed to register async notifier : %d\n", ret);
        /* 清理通知器资源 */
        v4l2_async_notifier_cleanup(&dphy->notifier);
        return ret;
    }
​
    /* ========== 4. 注册为 V4L2 异步子设备 ========== */
    
    /* 
     * 将 DPHY 注册为 V4L2 子设备
     * - 等待上级设备(如 ISP)连接
     * - 等待下级设备(摄像头传感器)连接
     * - 所有设备连接后,整个视频管道才能工作
     */
    return v4l2_async_register_subdev(&dphy->sd);
}

这个函数的流程基本与上一篇文章的sensor异步注册subdev过程一致,因此不做过多讲解,简单看下注释即可。

三、细节小结

看到这里,有人可能会发现之前的sensor注册过程和dphy注册过程有一点差别,具体如下:

3.1 sensor:

cpp 复制代码
v4l2_async_notifier_init(notifier);
​
ret = v4l2_async_notifier_parse_fwnode_sensor_common(sd->dev, notifier);
​
ret = v4l2_async_subdev_notifier_register(sd, notifier);
​
ret = v4l2_async_register_subdev(sd);

3.2 dphy:

cpp 复制代码
v4l2_async_notifier_init(&dphy->notifier);
​
ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(dphy->dev, &dphy->notifier,
    sizeof(struct sensor_async_subdev), 0,
    rockchip_csi2_dphy_fwnode_parse);
​
dphy->notifier.ops = &rockchip_csi2_dphy_async_ops;             // 主要区别!!!
​
ret = v4l2_async_subdev_notifier_register(&dphy->sd, &dphy->notifier);
​
v4l2_async_register_subdev(&dphy->sd);

这两个驱动虽然都使用异步通知器,但用途完全不同 ,所以是否需要设置 ops 也不同

3.3 关键区别:谁在等待谁?

1. rockchip_csi2dphy_media_init(DPHY驱动)
cpp 复制代码
dphy->notifier.ops = &rockchip_csi2_dphy_async_ops;  // 设置了 ops

作用:DPHY 等待摄像头传感器连接

css 复制代码
数据流向:摄像头 → DPHY → ISP
​
DPHY 的角色:
- 我是"中间设备",需要等待上游的摄像头
- 摄像头连接成功时,我需要执行一些操作(通过 ops 回调)

ops 的具体作用:

cpp 复制代码
static const struct v4l2_async_notifier_operations rockchip_csi2_dphy_async_ops = {
    /* 当摄像头设备绑定成功时调用 */
    .bound = rockchip_csi2_dphy_notifier_bound,
    
    /* 当所有等待的设备都就绪时调用 */
    .complete = rockchip_csi2_dphy_notifier_complete,
};

为什么需要 ops?

  • 摄像头连接后,DPHY 需要建立数据链路(media link)

  • 需要配置数据通道数(data-lanes)

  • 需要通知下游设备(ISP)准备接收数据

2. v4l2_async_register_subdev_sensor_common(sensor驱动)
cpp 复制代码
// 注意:这里没有设置 notifier->ops!
v4l2_async_notifier_init(notifier);

作用:摄像头传感器等待镜头/闪光灯等外围设备

css 复制代码
数据流向:镜头 → 传感器 → DPHY
​
传感器的角色:
- 我是"最上游设备",只需要等待镜头、闪光灯等配件
- 这些配件连接与否不影响数据流,只是功能增强
- 不需要复杂的回调处理

为什么不需要 ops?

  • 镜头(lens)、闪光灯(flash)只是可选配件

  • 它们连接后,传感器只需要记录引用,不需要额外操作

  • V4L2 框架会自动处理基本的绑定逻辑

3.4 设备树对应关系

1. DPHY 的设备树(有 ops)
cpp 复制代码
csi2_dphy0 {
    ports {
        port@0 {  // ← 这个 port 会被 parse_fwnode_endpoints_by_port 解析
            mipi_in_ucam1: endpoint@1 {
                reg = <1>;
                remote-endpoint = <&ov13855_out2>;
                data-lanes = <1 2>;
            };
        };
    };
};

解析后的行为:

  1. v4l2_async_notifier_parse_fwnode_endpoints_by_port 发现 &ov13855_output

  2. 添加到等待列表

  3. 当 OV13855 驱动加载后,触发 rockchip_csi2_dphy_async_ops.bound

  4. 在 bound 回调中建立 media link

2. sensor的设备树(无 ops)
cpp 复制代码
ov13855: ov13855@36 {
    compatible = "ovti,ov13855";
    
    /* 可选的镜头配件 */
    lens-focus = <&dw9714_p1>;  // ← parse_fwnode_sensor_common 解析这个
    
    port {
            ov13855_out2: endpoint {
                remote-endpoint = <&mipi_in_ucam1>;
                data-lanes = <1 2>;
            };
    };
};

解析后的行为:

  1. v4l2_async_notifier_parse_fwnode_sensor_common 查找 lens-focus

  2. 添加到等待列表

  3. 镜头/闪光灯加载后,自动绑定,不需要额外回调

  4. 传感器只需要通过 sd->lens 访问镜头即可

3.5 代码执行流程对比

场景1:DPHY 等待摄像头(需要 ops)
css 复制代码
1. DPHY 驱动加载
   ↓
2. rockchip_csi2dphy_media_init()
   ↓
3. 解析设备树,发现需要等待 ov13855
   ↓
4. 注册通知器,设置 ops
   ↓
5. OV13855 驱动加载
   ↓
6. 触发 DPHY 的 ops.bound 回调
   ↓
   【在回调中执行】:
   - 获取摄像头的数据通道配置
   - 创建 media link: ov13855:0 → dphy:0
   - 配置 DPHY 的接收参数
   ↓
7. 所有设备就绪,触发 ops.complete
   ↓
   【在回调中执行】:
   - 注册整个 media controller 图
   - 通知用户空间可以使用摄像头

ops.bound 实际代码示例:

cpp 复制代码
static int rockchip_csi2_dphy_notifier_bound(
    struct v4l2_async_notifier *notifier,
    struct v4l2_subdev *sd,
    struct v4l2_async_subdev *asd)
{
    struct csi2_dphy *dphy = container_of(notifier, struct csi2_dphy, notifier);
    struct sensor_async_subdev *s_asd = container_of(asd, ...);
    
    /* 保存传感器引用 */
    dphy->sensor_sd = sd;
    
    /* 获取数据通道配置 */
    dphy->lane_num = s_asd->lanes;
    
    /* 创建 media link */
    return media_create_pad_link(&sd->entity, 0,
                                 &dphy->sd.entity, CSI2_DPHY_RX_PAD_SINK,
                                 MEDIA_LNK_FL_ENABLED);
}
场景2:传感器等待镜头(不需要 ops)
css 复制代码
1. OV13855 驱动加载
   ↓
2. v4l2_async_register_subdev_sensor_common()
   ↓
3. 解析设备树,发现 lens-focus = <&dw9714_p1>
   ↓
4. 注册通知器,不设置 ops(使用默认行为)
   ↓
5. DW9714 镜头驱动加载
   ↓
6. V4L2 框架自动绑定(默认行为)
   ↓
   【框架自动执行】:
   - 设置 sd->lens = dw9714_subdev
   ↓
7. 传感器可以直接使用 sd->lens 控制镜头对焦

为什么不需要 ops? 因为 V4L2 框架已经提供了默认的绑定逻辑:

cpp 复制代码
// V4L2 框架内部的默认绑定代码(简化版)
static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
                                   struct v4l2_subdev *sd,
                                   struct v4l2_async_subdev *asd)
{
    // 如果是镜头设备
    if (asd->match.fwnode.fwnode == lens_fwnode) {
        notifier->sd->lens = sd;  // 自动保存镜头引用
    }
    
    // 如果是闪光灯设备
    if (asd->match.fwnode.fwnode == flash_fwnode) {
        notifier->sd->flash = sd;  // 自动保存闪光灯引用
    }
    
    // 不需要用户提供额外的 ops
    return 0;
}

3.6 什么时候需要设置 ops?

需要设置 ops 的情况:

  1. 需要建立 media link(如 DPHY ↔ ISP)

  2. 需要配置硬件参数(如 DPHY 的数据通道数)

  3. 需要通知下游设备(如告诉 ISP 准备接收数据)

  4. 需要复杂的初始化流程(如 ISP 的多级管道配置)

不需要设置 ops 的情况:

  1. 只是简单的设备引用(如传感器 ↔ 镜头)

  2. 框架已提供默认行为(如 lens、flash 的自动绑定)

  3. 不涉及数据流配置(镜头不参与数据传输)

驱动源码解析:CSI(控制器)驱动

一、设备树节点

同理,内核在启动时,会根据其生成一个platform_device结构体

cpp 复制代码
&mipi2_csi2 {
    status = "disabled";
    ports {
        #address-cells = <1>;
        #size-cells = <0>;
        port@0 {
            reg = <0>;
            #address-cells = <1>;
            #size-cells = <0>;
            mipi2_csi2_input: endpoint@1 {
                reg = <1>;
                remote-endpoint = <&csidphy0_out>;
            };
        };
        port@1 {
            reg = <1>;
            #address-cells = <1>;
            #size-cells = <0>;
            mipi2_csi2_output: endpoint@0 {
                reg = <0>;
                remote-endpoint = <&cif_mipi_in2>;
            };
        };
    };
};
​
mipi2_csi2: mipi2-csi2@fdd30000 {
    compatible = "rockchip,rk3588-mipi-csi2";
    reg = <0x0 0xfdd30000 0x0 0x10000>;
    reg-names = "csihost_regs";
    interrupts = <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>,
             <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-names = "csi-intr1", "csi-intr2";
    clocks = <&cru PCLK_CSI_HOST_2>;
    clock-names = "pclk_csi2host";
    resets = <&cru SRST_P_CSI_HOST_2>, <&cru SRST_CSIHOST2_VICAP>;
    reset-names = "srst_csihost_p", "srst_csihost_vicap";
    status = "disabled";
};

二、驱动代码解析

drivers/media/platform/rockchip/cif/mipi-csi2.c

CSI2的驱动注册、匹配流程和之前的dphy驱动注册、匹配流程完全一致,这里不过多赘述。下面主要看prob函数中是否还是那三大步(分配、设置、注册),验证我们所了解的"套路"。

2.1 分配subdev

cpp 复制代码
csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL);

分配csi2结构体时,顺带分配subdev

2.2 设置subdev

cpp 复制代码
v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops);
v4l2_set_subdevdata(&csi2->sd, &pdev->dev);
csi2->sd.entity.ops = &csi2_entity_ops;
csi2->sd.dev = &pdev->dev;
csi2->sd.owner = THIS_MODULE;
csi2->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
ret = strscpy(csi2->sd.name, DEVICE_NAME, sizeof(csi2->sd.name));
​
ret = csi2_media_init(&csi2->sd);

设置subdev中的一系列成员。

cpp 复制代码
static int csi2_media_init(struct v4l2_subdev *sd)
{
    struct csi2_dev *csi2 = sd_to_dev(sd);
    int i = 0, num_pads = 0;
​
    num_pads = csi2->match_data->num_pads;
​
    for (i = 0; i < num_pads; i++) {
        csi2->pad[i].flags = (i == CSI2_SINK_PAD) ?
        MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
    }
​
    csi2->pad[RK_CSI2X_PAD_SOURCE0].flags =
        MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;
    csi2->pad[RK_CSI2_PAD_SINK].flags =
        MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
​
    // .......
​
    return media_entity_pads_init(&sd->entity, num_pads, csi2->pad);
}

初始化entity的pad端口(entity与subdev是一一对应的)。

2.3 注册subdev

cpp 复制代码
ret = csi2_notifier(csi2);
​
static int csi2_notifier(struct csi2_dev *csi2)
{
    struct v4l2_async_notifier *ntf = &csi2->notifier;
    int ret;
​
    v4l2_async_notifier_init(ntf);
​
    ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(csi2->dev,
                                 &csi2->notifier,
                                 sizeof(struct v4l2_async_subdev), 0,
                                 csi2_parse_endpoint);
    if (ret < 0)
        return ret;
​
    csi2->sd.subdev_notifier = &csi2->notifier;
    csi2->notifier.ops = &csi2_async_ops;
    ret = v4l2_async_subdev_notifier_register(&csi2->sd, &csi2->notifier);
    if (ret) {
        v4l2_err(&csi2->sd,
             "failed to register async notifier : %d\n",
             ret);
        v4l2_async_notifier_cleanup(&csi2->notifier);
        return ret;
    }
​
    ret = v4l2_async_register_subdev(&csi2->sd);
​
    return ret;
}

和之前的套路一样:初始化异步通知器、解析设备树port、注册异步通知器、异步注册subdev

相关推荐
小尧嵌入式2 小时前
【Linux开发四】Linux中概念|MobaXterm和Filezilla软件使用|线程|互斥锁|读写锁
linux·运维·服务器·开发语言·数据结构
峥嵘life2 小时前
Android16 EDLA【GTS】GtsUnofficialApisUsageTestCases存在fail项
android·linux·运维·学习
海天鹰2 小时前
fcitx5皮肤
linux
晚风吹长发2 小时前
初步了解Linux中的信号保存和简单使用
linux·运维·服务器·数据结构·后端·算法
林深现海2 小时前
基于宇树 Go2 与 NaVILA 的全栈视觉导航系统深度解析
linux·vscode·yolo·ubuntu·机器人
阿豪学编程2 小时前
【Linux】Socket网络编程
linux·服务器·网络
小张成长计划..2 小时前
【linux】4:编辑器vim的使用
linux·编辑器·vim
燃于AC之乐3 小时前
【Linux系统编程】进程调度解析:优先级与O(1)调度算法
linux·操作系统·进程调度·进程优先级·调度算法
Nick.Q11 小时前
vim插件的管理与离线安装
linux·编辑器·vim