核心内容摘要
搞技术的人员为什么通常当不了领导?
上一篇文章已经讲解过摄像头数据流动环节的
分——DPHY驱动CSI驱动接下来进行剩余部分的讲解本篇文章进行CIF驱动SDITF驱动加载过程的解析Sensor (OV
➔ DPHY (物理层) ➔ CSI Host (控制器) ➔ VICAP(CIF) ➔ ISPSDITF是依附于CIF硬件控制器的。
驱动源码解析cif_lvds2驱动这个设备驱动有些特殊和前面已经讲过的设备驱动稍有不同它处于/dev/media0所管理的层次中的最后一层。
设备树节点内核会根据此节点生成一个platform_device。
rkcif_mipi_lvds2 { status disabled; port { cif_mipi_in2: endpoint { remote-endpoint mipi2_csi2_output; }; }; }; rkcif_mipi_lvds2: rkcif-mipi-lvds2 { compatible rockchip,rkcif-mipi-lvds; rockchip,hw rkcif; iommus rkcif_mmu; status disabled; };
驱动代码解析drivers/media/platform/rockchip/cif/dev.c
1 probe函数详解详细注释已经放在源码中后续重点关注两个函数rkcif_plat_init、rkcif_get_reserved_mem/** * rkcif_plat_probe - Rockchip CIF 平台驱动初始化函数 * pdev: 平台设备指针 * * 功能初始化 CIFCamera Interface设备负责接收摄像头数据并写入内存 * CIF 在视频管道中的位置摄像头 → DPHY → CIF → 内存 → ISP * * 返回值0表示成功负数表示错误码 */ static int rkcif_plat_probe(struct platform_device *pdev) { const struct of_device_id *match; // 设备树匹配信息 struct device_node *node pdev-dev.of_node; // 设备树节点 struct device *dev pdev-dev; // 设备指针 struct rkcif_device *cif_dev; // CIF 设备结构体 const struct rkcif_match_data *data; // 驱动私有数据 int ret; /*
打印驱动版本信息 */ /* 将版本号格式化为字符串如 v
01.
0
03 */ sprintf(rkcif_version, v%02x.%02x.%02x, RKCIF_DRIVER_VERSION 16, // 主版本号 (RKCIF_DRIVER_VERSION 0xff
8, // 次版本号 RKCIF_DRIVER_VERSION 0x00ff); // 修订号 /* 打印版本信息方便调试 */ dev_info(dev, rkcif driver version: %s\n, rkcif_version); /*
匹配设备树并获取驱动配置 */ /* 从设备树中查找匹配的节点根据 compatible 属性 */ match of_match_node(rkcif_plat_of_match, node); if (IS_ERR(match)) return PTR_ERR(match); // 设备树中没有匹配的节点 /* 获取该设备的私有配置数据如接口类型、通道数等 */ data match-data; /*
分配内存并初始化设备结构体 */ /* 为 CIF 设备结构体分配内存驱动卸载时自动释放 */ cif_dev devm_kzalloc(dev, sizeof(*cif_dev), GFP_KERNEL); if (!cif_dev) return -ENOMEM; // 内存不足 /* 将 cif_dev 保存到平台设备的私有数据中 */ dev_set_drvdata(dev, cif_dev); /* 保存设备指针 */ cif_dev-dev dev; /*
创建 sysfs 属性组 */ /* * 在 sysfs 中创建属性文件通常在 /sys/devices/.../下 * 用户空间可以通过这些文件查询状态或调整参数 * 例如 * /sys/.../rkcif/status - 查看 CIF 状态 * /sys/.../rkcif/frame_count - 查看接收帧数 */ if (sysfs_create_group(pdev-dev.kobj, dev_attr_grp)) return -ENODEV; // sysfs 创建失败 /*
连接硬件资源 */ /* * 获取并映射硬件资源 * - 映射寄存器地址ioremap * - 获取时钟如 ACLK_CIF、HCLK_CIF * - 获取复位控制器 * - 获取中断号 */ rkcif_attach_hw(cif_dev); /*
解析设备树配置 */ /* * 从设备树读取配置参数 * - 接口类型MIPI/DVP/BT656 等 * - 数据通道数量 * - DMA 缓冲区配置 * - 中断配置 * - 工作模式等 */ rkcif_parse_dts(cif_dev); /*
初始化 CIF 平台相关功能重点关注 */ /* * 功能初始化 CIF 设备的核心功能包括 * - 初始化锁和管道 * - 根据芯片型号初始化不同数量的数据流通道 * - 注册 V4L2 和 Media 设备 * - 注册平台子设备异步等待上游 DPHY *>
rkcif_plat_init/** * rkcif_plat_init - CIF 平台初始化函数 * cif_dev: CIF 设备结构体 * node: 设备树节点 * inf_id: 接口 IDMIPI/DVP 等 * * 功能初始化 CIF 设备的核心功能包括 * - 初始化锁和管道 * - 根据芯片型号初始化不同数量的数据流通道 * - 注册 V4L2 和 Media 设备 * - 注册平台子设备异步等待上游 DPHY * * 返回值0表示成功负数表示错误码 */ int rkcif_plat_init(struct rkcif_device *cif_dev, struct device_node *node, int inf_id) { struct device *dev cif_dev-dev; struct v4l2_device *v4l2_dev; int ret; /*
初始化基本配置 */ cif_dev-hdr.hdr_mode NO_HDR; // HDR 模式默认关闭 cif_dev-inf_id inf_id; // 接口 IDMIPI/DVP /*
初始化同步机制 */ /* 互斥锁保护共享资源 */ mutex_init(cif_dev-stream_lock); // 数据流操作锁 mutex_init(cif_dev-scale_lock); // 缩放操作锁 mutex_init(cif_dev-tools_lock); // 工具通道锁 /* 自旋锁保护中断上下文的数据 */ spin_lock_init(cif_dev-hdr_lock); // HDR 配置锁 spin_lock_init(cif_dev-buffree_lock); // 缓冲区释放锁 spin_lock_init(cif_dev-reset_watchdog_timer.timer_lock); // 看门狗定时器锁 spin_lock_init(cif_dev-reset_watchdog_timer.csi2_err_lock); // CSI2 错误锁 /*
初始化引用计数和状态标志 */ atomic_set(cif_dev-pipe.power_cnt,
; // 管道电源引用计数 atomic_set(cif_dev-pipe.stream_cnt,
; // 管道流引用计数 atomic_set(cif_dev-power_cnt,
; // 设备电源引用计数 cif_dev-is_start_hdr false; // HDR 启动标志 cif_dev-id_use_cnt 0; // ID 使用计数 cif_dev-sync_type NO_SYNC_MODE; // 同步类型 cif_dev-sditf_cnt 0; // SDITF 计数 cif_dev-is_notifier_isp false; // ISP 通知标志 cif_dev-sensor_linetime 0; // 传感器行时间 cif_dev-early_line 0; // 提前行数 cif_dev-is_thunderboot false; // 快速启动标志 cif_dev-rdbk_debug 0; // Readback 调试 /*
初始化管道操作函数 */ cif_dev-pipe.open rkcif_pipeline_open; // 打开管道 cif_dev-pipe.close rkcif_pipeline_close; // 关闭管道 cif_dev-pipe.set_stream rkcif_pipeline_set_stream; // 设置流状态 /* 中断处理函数根据芯片型号选择 */ cif_dev-isr_hdl rkcif_irq_handler; // 默认中断处理 if (cif_dev-chip_id CHIP_RV1126_CIF_LITE) cif_dev-isr_hdl rkcif_irq_lite_handler; // Lite 版本的中断处理 /*
根据芯片型号初始化数据流通道 */ /* 老芯片RK3399/RK3326 等根据接口类型决定通道数 */ if (cif_dev-chip_id CHIP_RV1126_CIF) { if (cif_dev-inf_id RKCIF_MIPI_LVDS) { /* MIPI 接口4 个虚拟通道 */ rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; } else { /* DVP/BT656 接口1 个通道 */ rkcif_stream_init(cif_dev, RKCIF_STREAM_CIF); } } else { /* 新芯片RV1126/RK3568/RK3588统一支持 4 通道 */ // -- 对于本案例走这条分支 rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; rkcif_stream_init(cif_dev, RKCIF_STREAM_MIPI_ID
; } /*
初始化额外的缩放和工具通道仅部分芯片支持 */ if (cif_dev-chip_id CHIP_RK3588_CIF || cif_dev-chip_id CHIP_RV1106_CIF) { /* 缩放通道Scale Channel用于硬件缩放 */ rkcif_init_scale_vdev(cif_dev, RKCIF_SCALE_CH
; rkcif_init_scale_vdev(cif_dev, RKCIF_SCALE_CH
; rkcif_init_scale_vdev(cif_dev, RKCIF_SCALE_CH
; rkcif_init_scale_vdev(cif_dev, RKCIF_SCALE_CH
; /* 工具通道Tools Channel用于调试/统计 */ rkcif_init_tools_vdev(cif_dev, RKCIF_TOOLS_CH
; rkcif_init_tools_vdev(cif_dev, RKCIF_TOOLS_CH
; rkcif_init_tools_vdev(cif_dev, RKCIF_TOOLS_CH
; } /*
根据编译配置设置工作模式 */ #if defined(CONFIG_ROCKCHIP_CIF_WORKMODE_PINGPONG) cif_dev-workmode RKCIF_WORKMODE_PINGPONG; // 乒乓模式双缓冲 #elif defined(CONFIG_ROCKCHIP_CIF_WORKMODE_ONEFRAME) cif_dev-workmode RKCIF_WORKMODE_ONEFRAME; // 单帧模式 #else cif_dev-workmode RKCIF_WORKMODE_PINGPONG; // 默认乒乓模式 #endif /*
根据编译配置决定是否使用 Dummy Buffer */ #if defined(CONFIG_ROCKCHIP_CIF_USE_DUMMY_BUF) cif_dev-is_use_dummybuf true; // 使用虚拟缓冲区避免溢出 #else cif_dev-is_use_dummybuf false; #endif /* RV1106 特例强制禁用 Dummy Buffer */ if (cif_dev-chip_id CHIP_RV1106_CIF) cif_dev-is_use_dummybuf false; /*
初始化 Media Device */ /* 设置 media device 名称如 rkcif-mipi-lvds */ strlcpy(cif_dev-media_dev.model, dev_name(dev), sizeof(cif_dev-media_dev.model)); /* 获取 CSI Host 索引从设备树别名 */ cif_dev-csi_host_idx of_alias_get_id(node, rkcif_mipi_lvds); if (cif_dev-csi_host_idx 0 || cif_dev-csi_host_idx
cif_dev-csi_host_idx 0; // 默认索引 0 cif_dev-media_dev.dev dev; /*
初始化 V4L2 Device */ v4l2_dev cif_dev-v4l2_dev; v4l2_dev-mdev cif_dev-media_dev; // 关联 media device strlcpy(v4l2_dev-name, dev_name(dev), sizeof(v4l2_dev-name)); /* 注册 V4L2 设备 */ ret v4l2_device_register(cif_dev-dev, cif_dev-v4l2_dev); if (ret
return ret; /*
注册 Media Device */ media_device_init(cif_dev-media_dev); ret media_device_register(cif_dev-media_dev); if (ret
{ v4l2_err(v4l2_dev, Failed to register media device: %d\n, ret); goto err_unreg_v4l2_dev; } /*
注册平台子设备异步等待 DPHY */ /* * ★ 关键函数注册异步通知器 * - 解析设备树 endpoint * - 查找上游设备DPHY * - 注册 bound/complete 回调 */ ret rkcif_register_platform_subdevs(cif_dev); if (ret
goto err_unreg_media_dev; /*
注册亮度统计设备仅部分芯片 */ if (cif_dev-chip_id CHIP_RV1126_CIF || cif_dev-chip_id CHIP_RV1126_CIF_LITE || cif_dev-chip_id CHIP_RK3568_CIF) rkcif_register_luma_vdev(cif_dev-luma_vdev, v4l2_dev, cif_dev); /*
将设备加入全局列表 */ mutex_lock(rkcif_dev_mutex); list_add_tail(cif_dev-list, rkcif_device_list); // 加入全局设备链表 mutex_unlock(rkcif_dev_mutex); return 0; // 初始化成功 /* 错误处理 */ err_unreg_media_dev: media_device_unregister(cif_dev-media_dev); err_unreg_v4l2_dev: v4l2_device_unregister(cif_dev-v4l2_dev); return ret; }可以看到大部分步骤1~8都是初始化和基础配置之类的代码简单看看即可重点看后面的代码。
第9步初始化一个media_devicemedia_device管理着所有entity实体第10步初始化一个v4l2_device并将其与media_device关联起来最后注册此v4l2_devicev4l2_device管理着多个subdev这里的内容在前面
总结v4l2子系统时已经讲解过不清楚的可以看看前面的文章第11步注册media_device注册后可生成创建/dev/mediaX(在本例中代表/dev/media
等设备节点第12步我去除了错误检查等冗余代码剩下的是比较重要的函数/** * rkcif_register_platform_subdevs - 注册 CIF 平台的所有子设备 * cif_dev: CIF 设备结构体 * * 功能这是 CIF 驱动初始化的核心函数负责 *
注册视频流设备/dev/videoX *
注册缩放通道设备硬件缩放功能 *
注册工具通道设备调试/统计功能 *
启动异步通知机制等待上游设备连接 * * 这个函数会被 rkcif_plat_init() 调用 * * 返回值0表示成功负数表示错误码 */ static int rkcif_register_platform_subdevs(struct rkcif_device *cif_dev) { int stream_num 0, ret; /*
注册视频流设备核心功能 */ /* * 设置视频流数量为最大 MIPI 通道数 * RKCIF_MAX_STREAM_MIPI 通常是 4对应 MIPI 的 4 个虚拟通道 */ stream_num RKCIF_MAX_STREAM_MIPI; /* * 注册视频流设备 * - 创建 4 个 video 设备节点/dev/video0 ~ /dev/video3 * - 每个节点对应一个 MIPI 虚拟通道 * - true: 表示支持多输入模式 * */ ret rkcif_register_stream_vdevs(cif_dev, stream_num, true); /*
注册缩放通道设备可选功能 */ /* * 注册硬件缩放通道 * - RKCIF_MAX_SCALE_CH 通常是 4 * - 仅 RK3588/RV1106 等芯片支持 * - 功能在硬件层面对图像进行缩放 * - 生成额外的 video 设备节点如 /dev/video4 ~ /dev/video7 * */ ret rkcif_register_scale_vdevs(cif_dev, RKCIF_MAX_SCALE_CH, true); /*
注册工具通道设备调试功能 */ /* * 注册工具/调试通道 * - RKCIF_MAX_TOOLS_CH 通常是 3 * - 用于图像质量分析、统计信息等 * - 仅部分高端芯片支持 * * 功能示例 * - 亮度直方图统计 * - 自动曝光/白平衡参考数据 * - 帧率统计 */ ret rkcif_register_tools_vdevs(cif_dev, RKCIF_MAX_TOOLS_CH, true); /*
初始化异步通知机制 */ /* * 初始化完成量completion * - 用于同步通知 ISP 驱动 * - 当 CIF 准备好后会通过这个机制通知 ISP * - ISP 驱动会等待wait_for_completion这个信号 */ init_completion(cif_dev-cmpl_ntf); /* * 创建内核线程用于通知 ISP * - 线程名notifier isp * - 线程函数notifier_isp_thread * - 参数cif_dev * * 这个线程会等待 CIF 初始化完成然后通知 ISP 驱动 */ kthread_run(notifier_isp_thread, cif_dev, notifier isp); /*
注册异步子设备通知器核心机制 */ /* * 启动 V4L2 异步通知机制 * - 解析设备树查找上游设备DPHY/Sensor * - 注册 notifier等待上游设备注册 * - 当 DPHY 注册时触发 bound 回调 * - 当所有依赖都就绪时触发 complete 回调 * * 这是我们之前详细讨论过的异步绑定机制 */ ret cif_subdev_notifier(cif_dev); /*
返回成功 */ return 0; /* 总是返回 0即使前面有错误也不检查*/ }rkcif_register_stream_vdevs、rkcif_register_scale_vdevs、rkcif_register_tools_vdevs基本是相同的套路和前面讲过的内容大差不差唯一需要注意的是这里注册的设备节点是/dev/video区别于前面的/dev/subdev)以rkcif_register_stream_vdevs为例看一下代码。
/** * rkcif_register_stream_vdevs - 批量注册视频流设备节点 * dev: CIF 设备结构体 * stream_num: 需要注册的视频流数量通常是
个 * is_multi_input: 是否为多输入模式 * * 功能 * - 为每个数据流stream注册一个独立的 video 设备 * - 每个 stream 对应一个 /dev/videoX 节点 * - 例如4 个 MIPI 虚拟通道会生成 /dev/video0 ~ /dev/video3 * * 返回值0表示成功负数表示错误码 */ int rkcif_register_stream_vdevs(struct rkcif_device *dev, int stream_num, bool is_multi_input) { struct rkcif_stream *stream; int i, j, ret; /* 遍历所有需要注册的视频流 */ for (i 0; i stream_num; i) { /* 获取当前流结构体 */ stream dev-stream[i]; /* 关联到父设备 */ stream-cifdev dev; /* 注册单个视频流设备 */ ret rkcif_register_stream_vdev(stream, is_multi_input); if (ret
goto err; /* 注册失败跳转到错误处理 */ } return 0; /* 所有流注册成功 */ err: /* 错误处理注销已成功注册的流设备代码未显示 */ return ret; } /** * rkcif_register_stream_vdev - 注册单个视频流设备 * stream: 视频流结构体 * is_multi_input: 是否为多输入模式 * * 功能 *
初始化 video_device 结构体 *
设置设备操作函数ioctl/fops *
初始化 vb2 缓冲区队列 *
注册为 V4L2 video 设备生成 /dev/videoX *
初始化 media entity用于 media controller * * 返回值0表示成功负数表示错误码 */ static int rkcif_register_stream_vdev(struct rkcif_stream *stream, bool is_multi_input) { struct rkcif_device *dev stream-cifdev; struct v4l2_device *v4l2_dev dev-v4l2_dev; struct video_device *vdev stream-vnode.vdev; struct rkcif_vdev_node *node; int ret 0; char *vdev_name; /*
初始化 video_device 基本信息 */ /* 设置设备名称如 rkcif_mipi_id0 */ strlcpy(vdev-name, vdev_name, sizeof(vdev-name)); /* 获取 vdev_node 结构体 */ node vdev_to_node(vdev); /* 初始化互斥锁保护设备访问 */ mutex_init(node-vlock); /*
配置 video_device 回调和属性 */ /* 设置 ioctl 操作函数集处理用户空间的 ioctl 调用 */ vdev-ioctl_ops rkcif_v4l2_ioctl_ops; /* 设置释放函数设备卸载时调用 */ vdev-release video_device_release_empty; /* 设置文件操作函数集open/close/mmap/poll 等 */ vdev-fops rkcif_fops; /* 设备次设备号-1 表示自动分配 */ vdev-minor -1; /* 关联到父 V4L2 设备 */ vdev-v4l2_dev v4l2_dev; /* 设置互斥锁在 ioctl 时自动加锁 */ vdev-lock node-vlock; /* 设置设备能力标志 */ vdev-device_caps V4L2_CAP_VIDEO_CAPTURE_MPLANE | /* 多平面视频捕获 */ V4L2_CAP_STREAMING; /* 支持流式传输 */ /* 将 stream 保存到 video_device 的私有数据中 */ video_set_drvdata(vdev, stream); /* 设置数据流方向RX 接收/捕获 */ vdev-vfl_dir VFL_DIR_RX; /*
初始化 media pad用于 media controller */ /* 设置 pad 类型为 SINK数据输入端 */ node-pad.flags MEDIA_PAD_FL_SINK; /*
初始化 videobuf2 队列 */ /* * 初始化缓冲区队列 * - 管理用户空间的视频缓冲区 * - 支持 MMAP/USERPTR/DMABUF 等内存类型 * - 处理 QBUF/DQBUF 操作 */ rkcif_init_vb2_queue(node-buf_queue, stream, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); /* 关联缓冲区队列到 video_device */ vdev-queue node-buf_queue; /*
注册 video 设备 */ /* * 注册为 V4L2 video 设备 * - 分配设备号/dev/videoX * - 创建字符设备文件 * - 注册到 V4L2 核心框架 * * VFL_TYPE_VIDEO: 标准视频捕获设备 * -1: 自动分配设备号 */ ret video_register_device(vdev, VFL_TYPE_VIDEO, -
; if (ret
{ v4l2_err(v4l2_dev, video_register_device failed with error %d\n, ret); return ret; } /*
初始化 media entity */ /* * 初始化 media entity 的 pads * - 用于 media controller 框架 * - 描述设备的输入/输出端口 * - 支持 media-ctl 工具查询拓扑 * * 参数说明 * - vdev-entity: video_device 对应的 media entity * - 1: 这个 entity 有 1 个 pad * - node-pad: pad 结构体数组 */ ret media_entity_pads_init(vdev-entity, 1, node-pad); if (ret
goto unreg; /* 初始化失败注销 video 设备 */ /*
注册成功 */ return 0; unreg: /* 错误处理注销已注册的 video 设备 */ video_unregister_device(vdev); return ret; }通过上述过程内核最终会注册4个设备节点/dev/video0~3同理rkcif_register_scale_vdevs会注册4个节点/dev/video4~7rkcif_register_tools_vdevs会注册3个节点/dev/video8~10。
接着看看线程函数notifier_isp_thread这个函数有点特殊他会异步注册后面要讲解的 cif_lvds2_sditf 驱动中的subdev也就是两个驱动配合工作cif_lvds2_sditf 驱动负责分配、设置一个subdevcif_lvds2驱动负责注册这个subdev。
static int notifier_isp_thread(void *data) { // ...... subdev_asyn_register_itf(dev); // ...... return 0; } static int subdev_asyn_register_itf(struct rkcif_device *dev) { // ...... sditf dev-sditf[0]; // 取出cif_lvds2_sditf 驱动中分配的 subdev if (sditf (!sditf-is_combine_mode) (!dev-is_notifier_isp)) { ret v4l2_async_register_subdev_sensor_common(sditf-sd); dev-is_notifier_isp true; } return ret; } int v4l2_async_register_subdev_sensor_common(struct v4l2_subdev *sd) { // ...... 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); // 最终调用到此函数异步注册subdev // ...... sd-subdev_notifier notifier; return 0; // ...... }现在回过头看之前的RK3588数据流向过程两段式接力就明白了下图中 media0 下管理的最底下11个设备节点从哪里生成的了。
rkcif_get_reserved_mem通过这个函数我们可以看出数据经过 cif 存入内存中。
/** * rkcif_get_reserved_mem - 获取 Thunderboot 预留内存 * cif_dev: CIF 设备结构体 * * 功能从设备树中获取专门为快速启动Thunderboot预留的内存区域 * * Thunderboot 应用场景 * - 车载后视摄像头上电后需要立即显示画面 * - 倒车影像不能等待系统完全启动 * - 安防监控需要尽快开始录像 * * 工作原理 *
Bootloader 阶段已经配置好摄像头并使用这块内存 *
内核启动时CIF 驱动直接接管这块内存 *
避免重新分配内存和重新初始化硬件的延迟 * * 返回值0表示成功或无预留内存负数表示错误 */ static int rkcif_get_reserved_mem(struct rkcif_device *cif_dev) { struct device *dev cif_dev-dev; // 设备指针 struct device_node *np; // 设备树节点指针 struct resource r; // 资源结构体保存地址和大小 int ret; /*
从设备树查找预留内存节点 */ /* * 解析设备树属性 memory-region-thunderboot * * 设备树示例 * rkcif: rkciffdce0000 { * memory-region-thunderboot cif_thunderboot; * ↑ phandle 引用 * }; * * 这个函数会找到 cif_thunderboot 对应的设备树节点 * * 注意使用 memory-region-thunderboot 而非标准的 memory-region * 原因Rockchip 的 Thunderboot 是专有的快速启动方案使用专用属性名 */ np of_parse_phandle(dev-of_node, memory-region-thunderboot,
; /* 如果没有找到该属性说明未配置 Thunderboot 模式 */ if (!np) { /* 打印提示信息这不是错误只是没有使用 Thunderboot*/ dev_info(dev, No memory-region-thunderboot specified\n); /* 返回 0 表示成功但没有预留内存 */ /* 驱动会继续工作使用普通的动态内存分配 */ return 0; } /*
解析预留内存的地址和大小 */ /* * 将设备树节点转换为 resource 结构体 * * 设备树示例 * cif_thunderboot: cif-thunderboot70000000 { * reg 0x0 0x70000000 0x0 0x4000000; * ↓ ↓ ↓ ↓ * 高32位 低32位 高32位 低32位 * (起始地址) (大小) * }; * * 解析后的结果 * r.start 0x70000000 // 物理地址起始位置1792MB * r.end 0x73FFFFFF // 物理地址结束位置 * r.flags IORESOURCE_MEM // 资源类型内存 */ ret of_address_to_resource(np, 0, r); /* 如果解析失败说明设备树配置有误 */ if (ret) { dev_err(dev, No memory address assigned to the region\n); return ret; // 返回错误码 } /*
保存预留内存信息到 CIF 设备结构体 */ /* 保存物理地址起始位置 */ cif_dev-resmem_pa r.start; /* 计算并保存内存大小end - start 1*/ cif_dev-resmem_size resource_size(r); /* resource_size() 宏定义 * #define resource_size(res) ((res)-end - (res)-start
* 例如0x73FFFFFF - 0x70000000 1 0x4000000 (64MB) */ /* 标记为 Thunderboot 模式 */ cif_dev-is_thunderboot true; /*
打印调试信息 */ /* * 打印预留内存的物理地址和大小 * 示例输出 * Allocated reserved memory, paddr: 0x70000000, size 0x4000000 * * 注意使用 (u
强制转换因为 * - resmem_pa 和 resmem_size 可能是 64 位类型 * - %x 格式化字符串期望 32 位整数 * - 在 32 位系统上这样转换是安全的 */ dev_info(dev, Allocated reserved memory, paddr: 0x%x, size 0x%x\n, (u
cif_dev-resmem_pa, // 物理地址 (u
cif_dev-resmem_size); // 大小 /*
返回成功 */ return ret; // ret 0成功 }驱动源码解析cif_lvds2_sditf驱动
设备树节点内核会根据此节点生成一个platform_device。
rkcif_mipi_lvds2_sditf { status disabled; port { mipi2_lvds_sditf: endpoint { remote-endpoint isp0_vir1; }; }; }; rkcif_mipi_lvds2_sditf: rkcif-mipi-lvds2-sditf { compatible rockchip,rkcif-sditf; rockchip,cif rkcif_mipi_lvds2; status disabled; };
驱动代码解析probe函数drivers/media/platform/rockchip/cif/subdev-itf.c/** * rkcif_subdev_probe - CIF 子设备SDITF探测函数 * pdev: 平台设备指针 * * 功能初始化 SDITFSerial Data Interface子设备 * * SDITF 作用 * - 处理串行数据接口如 BT656/BT1120 * - 支持多路输入合并Combine Mode * - 作为 V4L2 子设备提供额外的数据通道 * * 返回值0表示成功负数表示错误码 */ static int rkcif_subdev_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct v4l2_subdev *sd; /* V4L2 子设备结构体 */ struct sditf_priv *priv; /* SDITF 私有数据 */ struct device_node *node dev-of_node; int ret; /*
分配并初始化私有数据结构体 */ /* 分配 SDITF 私有数据结构体内存 */ priv devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* 内存分配失败 */ /* 保存设备指针 */ priv-dev dev; /*
初始化 V4L2 子设备 */ /* 获取 v4l2_subdev 结构体指针 */ sd priv-sd; /* * 初始化子设备 * - 设置操作函数集sditf_subdev_ops * - 包含 pad 操作、video 操作等回调 */ v4l2_subdev_init(sd, sditf_subdev_ops); /* * 设置子设备标志 * V4L2_SUBDEV_FL_HAS_DEVNODE * - 创建独立的设备节点/dev/v4l-subdevX * - 允许用户空间直接访问子设备 * - 支持独立的 ioctl 控制 */ sd-flags | V4L2_SUBDEV_FL_HAS_DEVNODE; /* 设置子设备名称用于调试和日志*/ snprintf(sd-name, sizeof(sd-name), rockchip-cif-sditf); /* 关联设备指针 */ sd-dev dev; /*
保存子设备到平台设备私有数据 */ /* * 将 media_entity 保存到平台设备 * - 后续可以通过 platform_get_drvdata() 获取 * - 用于 media controller 拓扑构建 */ platform_set_drvdata(pdev, sd-entity); /*
关联到父 CIF 设备 */ /* * 查找并关联到父 CIF 设备 * - 通过设备树的 rockchip,cif 属性查找 * - 建立父子设备关系 * - 共享硬件资源寄存器、时钟等 * * 设备树示例 * rkcif_mipi_lvds2_sditf: rkcif-mipi-lvds2-sditf { * rockchip,cif rkcif_mipi_lvds2; ← 指向父设备 * }; */ ret rkcif_sditf_attach_cifdev(priv); if (ret
return ret; /* 关联失败返回错误 */ /*
解析设备树配置Combine Mode */ /* * 读取 combine-index 属性 * - 用于多路输入合并模式 * - 例如将 2 路 BT656 合并为 1 路输出 * * 设备树示例 * rkcif_sditf: cif-sditf { * rockchip,combine-index 0; * }; */ ret of_property_read_u32(node, rockchip,combine-index, priv-combine_index); if (ret) { /* 没有配置 combine-index使用普通模式 */ // -- 本例子走此分支 priv-is_combine_mode false; priv-combine_index 0; } else { /* 配置了 combine-index启用合并模式 */ priv-is_combine_mode true; } /*
初始化 Media Controller */ /* * 初始化子设备的 media 实体 * - 创建 pads输入/输出端口 * - 注册到 media controller * - 支持 media-ctl 工具查询 */ ret rkcif_subdev_media_init(priv); if (ret
return ret; /*
启用电源管理 */ /* * 启用 Runtime PM运行时电源管理 * - 设备不使用时自动进入省电模式 * - 使用时自动唤醒 * - 框架会调用 runtime_suspend/resume 回调 */ pm_runtime_enable(pdev-dev); /*
初始化成功 */ return 0; }都是老套路了分配、设置subdev结构体但是注册在哪里SDITF 子设备的注册不是在 probe 阶段完成而是在父 CIF 设备cif_lvds2_sditf 驱动的异步绑定回调中完成。
也就是下图中红框部分的节点上图剩余节点在下篇文章中讲解。