框架总览
源码仓库:基于树莓派代码仓库,分析qcom的camera驱动框架,此代码并非高通最新代码,主要目的是分析学习Camera驱动架构是如何设计的
https://github.com/raspberrypi/linux
内核版本:6.12
驱动代码路径:linux\drivers\media\platform\qcom
源代码文件结构:
linux/drivers/media/platform/qcom# ls
Kconfig Makefile camss venus
linux/drivers/media/platform/qcom/camss# ls
Kconfig camss-csid-gen2.c camss-csiphy-3ph-1-0.c camss-ispif.c camss-vfe-4-8.c camss-vfe.h
Makefile camss-csid-gen2.h camss-csiphy.c camss-ispif.h camss-vfe-480.c camss-video.c
camss-csid-4-1.c camss-csid.c camss-csiphy.h camss-vfe-17x.c camss-vfe-gen1.c camss-video.h
camss-csid-4-7.c camss-csid.h camss-format.c camss-vfe-4-1.c camss-vfe-gen1.h camss.c
camss-csid-gen1.h camss-csiphy-2ph-1-0.c camss-format.h camss-vfe-4-7.c camss-vfe.c camss.h
linux/drivers/media/platform/qcom/venus# ls
Kconfig dbgfs.h hfi.c hfi_msgs.c hfi_plat_bufs_v6.c hfi_venus.c vdec.c venc_ctrls.c
Makefile firmware.c hfi.h hfi_msgs.h hfi_platform.c hfi_venus.h vdec.h
core.c firmware.h hfi_cmds.c hfi_parser.c hfi_platform.h hfi_venus_io.h vdec_ctrls.c
core.h helpers.c hfi_cmds.h hfi_parser.h hfi_platform_v4.c pm_helpers.c venc.c
dbgfs.c helpers.h hfi_helper.h hfi_plat_bufs.h hfi_platform_v6.c pm_helpers.h venc.h
顶层架构:
camss/ - 摄像头子系统(Camera Subsystem),简称CAMSS驱动
venus/ - 视频编解码硬件驱动(VPU,video Processing unit)
kconfig/makefile : 内核配置和编译规则
camss - 主要分为摄像头硬件的驱动,分为多个模块,如 CSID、CSIPHY、ISPIF、VFE、格式处理、视频节点等。
camss.c / camss.h:
CAMSS 主驱动入口,负责整体初始化、资源管理。
camss-csid*.c / camss-csid*.h:
CSID(Camera Serial Interface Decoder)模块,负责 MIPI CSI 数据流的解码,支持不同硬件版本。
camss-csiphy*.c / camss-csiphy*.h:
CSIPHY(Camera Serial Interface PHY)模块,负责 MIPI PHY 层的配置和控制。
camss-format.c / camss-format.h:
格式转换与支持,定义和处理摄像头数据格式。
camss-ispif.c / camss-ispif.h:
ISPIF(Image Signal Processor Interface)模块,负责 ISP 接口的配置。
camss-vfe*.c / camss-vfe*.h:
VFE(Video Front End)模块,负责图像处理前端,支持不同硬件版本。
camss-video.c / camss-video.h:
视频节点管理,负责与 V4L2 框架的交互。
camss-csid-gen1/gen2:
不同硬件代际的 CSID 支持。
Kconfig / Makefile:
camss 子模块的配置和编译规则
venus - 负责视频编解码硬件的驱动,分为核心、固件、HFI 通信、调试、电源管理、解码/编码等模块。
core.c / core.h:
venus 驱动核心,负责整体初始化、资源管理。
firmware.c / firmware.h:
固件加载与管理。
helpers.c / helpers.h:
辅助函数,简化硬件操作。
hfi_*:
HFI(Host Firmware Interface)相关文件,负责与硬件固件通信,包括命令、消息、解析、平台相关缓冲区等。
hfi_platform*.c / hfi_platform*.h:
不同硬件平台的 HFI 支持。
hfi_venus*.c / hfi_venus*.h:
venus 特定的 HFI 实现。
dbgfs.c / dbgfs.h:
调试文件系统支持,便于调试和状态查看。
pm_helpers.c / pm_helpers.h:
电源管理辅助。
vdec.c / vdec.h / vdec_ctrls.c:
视频解码器实现及控制接口。
venc.c / venc.h / venc_ctrls.c:
视频编码器实现及控制接口。
Kconfig / Makefile:
venus 子模块的配置和编译规则。
硬件数据流
数据从摄像头传感器经 MIPI CSI PHY 接收,进入 CSID 解码
|
CSID 输出经 ISPIF 分配到 VF
|
VFE 进行图像预处理后,通过 camss-video 节点暴露给 V4L2,最终到用户空间
Camera Sensor -> [MIPI CSI PHY (CSIPHY)] -> [MIPI CSI Decoder (CSID)] -> [ISPIF] -> [VFE] -> [camss-video (V4L2)] -> User Space (应用层)
CAMSS驱动核心架构
核心驱动文件
camss.c - 顶层管理者,对应平台驱动(platform_driver)
负责整个相机子系统的初始化、资源管理、电源时钟等。它不直接处理视频数据,而是创建并管理所有的子模块,VFE,CSID,CSIPHY,Video节点。通过解析设备树DTS,分配内存,注册v4l2设备实例。
关键函数:camss_probe,camss_remove,
camss-vfe.c - 核心硬件控制器,对应v4l2_subdev
直接操作vfe(video front end)硬件寄存器,开启或关闭VFE硬件数据流,是由camss-video触发的底层动作
关键函数:set_format,set_stream,vfe_isr*
camss-video.c - 缓冲区管理和用户接口,对应v4l2 video device(video_device)
为用户提供/dev/videoX节点。它本身没有硬件逻辑,而是在VFE之上,负责管理DMA缓冲区队列(Queue),将用户空间的内存映射到硬件,并触发VFE开始传输数据。每个VFE的输出端口(RDI0,RDI1,Pix),通常对应一个独立的camss-video实例
关键函数:video_queue_setup,video_buf_queue,video_start_streaming,video_stop_streaming
协同工作流程
1.系统启动
camss.c负责加载,创建设备,初始化camss-vfe和camss-video实例,建立它们之间的关联(camss_video - vfe_line - vfe_device)
2.应用配置
用户空间通过 /dev/v4l-subdevX (VFE) 设置格式 (分辨率、像素格式)。camss-vfe.c 记录这些参数
3.内存缓冲
用户空间打开 /dev/videoX (camss-video.c),申请缓冲区 (REQBUFS),并将内存地址入队 (QBUF)。camss-video.c 将这些地址暂存
4.streaming
用户空间发送 STREAMON --> camss-video.c (vb2_start_streaming) 调用 camss-vfe.c (vfe_set_stream) --> camss-vfe.c 配置硬件寄存器 (格式、裁剪),将第一个缓冲区的地址写入 DMA 寄存器,并启动 VFE 状态机
5.视频流采集
VFE 硬件从 CSID 接收数据,直接 DMA 到内存
|
帧完成后,VFE 触发中断
|
camss-vfe.c (vfe_isr) 捕获中断,调用 camss-video.c 的回调 (vfe_line_frame_done)
|
camss-video.c 将该缓冲区标记为 DONE,唤醒用户空间进程读取数据
|
camss-video.c 从队列中取出下一个缓冲区,通知 camss-vfe.c 更新 DMA 地址,循环继续
6.数据与控制流向图
内核空间 Kernel Space
ioctl / mmap
调用 s_stream/set_fmt
传递 DMA 地址
配置寄存器
中断信号 IRQ
初始化/电源/时钟
创建实例
创建实例
MIPI/并行数据
DMA 写入
用户空间应用
camss-video.c /dev/videoX
camss-vfe.c msm_vfe_x Subdev
camss.c 平台总控驱动
VFE 硬件引擎
传感器 / CSID 数据源
DDR 系统内存

camss
主要数据结构
c
// 子设备资源详情,定义了每个具体硬件模块(如 VFE0, CSID1)所需的资源。
struct camss_subdev_resources {
char *regulators[CAMSS_RES_MAX]; // 稳压器名称 (如 "vdda")
char *clock[CAMSS_RES_MAX]; // 时钟名称列表 (如 "vfe0", "ahb", "axi")
char *clock_for_reset[CAMSS_RES_MAX]; // 用于复位的时钟名称列表
u32 clock_rate[CAMSS_RES_MAX][CAMSS_RES_MAX]; // 支持的时钟频率表
char *reg[CAMSS_RES_MAX]; // 寄存器区域名称 (用于 ioremap)
char *interrupt[CAMSS_RES_MAX]; // 中断名称
// 联合体:指向具体子模块的私有配置
union {
struct csiphy_subdev_resources csiphy;
struct csid_subdev_resources csid;
struct vfe_subdev_resources vfe;
};
};
// 硬件资源配置表
struct camss_resources {
enum camss_version version; // 芯片版本标识 (如 CAMSS_845, CAMSS_8250)
const char *pd_name; // 电源域名称 (如 "top")
// 指向各子模块的资源配置数组 (定义在 camss.c 中,如 csiphy_res_845[])
const struct camss_subdev_resources *csiphy_res;
const struct camss_subdev_resources *csid_res;
const struct camss_subdev_resources *ispif_res;
const struct camss_subdev_resources *vfe_res;
// 互联带宽配置 (ICC)
const struct resources_icc *icc_res;
const unsigned int icc_path_num;
// 硬件实例数量
const unsigned int csiphy_num;
const unsigned int csid_num;
const unsigned int vfe_num;
// 回调函数:用于建立子设备间的媒体链路
int (*link_entities)(struct camss *camss);
};
// 驱动核心上下文,代表整个相机子系统实例
struct camss {
struct v4l2_device v4l2_dev; // V4L2 设备核心结构,注册所有子设备
struct v4l2_async_notifier notifier; // 异步通知器,用于等待传感器等外部子设备就绪
struct media_device media_dev; // 媒体设备结构,用于构建 Media Controller 拓扑
struct device *dev; // 指向平台设备 struct device 的指针
// 子模块数组指针 (由 probe 函数根据 DTS 中的数量动态分配)
struct csiphy_device *csiphy; // CSI PHY 子设备(物理层接口)
struct csid_device *csid; // CSI 解码器子设备(CSI Decoder,协议层)
struct ispif_device *ispif; // ISP 接口子设备(图像信号处理接口)
struct vfe_device *vfe; // 视频前端子设备 (Video Front End,核心图像处理引擎)
atomic_t ref_count; // 引用计数,用于电源管理 (Runtime PM)
// 电源域 (Power Domain) 相关
int genpd_num; // 电源域数量
struct device *genpd; // 通用电源域设备指针
struct device_link *genpd_link; // 设备链接,确保电源域顺序
// 互联时钟 (Interconnect) 带宽管理
struct icc_path *icc_path[ICC_SM8250_COUNT];
const struct camss_resources *res; // 指向当前 SoC 对应的硬件资源配置表
};
主要函数
1.探针函数(probe)-驱动入口
camss_probe,当设备树匹配成功时调用,负责初始化整个相机子系统
c
static int camss_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct camss *camss;
int num_subdevs;
int ret;
// 1. 分配主结构体 camss_device
camss = devm_kzalloc(dev, sizeof(*camss), GFP_KERNEL);
if (!camss)
return -ENOMEM;
// 2. 获取当前 SoC 的硬件资源配置表,通过设备树 compatible 匹配
camss->res = of_device_get_match_data(dev);
atomic_set(&camss->ref_count, 0);
camss->dev = dev;
platform_set_drvdata(pdev, camss);
// 3. 根据配置表中的数量,动态分配子模块csiphy,csid,vfe数组内存
camss->csiphy = devm_kcalloc(dev, camss->res->csiphy_num,
sizeof(*camss->csiphy), GFP_KERNEL);
if (!camss->csiphy)
return -ENOMEM;
camss->csid = devm_kcalloc(dev, camss->res->csid_num, sizeof(*camss->csid),
GFP_KERNEL);
if (!camss->csid)
return -ENOMEM;
if (camss->res->version == CAMSS_8x16 ||
camss->res->version == CAMSS_8x96) {
camss->ispif = devm_kcalloc(dev, 1, sizeof(*camss->ispif), GFP_KERNEL);
if (!camss->ispif)
return -ENOMEM;
}
camss->vfe = devm_kcalloc(dev, camss->res->vfe_num,
sizeof(*camss->vfe), GFP_KERNEL);
if (!camss->vfe)
return -ENOMEM;
// 4. 初始化互联带宽 (ICC) 和 电源域 (Power Domains)
ret = camss_icc_get(camss);
if (ret < 0)
return ret;
ret = camss_configure_pd(camss);
if (ret < 0) {
dev_err(dev, "Failed to configure power domains: %d\n", ret);
return ret;
}
// 5. 初始化所有子设备 (CSIPHY, VFE, CSID, ISPIF)
ret = camss_init_subdevices(camss);
if (ret < 0)
goto err_genpd_cleanup;
// 6. 设置 DMA 掩码 (32-bit 或 64-bit)
ret = dma_set_mask_and_coherent(dev, 0xffffffff);
if (ret)
goto err_genpd_cleanup;
// 7. 注册 V4L2 设备和 Media Device
camss->media_dev.dev = camss->dev;
strscpy(camss->media_dev.model, "Qualcomm Camera Subsystem",
sizeof(camss->media_dev.model));
camss->media_dev.ops = &camss_media_ops;
media_device_init(&camss->media_dev);
camss->v4l2_dev.mdev = &camss->media_dev;
ret = v4l2_device_register(camss->dev, &camss->v4l2_dev);
if (ret < 0) {
dev_err(dev, "Failed to register V4L2 device: %d\n", ret);
goto err_media_device_cleanup;
}
v4l2_async_nf_init(&camss->notifier, &camss->v4l2_dev);
pm_runtime_enable(dev);
// 8. 解析设备树中的端口 (Ports),绑定外部传感器,这会创建 async notifier,等待传感器驱动加载
num_subdevs = camss_of_parse_ports(camss);
if (num_subdevs < 0) {
ret = num_subdevs;
goto err_v4l2_device_unregister;
}
// 9. 注册内部子设备实体 (Entity) 到 Media Controller
ret = camss_register_entities(camss);
if (ret < 0)
goto err_v4l2_device_unregister;
// 10. 创建内部链路 (Link)
ret = camss->res->link_entities(camss);
if (ret < 0)
goto err_register_subdevs;
// 11. 注册异步通知器,完成最终拓扑构建
if (num_subdevs) {
camss->notifier.ops = &camss_subdev_notifier_ops;
ret = v4l2_async_nf_register(&camss->notifier);
if (ret) {
dev_err(dev,
"Failed to register async subdev nodes: %d\n",
ret);
goto err_register_subdevs;
}
} else {
ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev);
if (ret < 0) {
dev_err(dev, "Failed to register subdev nodes: %d\n",
ret);
goto err_register_subdevs;
}
// 如果没有外部传感器,直接注册节点
ret = media_device_register(&camss->media_dev);
if (ret < 0) {
dev_err(dev, "Failed to register media device: %d\n",
ret);
goto err_register_subdevs;
}
}
return 0;
err_register_subdevs:
camss_unregister_entities(camss);
err_v4l2_device_unregister:
v4l2_device_unregister(&camss->v4l2_dev);
v4l2_async_nf_cleanup(&camss->notifier);
pm_runtime_disable(dev);
err_media_device_cleanup:
media_device_cleanup(&camss->media_dev);
err_genpd_cleanup:
camss_genpd_cleanup(camss);
return ret;
}
2.子设备初始化
camss_init_subdevices,编译资源表,调用各子模块的初始化函数
c
static int camss_init_subdevices(struct camss *camss)
{
const struct camss_resources *res = camss->res;
unsigned int i;
int ret;
// 1. 初始化 CSIPHY (物理层)
for (i = 0; i < camss->res->csiphy_num; i++) {
ret = msm_csiphy_subdev_init(camss, &camss->csiphy[i],
&res->csiphy_res[i], i);
if (ret < 0) {
dev_err(camss->dev,
"Failed to init csiphy%d sub-device: %d\n",
i, ret);
return ret;
}
}
// 2. 初始化 VFE (视频前端)
/* note: SM8250 requires VFE to be initialized before CSID */
for (i = 0; i < camss->res->vfe_num; i++) {
ret = msm_vfe_subdev_init(camss, &camss->vfe[i],
&res->vfe_res[i], i);
if (ret < 0) {
dev_err(camss->dev,
"Fail to init vfe%d sub-device: %d\n", i, ret);
return ret;
}
}
// 3. 初始化 CSID (CSI 解码器)
for (i = 0; i < camss->res->csid_num; i++) {
ret = msm_csid_subdev_init(camss, &camss->csid[i],
&res->csid_res[i], i);
if (ret < 0) {
dev_err(camss->dev,
"Failed to init csid%d sub-device: %d\n",
i, ret);
return ret;
}
}
// 4. 初始化 ISPIF (如果存在)
ret = msm_ispif_subdev_init(camss, res->ispif_res);
if (ret < 0) {
dev_err(camss->dev, "Failed to init ispif sub-device: %d\n",
ret);
return ret;
}
return 0;
}
3.实体注册与链路构建
camss_register_entities和camss_link_entities,负责将子模块注册到v4l2/Media框架,并建立它们之间的数据流连接
c
// 注册实体
static int camss_register_entities(struct camss *camss)
{
int i;
int ret;
// 依次注册 CSIPHY, CSID, ISPIF, VFE 到 v4l2_device
for (i = 0; i < camss->res->csiphy_num; i++) {
ret = msm_csiphy_register_entity(&camss->csiphy[i],
&camss->v4l2_dev);
if (ret < 0) {
dev_err(camss->dev,
"Failed to register csiphy%d entity: %d\n",
i, ret);
goto err_reg_csiphy;
}
}
for (i = 0; i < camss->res->csid_num; i++) {
ret = msm_csid_register_entity(&camss->csid[i],
&camss->v4l2_dev);
if (ret < 0) {
dev_err(camss->dev,
"Failed to register csid%d entity: %d\n",
i, ret);
goto err_reg_csid;
}
}
ret = msm_ispif_register_entities(camss->ispif,
&camss->v4l2_dev);
if (ret < 0) {
dev_err(camss->dev, "Failed to register ispif entities: %d\n", ret);
goto err_reg_ispif;
}
for (i = 0; i < camss->res->vfe_num; i++) {
ret = msm_vfe_register_entities(&camss->vfe[i],
&camss->v4l2_dev);
if (ret < 0) {
dev_err(camss->dev,
"Failed to register vfe%d entities: %d\n",
i, ret);
goto err_reg_vfe;
}
}
return 0;
err_reg_vfe:
for (i--; i >= 0; i--)
msm_vfe_unregister_entities(&camss->vfe[i]);
err_reg_ispif:
msm_ispif_unregister_entities(camss->ispif);
i = camss->res->csid_num;
err_reg_csid:
for (i--; i >= 0; i--)
msm_csid_unregister_entity(&camss->csid[i]);
i = camss->res->csiphy_num;
err_reg_csiphy:
for (i--; i >= 0; i--)
msm_csiphy_unregister_entity(&camss->csiphy[i]);
return ret;
}
// 创建媒体链路 (Media Links)
static int camss_link_entities(struct camss *camss)
{
int i, j, k;
int ret;
// 连接 CSIPHY -> CSID
for (i = 0; i < camss->res->csiphy_num; i++) {
for (j = 0; j < camss->res->csid_num; j++) {
ret = media_create_pad_link(&camss->csiphy[i].subdev.entity,
MSM_CSIPHY_PAD_SRC,
&camss->csid[j].subdev.entity,
MSM_CSID_PAD_SINK,
0);
if (ret < 0) {
dev_err(camss->dev,
"Failed to link %s->%s entities: %d\n",
camss->csiphy[i].subdev.entity.name,
camss->csid[j].subdev.entity.name,
ret);
return ret;
}
}
}
// 连接 CSID -> VFE (如果有 ISPIF 则经过 ISPIF)
if (camss->ispif) {
// CSID -> ISPIF
for (i = 0; i < camss->res->csid_num; i++) {
for (j = 0; j < camss->ispif->line_num; j++) {
ret = media_create_pad_link(&camss->csid[i].subdev.entity,
MSM_CSID_PAD_SRC,
&camss->ispif->line[j].subdev.entity,
MSM_ISPIF_PAD_SINK,
0);
if (ret < 0) {
dev_err(camss->dev,
"Failed to link %s->%s entities: %d\n",
camss->csid[i].subdev.entity.name,
camss->ispif->line[j].subdev.entity.name,
ret);
return ret;
}
}
}
// ISPIF -> VFE
for (i = 0; i < camss->ispif->line_num; i++)
for (k = 0; k < camss->res->vfe_num; k++)
for (j = 0; j < camss->vfe[k].res->line_num; j++) {
struct v4l2_subdev *ispif = &camss->ispif->line[i].subdev;
struct v4l2_subdev *vfe = &camss->vfe[k].line[j].subdev;
ret = media_create_pad_link(&ispif->entity,
MSM_ISPIF_PAD_SRC,
&vfe->entity,
MSM_VFE_PAD_SINK,
0);
if (ret < 0) {
dev_err(camss->dev,
"Failed to link %s->%s entities: %d\n",
ispif->entity.name,
vfe->entity.name,
ret);
return ret;
}
}
} else {
// 新架构:CSID 直接连接 VFE
for (i = 0; i < camss->res->csid_num; i++)
for (k = 0; k < camss->res->vfe_num; k++)
// 遍历 VFE 的每一条 Line (RDI/Pix)
for (j = 0; j < camss->vfe[k].res->line_num; j++) {
struct v4l2_subdev *csid = &camss->csid[i].subdev;
struct v4l2_subdev *vfe = &camss->vfe[k].line[j].subdev;
ret = media_create_pad_link(&csid->entity,
MSM_CSID_PAD_FIRST_SRC + j,
&vfe->entity,
MSM_VFE_PAD_SINK,
0);
if (ret < 0) {
dev_err(camss->dev,
"Failed to link %s->%s entities: %d\n",
csid->entity.name,
vfe->entity.name,
ret);
return ret;
}
}
}
return 0;
}
4.电源与时钟辅助函数
主要封装了通用的时钟和电源操作
c
// 启用一组时钟
int camss_enable_clocks(int nclocks, struct camss_clock *clock,
struct device *dev)
{
int ret;
int i;
for (i = 0; i < nclocks; i++) {
ret = clk_prepare_enable(clock[i].clk);
if (ret) {
dev_err(dev, "clock enable failed: %d\n", ret);
goto error;
}
}
return 0;
error:
for (i--; i >= 0; i--)
clk_disable_unprepare(clock[i].clk);
return ret;
}
// 禁用一组时钟
void camss_disable_clocks(int nclocks, struct camss_clock *clock)
{
int i;
for (i = nclocks - 1; i >= 0; i--)
clk_disable_unprepare(clock[i].clk);
}
// 增加时钟频率的安全余量
// 防止因计算误差导致频率略低于硬件需求
inline void camss_add_clock_margin(u64 *rate)
{
*rate *= CAMSS_CLOCK_MARGIN_NUMERATOR;
*rate = div_u64(*rate, CAMSS_CLOCK_MARGIN_DENOMINATOR);
}
这里解释一下icc,在现代高通 SoC (如 SDM845, SM8250, SM8450 等) 中,芯片内部各个模块(如 VFE、DDR 内存、CPU)之间不是直接连线的,而是通过一个复杂的片上网络 (NoC, Network on Chip) 连接。
- ICC (Interconnect Consumer): 指需要传输数据的模块(这里是 CAMSS)
- Bandwidth (带宽): 指数据传输的速度需求
- 机制原理:内核需要根据当前的负载动态调整 NoC 的时钟频率和电压。如果不需要传数据,就把带宽设为 0,让总线进入低功耗状态;如果需要传 4K 视频,就申请高带宽,提高总线频率
5.资源表定义
camss.c中包含了大量静态常量数组,定义了不同芯片的详细参数,目的是为了支持多平台
c
// SDM845 的 VFE0 资源配置
static const struct camss_subdev_resources vfe_res_845[] = {
/* VFE0 */
{
// 无专用稳压器
.regulators = {},
// 定义该模块所需的所有时钟名称
.clock = { "camnoc_axi", "cpas_ahb", "slow_ahb_src",
"soc_ahb", "vfe0", "vfe0_axi",
"vfe0_src", "csi0",
"csi0_src"},
// 定义每个时钟支持的频率档位 (Hz)
.clock_rate = { { 0 },
{ 0 },
{ 80000000 },
{ 0 },
{ 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 },
{ 0 },
{ 320000000 },
{ 19200000, 75000000, 384000000, 538666667 },
{ 384000000 } },
// 寄存器区域名
.reg = { "vfe0" },
// 中断名
.interrupt = { "vfe0" },
.vfe = {
.line_num = 4, // 支持 4 条处理线 (Line 0-3)
.has_pd = true, // 是否有独立电源域
.hw_ops = &vfe_ops_170, // 指向具体的硬件操作函数集
.formats_rdi = &vfe_formats_rdi_845, // 支持的 RDI 格式表
.formats_pix = &vfe_formats_pix_845 // 支持的 Pix 格式表
}
},
/* VFE1 */
{
.regulators = {},
.clock = { "camnoc_axi", "cpas_ahb", "slow_ahb_src",
"soc_ahb", "vfe1", "vfe1_axi",
"vfe1_src", "csi1",
"csi1_src"},
.clock_rate = { { 0 },
{ 0 },
{ 80000000 },
{ 0 },
{ 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 },
{ 0 },
{ 320000000 },
{ 19200000, 75000000, 384000000, 538666667 },
{ 384000000 } },
.reg = { "vfe1" },
.interrupt = { "vfe1" },
.vfe = {
.line_num = 4,
.has_pd = true,
.hw_ops = &vfe_ops_170,
.formats_rdi = &vfe_formats_rdi_845,
.formats_pix = &vfe_formats_pix_845
}
},
/* VFE-lite */
{
.regulators = {},
.clock = { "camnoc_axi", "cpas_ahb", "slow_ahb_src",
"soc_ahb", "vfe_lite",
"vfe_lite_src", "csi2",
"csi2_src"},
.clock_rate = { { 0 },
{ 0 },
{ 80000000 },
{ 0 },
{ 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 },
{ 320000000 },
{ 19200000, 75000000, 384000000, 538666667 },
{ 384000000 } },
.reg = { "vfe_lite" },
.interrupt = { "vfe_lite" },
.vfe = {
.is_lite = true,
.line_num = 4,
.hw_ops = &vfe_ops_170,
.formats_rdi = &vfe_formats_rdi_845,
.formats_pix = &vfe_formats_pix_845
}
}
};
// 将资源表绑定到设备树 compatible 字符串
static const struct of_device_id camss_dt_match[] = {
{ .compatible = "qcom,msm8916-camss", .data = &msm8916_resources },
{ .compatible = "qcom,msm8996-camss", .data = &msm8996_resources },
{ .compatible = "qcom,sdm660-camss", .data = &sdm660_resources },
{ .compatible = "qcom,sdm845-camss", .data = &sdm845_resources },
{ .compatible = "qcom,sm8250-camss", .data = &sm8250_resources },
{ .compatible = "qcom,sc8280xp-camss", .data = &sc8280xp_resources },
{ }
};
6.小结
camss头文件中定义了驱动的核心设备,struct camss 作为总控制器,以及 struct camss_resources 作为硬件抽象层的数据源。
camss实现了驱动的生命周期管理:
1.匹配主控:通过of_device_id识别Soc
2.分配子模块:根据 SoC 特性动态分配子模块数组
3.初始化:按依赖顺序初始化 PHY, VFE, CSID
4.拓扑:注册实体并建立内部数据链路
5.异步:等待外部传感器驱动加载并完成最终链路连接

camss-video
camss-video是QCOM MSM Camera子系统的v4l2视频节点层,是用户空间应用程序与底层硬件VFE/ISP之间的标准接口层
实现了一下功能:
1.暴露设备节点:创建/dev/videoX字符设备,供用户空间打开和操作
2.缓冲求管理:实现 Videobuf2 (VB2) 框架,处理用户空间缓冲区的队列 (QBUF)、去队列 (DQBUF) 和 DMA 地址映射
3.流媒体控制:响应 STREAMON/STREAMOFF 命令,通过 Media Controller 框架触发上游子设备(如 VFE Subdev)启动或停止数据流
4.格式协商:处理 V4L2 格式 IOCTLs (S_FMT, G_FMT, TRY_FMT),将用户请求的像素格式转换为底层硬件支持的格式,并计算行宽对齐 (bytesperline) 和图像大小 (sizeimage)
5.媒体管线集成:作为 Media Pipeline 的 Sink 端,管理与上游 Subdev 的连接关系
主要数据结构
c
// 自定义缓冲封装,目的是扩展标准的vb2_v4l2_buffer,用以支持
// 存储驱动需要的DMA物理地址数组(支持YUV等多平面格式)和队列指针
struct camss_buffer {
struct vb2_v4l2_buffer vb; // 嵌入标准的V4L2 VB2缓冲结构
dma_addr_t addr[3]; // 存储多平面 (Multi-planar) 的物理 DMA 地址
struct list_head queue; // 用于挂入待处理链表的节点
};
struct camss_video;
// 回调操作集,定义了视频节点与硬件驱动的交互接口
struct camss_video_ops {
// 当用户空间QBUF后,调用此函数将缓冲交给底层硬件
int (*queue_buffer)(struct camss_video *vid, struct camss_buffer *buf);
// 当流停止或出错时,调用此函数清理队列并返回缓冲给用户
int (*flush_buffers)(struct camss_video *vid,
enum vb2_buffer_state state);
};
struct camss_video {
struct camss *camss; // 反向引用 CAMSS 主设备
struct vb2_queue vb2_q; // Videobuf2 队列核心结构
struct video_device vdev; // V4L2 视频设备结构 (对应 /dev/videoX)
struct media_pad pad; // Media Controller Pad (作为 Sink 端)
struct v4l2_format active_fmt; // 当前激活的视频格式配置
enum v4l2_buf_type type; // 缓冲类型 (通常为 VIDEO_CAPTURE_MPLANE)
struct media_pipeline pipe; // 媒体管线状态
const struct camss_video_ops *ops; // 指向底层硬件操作回调
struct mutex lock; // 保护设备全局状态
struct mutex q_lock; // 保护设备全局状态
unsigned int bpl_alignment; // 行宽字节对齐要求 (硬件特性)
unsigned int line_based; // 标志位:是否基于行的限制 (影响最大高度)
const struct camss_format_info *formats; // 支持的格式表
unsigned int nformats; // 支持格式的数量
};
主要函数及关键功能模块
1.Videobuf2队列操作
主要实现Videobuf2 (VB2) 框架的回调函数,负责管理 DMA 缓冲区的生命周期、数据流的启动与停止,以及与底层硬件(VFE)的交互。
c
/* -----------------------------------------------------------------------------
* Video queue operations
*/
// 当用户空间调用VIDIOC_REQBUFS申请缓冲区时调用,用于告知VB2框架
static int video_queue_setup(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct camss_video *video = vb2_get_drv_priv(q); // 获取私有数据,在msm_video_register中,会q->drv_priv = video,将私有指针存入公共队列,此处是取出私有数据
const struct v4l2_pix_format_mplane *format = // 获取当前激活的格式
&video->active_fmt.fmt.pix_mp;
unsigned int i;
// 1. 验证阶段
// 如果 num_planes 不为 0,说明用户空间指定了大小
// 驱动必须检查用户指定的大小是否满足硬件要求
if (*num_planes) {
// 平面数量必须一致
if (*num_planes != format->num_planes)
return -EINVAL;
for (i = 0; i < *num_planes; i++)
if (sizes[i] < format->plane_fmt[i].sizeimage) // 用户指定的大小不能小于格式要求
return -EINVAL;
return 0;
}
// 2.查询阶段
*num_planes = format->num_planes; // 告诉用户空间:需要几个平面,比如NV12需要2个平面
for (i = 0; i < *num_planes; i++)
sizes[i] = format->plane_fmt[i].sizeimage; // 告诉用户空间:每个平面的大小
return 0;
}
// 缓冲区初始化,VB2 分配完内存后调用,主要用于提取 DMA 地址
static int video_buf_init(struct vb2_buffer *vb)
{
// 从底层vb转换成中间层的vb2_v4l2_buffer,以便访问V4L2相关字段
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
// 获取私有数据,在msm_video_register中,会q->drv_priv = video,将私有指针存入公共队列,此处是取出私有数据
struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
// 将vb2_v4l2_buffer转换为自定义的camss_buffer,以便访问addr数组
struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer,
vb);
const struct v4l2_pix_format_mplane *format =
&video->active_fmt.fmt.pix_mp;
struct sg_table *sgt;
unsigned int i;
// 遍历所有平面,提取DMA物理地址
for (i = 0; i < format->num_planes; i++) {
sgt = vb2_dma_sg_plane_desc(vb, i); // 获取 Scatter-Gather Table
if (!sgt)
return -EFAULT;
buffer->addr[i] = sg_dma_address(sgt->sgl);
}
// V4L2_PIX_FMT_NV12格式修正
if (format->pixelformat == V4L2_PIX_FMT_NV12 ||
format->pixelformat == V4L2_PIX_FMT_NV21 ||
format->pixelformat == V4L2_PIX_FMT_NV16 ||
format->pixelformat == V4L2_PIX_FMT_NV61)
buffer->addr[1] = buffer->addr[0] +
format->plane_fmt[0].bytesperline * // Y平面行宽
format->height; // Y平面高度
return 0;
}
// 缓冲区准备,在User Space调用 QBUF(入队)时调用。用于校验缓冲区大小并设置有效载荷(Payload)
static int video_buf_prepare(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
// 获取私有数据,在msm_video_register中,会q->drv_priv = video,将私有指针存入公共队列,此处是取出私有数据
struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
const struct v4l2_pix_format_mplane *format =
&video->active_fmt.fmt.pix_mp;
unsigned int i;
// 确保分配的内存空间足够容纳当前格式的图像
for (i = 0; i < format->num_planes; i++) {
if (format->plane_fmt[i].sizeimage > vb2_plane_size(vb, i))
return -EINVAL;
// 设置有效payload
vb2_set_plane_payload(vb, i, format->plane_fmt[i].sizeimage);
}
// 设置场模式,不过Camera Capture通常是 progressive (逐行扫描),所以设置为 NONE
vbuf->field = V4L2_FIELD_NONE;
return 0;
}
// 缓冲区入队,当User Space调用QBUF时触发,是数据流向硬件传递的关键一步
static void video_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
// 获取私有数据,在msm_video_register中,会q->drv_priv = video,将私有指针存入公共队列,此处是取出私有数据
struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer,
vb);
// 调用底层硬件的操作函数,将缓冲区交给硬件处理,硬件驱动会将buffer->addr写入寄存器,或加入到DMA描述符环形队列中
video->ops->queue_buffer(video, buffer);
}
static int video_check_format(struct camss_video *video)
{
struct v4l2_pix_format_mplane *pix = &video->active_fmt.fmt.pix_mp;
struct v4l2_format format;
struct v4l2_pix_format_mplane *sd_pix = &format.fmt.pix_mp;
int ret;
sd_pix->pixelformat = pix->pixelformat;
ret = video_get_subdev_format(video, &format);
if (ret < 0)
return ret;
if (pix->pixelformat != sd_pix->pixelformat ||
pix->height != sd_pix->height ||
pix->width != sd_pix->width ||
pix->num_planes != sd_pix->num_planes ||
pix->field != format.fmt.pix_mp.field)
return -EPIPE;
return 0;
}
// User Space调用STREAMON且队列中有缓冲区时调用,负责整个Media Pipeline
static int video_start_streaming(struct vb2_queue *q, unsigned int count)
{
// 获取私有数据,在msm_video_register中,会q->drv_priv = video,将私有指针存入公共队列,此处是取出私有数据
struct camss_video *video = vb2_get_drv_priv(q);
struct video_device *vdev = &video->vdev;
struct media_entity *entity;
struct media_pad *pad;
struct v4l2_subdev *subdev;
int ret;
// 1,分配并启动媒体管线资源,主要是电源
ret = video_device_pipeline_alloc_start(vdev);
if (ret < 0) {
dev_err(video->camss->dev, "Failed to start media pipeline: %d\n", ret);
goto flush_buffers;
}
// 2.格式一致性检查,防止软件格式不匹配导致硬件无法正确处理
ret = video_check_format(video);
if (ret < 0)
goto error;
// 3.级联启动上游Subdev,通常是ISP或CSI解码器等,确保数据流动起来
entity = &vdev->entity;
while (1) {
pad = &entity->pads[0];
// 如果当前 Pad 不是接收端(Sink),说明已经到了源头,退出
if (!(pad->flags & MEDIA_PAD_FL_SINK))
break;
// 获取连接到上游实体的pad
pad = media_pad_remote_pad_first(pad);
if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
break;
entity = pad->entity;
subdev = media_entity_to_v4l2_subdev(entity);
// 向上游发送s_stream(1)命令,启动数据流,直到所有上游设备都启动成功
ret = v4l2_subdev_call(subdev, video, s_stream, 1);
if (ret < 0 && ret != -ENOIOCTLCMD)
goto error;
}
return 0;
error:
// 停止pipeline
video_device_pipeline_stop(vdev);
flush_buffers:
// 调用底层 Flush 回调,将所有已排队的缓冲区返回给用户空间,状态设为 VB2_BUF_STATE_QUEUED
video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED);
return ret;
}
// 当用户空间调用STREAM_OFF时调用,负责停止硬件并清理缓冲区
static void video_stop_streaming(struct vb2_queue *q)
{
struct camss_video *video = vb2_get_drv_priv(q);
struct video_device *vdev = &video->vdev;
struct media_entity *entity;
struct media_pad *pad;
struct v4l2_subdev *subdev;
int ret;
// 1.级联停止上游Subdev
entity = &vdev->entity;
while (1) {
pad = &entity->pads[0];
if (!(pad->flags & MEDIA_PAD_FL_SINK))
break;
pad = media_pad_remote_pad_first(pad);
if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
break;
entity = pad->entity;
subdev = media_entity_to_v4l2_subdev(entity);
/* 向上游发送 s_stream(0) 命令,停止数据流 */
ret = v4l2_subdev_call(subdev, video, s_stream, 0);
if (ret) {
dev_err(video->camss->dev, "Video pipeline stop failed: %d\n", ret);
return;
}
}
// 2. 停止管线电源管理
video_device_pipeline_stop(vdev);
// 3. 清理缓冲区,调用驱动flush回调,状态设为VB2_BUF_STATE_ERROR,通知用户空间流异常终止
video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR);
}
static const struct vb2_ops msm_video_vb2_q_ops = {
.queue_setup = video_queue_setup, // 内存申请
.wait_prepare = vb2_ops_wait_prepare, // 锁定互斥锁
.wait_finish = vb2_ops_wait_finish, // 解锁互斥锁
.buf_init = video_buf_init, // 缓冲区分配后初始化
.buf_prepare = video_buf_prepare, // QBUF 时准备
.buf_queue = video_buf_queue, // QBUF 时入队
.start_streaming = video_start_streaming, // STREAMON
.stop_streaming = video_stop_streaming, // STREAMOFF
};
video_buf_init函数可以看出v4l2驱动中,一个缓冲区通常会被包裹3层:camss_buffer(硬件专有) -> vb2_v4l2_buffer(视频标准) -> vb2_buffer(内存核心)
以下层次是软件架构上的,不是物理视角的,如果是物理视角的,层次应该反过来
1.最底层:struct vb2_buffer (vb2)
这是 Videobuf2 核心框架定义的,只定义最基础的东西,它不知道什么是 V4L2
c
struct vb2_buffer {
struct vb2_queue *vb2_queue;
unsigned int index; // 缓冲区在队列中的唯一编号
unsigned int type; // 缓冲区类型(比如:V4L2_BUF_TYPE_VIDEO_CAPTURE)
unsigned int memory; // 内存访问模式(比如:V4L2_MEMORY_MMAP、DMABUF、USERPTR)
unsigned int num_planes; // 平面数量(nv12:2[Y平面+UV平面],RGB:1)
u64 timestamp; // 时间戳,通常由驱动在捕获完成时填入
struct media_request *request; // 允许将配置(Controls)和缓冲区绑定在一起提交,保证操作的原子性
struct media_request_object req_obj;
enum vb2_buffer_state state; // 核心状态机(VB2_BUF_STATE_DEQUEUED,*_QUEUED,*_ACTIVE,*_DONE,*_ERROR)
unsigned int synced:1; // DMA 同步标志
unsigned int prepared:1; // 准备标志,表示该缓冲区已准备就绪
unsigned int copied_timestamp:1; // 时间戳复制标志,用于 M2M(内存到内存)设备,表示该捕获缓冲区的时间戳是从输出缓冲区复制过来的
unsigned int skip_cache_sync_on_prepare:1;// 跳过缓存同步标志,当设置此位时,缓冲区的 ->prepare() 函数会跳过缓存同步/无效化操作
unsigned int skip_cache_sync_on_finish:1; // 跳过缓存同步标志,当设置此位时,缓冲区的 ->finish() 函数会跳过缓存同步/无效化操作
struct vb2_plane planes[VB2_MAX_PLANES]; // 物理平面信息,每个元素描述图像的一个"平面"
struct list_head queued_entry; // 待处理队列,当缓冲区处于 QUEUED 状态时,它会被挂入驱动的一个"待处理链表"中
struct list_head done_entry; // 已完成队列,当缓冲区处理完成后(DONE),它会被移动到"已完成链表"中,等待用户空间调用 DQBUF 取走
};
2.中间层:struct vb2_v4l2_buffer(vbuf)
是 V4L2 框架在底层基础上扩展的,继承了底层的 vb2_buffer,并增加了 V4L2 特有的信息
c
struct vb2_v4l2_buffer {
struct vb2_buffer vb2_buf;
__u32 flags; // v4l2标志位,对应v4l2_buffer中的flags(V4L2_BUF_FLAG_KEYFRAME,V4L2_BUF_FLAG_PFRAME...)
__u32 field; // 场序,逐行or隔行(V4L2_FIELD_NONE,V4L2_FIELD_INTERLACED)
struct v4l2_timecode timecode; // 时间码,用于视频录制,包含小时、分钟、秒、帧数等信,普通camera较少使用
__u32 sequence; // 帧计数器序列号,用户可以用来检测是否丢帧
__s32 request_fd; // 请求文件描述符
bool is_held; // 保持标志,用于M2M设备,
struct vb2_plane planes[VB2_MAX_PLANES]; // 冗余平面信息,一般不使用
};
3.最顶层:struct camss_buffer (buffer)也是驱动层
这是驱动定义的,集成了v4l2的buffer,并增加了硬件特有的信息:DMA物理地址数组addr[]、队列节点queue
c
struct camss_buffer {
struct vb2_v4l2_buffer vb; // 嵌入标准的V4L2 VB2缓冲结构
dma_addr_t addr[3]; // 存储多平面 (Multi-planar) 的物理 DMA 地址
struct list_head queue; // 用于挂入待处理链表的节点
};
2.V4L2 IOCTL控制操作
将标准的v4l2命令映射到具体的驱动函数上
c
static const struct v4l2_ioctl_ops msm_vid_ioctl_ops = {
.vidioc_querycap = video_querycap, // 查询设备基本能力,对应VIDIOC_QUERYCAP
.vidioc_enum_fmt_vid_cap = video_enum_fmt, // 枚举支持的像素格式,对应VIDIOC_ENUM_FMT
.vidioc_enum_framesizes = video_enum_framesizes, // 枚举支持的分辨率,对应VIDIOC_ENUM_FRAMESIZES
.vidioc_g_fmt_vid_cap_mplane = video_g_fmt, // 获取当前格式,对应VIDIOC_G_FMT
.vidioc_s_fmt_vid_cap_mplane = video_s_fmt, // 设置格式,对应VIDIOC_S_FMT
.vidioc_try_fmt_vid_cap_mplane = video_try_fmt, // 尝试设置格式,对应VIDIOC_TRY_FMT
.vidioc_reqbufs = vb2_ioctl_reqbufs, // 请求缓冲区,对应VIDIOC_REQBUFS
.vidioc_querybuf = vb2_ioctl_querybuf, // 查询缓冲区状态,对应VIDIOC_QUERYBUF
.vidioc_qbuf = vb2_ioctl_qbuf, // 入队缓冲区,对应VIDIOC_QBUF
.vidioc_expbuf = vb2_ioctl_expbuf, // 导出缓冲区,对应VIDIOC_EXPBUF
.vidioc_dqbuf = vb2_ioctl_dqbuf, // 出队缓冲区,对应VIDIOC_DQBUF
.vidioc_create_bufs = vb2_ioctl_create_bufs, // 创建缓冲区,对应VIDIOC_CREATE_BUFS
.vidioc_prepare_buf = vb2_ioctl_prepare_buf, // 准备缓冲区,对应VIDIOC_PREPARE_BUF
.vidioc_streamon = vb2_ioctl_streamon, // 开始流,对应VIDIOC_STREAMON
.vidioc_streamoff = vb2_ioctl_streamoff, // 停止流,对应VIDIOC_STREAMOFF
.vidioc_enum_input = video_enum_input, // 枚举输入,对应VIDIOC_ENUM_INPUT
.vidioc_s_input = video_s_input, // 设置输入,对应VIDIOC_S_INPUT
.vidioc_g_input = video_g_input, // 获取当前输入,对应VIDIOC_G_INPUT
};
3.V4L2文件操作
定义了字符设备文件操作的标准接口msm_vid_fops,用于管理Camera管线pipeline的电源状态,在 Linux V4L2 框架中,通常遵循"打开设备即上电,关闭设备即断电"的原则。
c
/* -----------------------------------------------------------------------------
* V4L2 file operations
*/
static int video_open(struct file *file)
{
struct video_device *vdev = video_devdata(file); // 获取与设备关联的 video_device
struct camss_video *video = video_drvdata(file); // 从video_device获取camss_video结构体
struct v4l2_fh *vfh; // v4l2标准的文件句柄结构体,用于管理流控,事件等
int ret;
mutex_lock(&video->lock);
// 1. 分配并初始化 V4L2 文件句柄 (File Handle)
vfh = kzalloc(sizeof(*vfh), GFP_KERNEL);
if (vfh == NULL) {
ret = -ENOMEM;
goto error_alloc;
}
// 初始化句柄
v4l2_fh_init(vfh, vdev);
// 将其添加到设备的句柄列表中
v4l2_fh_add(vfh);
/* 将句柄关联到当前的文件结构 */
file->private_data = vfh;
// 2.电源管理 - 获取电源资源并开启pipeline
// 调用 Media Controller 的电源管理接口,增加引用计数,如果这是第一个打开的节点,硬件将会上电(Power ON),会触发上游 Subdev 比如 Sensor的电源控制
ret = v4l2_pipeline_pm_get(&vdev->entity);
if (ret < 0) {
dev_err(video->camss->dev, "Failed to power up pipeline: %d\n",
ret);
goto error_pm_use;
}
mutex_unlock(&video->lock);
return 0;
error_pm_use:
// 如果电源开启失败,释放刚才分配的句柄资源
v4l2_fh_release(file);
error_alloc:
mutex_unlock(&video->lock);
return ret;
}
static int video_release(struct file *file)
{
// 获取与设备关联的 video_device
struct video_device *vdev = video_devdata(file);
// 1.VB2 缓冲区清理
// 取消所有已排队的缓冲区 (DQBUF 所有 buffer)
// 释放通过 REQBUFS 分配的 DMA 内存
// 如果正在streaming,停止流
vb2_fop_release(file);
// 2.电源管理:关闭管线,power down pipeline
// 减少引用计数。当所有关联的设备节点都关闭后
// 硬件断电
v4l2_pipeline_pm_put(&vdev->entity);
// 3. 清理私有数据指针
file->private_data = NULL;
return 0;
}
static const struct v4l2_file_operations msm_vid_fops = {
.owner = THIS_MODULE, // 模块所有权,用于防止模块在使用中被卸载
.unlocked_ioctl = video_ioctl2, // 指向标准的 V4L2 ioctl 处理函数(定义在其他地方)
.open = video_open, // 绑定打开函数
.release = video_release, // 绑定关闭函数
.poll = vb2_fop_poll, // 支持 select()/poll() 机制,用于非阻塞 I/O 通知
.mmap = vb2_fop_mmap, // 支持 mmap 系统调用,让用户空间映射驱动的 DMA 内存
.read = vb2_fop_read, // 支持 read() 系统调用,虽然 Camera 通常用 MMAP,但为了兼容性保留
};
camss-vfe
camss子设备有csid,csiphy,ispif,和vfe,子设备的驱动框架都是一样的,这里只分析vfe的驱动代码(camss-vfe.h,camss-vfe.c)
高通的VFE是一个比较独特的架构,可以概括为"一线一节点(one line,one node)",VFE 硬件内部通常包含多条处理流水线(Line),例如一条用于图像处理(Pix),多条用于原始数据直通(RDI)。msm_vfe_register_entities为每一条硬件流水线都创建了对应的软件实体
主要数据结构
struct vfe_device - VFE设备的总控结构,代表整个VFE硬件模块(vfe0,vfe1)的核心结构体
c
struct vfe_device {
struct camss *camss; // 指向父级 CAMSS 子系统结构体
u8 id; // VFE 实例 ID (0, 1, ...)
void __iomem *base; // VFE 寄存器基地址 (IO 映射)
u32 irq; // 中断号
char irq_name[30]; // 中断名称
struct camss_clock *clock; // 时钟资源数组
int nclocks; // 时钟数量
// 电源与流控制
struct completion reset_complete; // 复位完成信号量
struct completion halt_complete; // 停止完成信号量
struct mutex power_lock; // 电源锁 (保护 power_count)
int power_count; // 电源引用计数 (用于自动休眠/唤醒)
struct mutex stream_lock; // 流控制锁
int stream_count; // 流引用计数
spinlock_t output_lock;
// 硬件映射表:Write Master (WM) -> Line ID,用于在中断中快速判断哪个 WM 对应哪条处理线
enum vfe_line_id wm_output_map[MSM_VFE_IMAGE_MASTERS_NUM];
// 处理线数组 (RDI0, RDI1, RDI2, PIX)
struct vfe_line line[VFE_LINE_NUM_MAX];
// 寄存器更新标志位
u32 reg_update;
// 标记掉电前是否正在推流
u8 was_streaming;
// 资源与操作集
const struct vfe_subdev_resources *res; // 硬件资源描述 (Lite/Full, 线数等)
const struct vfe_hw_ops_gen1 *ops_gen1; // 代系特定的操作集
struct vfe_isr_ops isr_ops; // 中断处理回调集合
struct camss_video_ops video_ops; // Video 节点操作集
// 电源域 (Power Domain)
struct device *genpd;
struct device_link *genpd_link;
};
struct vfe_line - 处理流水线结构,vfe内部包含多条独立的处理线(line),每条线可以独立配置格式和裁剪
c
struct vfe_line {
enum vfe_line_id id; // 线 ID (VFE_LINE_RDI0, VFE_LINE_PIX 等)
// V4L2 Subdev (控制节点 /dev/v4l-subdevX)
struct v4l2_subdev subdev;
struct media_pad pads[MSM_VFE_PADS_NUM]; // 2 个端口:Sink(输入), Src(输出)
struct v4l2_mbus_framefmt fmt[MSM_VFE_PADS_NUM]; // 各端口的格式配置
struct v4l2_rect compose; // 合成矩形 (Scaler 输出尺寸)
struct v4l2_rect crop; // 裁剪矩形 (输入裁剪)
// Video Device (数据节点 /dev/videoX)
struct camss_video video_out;
// 输出状态与缓冲管理
struct vfe_output output;
// 支持的格式列表
const struct camss_format_info *formats;
unsigned int nformats;
};
struct vfe_output - 输出缓冲管理,负责管理DMA缓冲区的队列、双缓冲切换(ping-Pong)和帧队列
c
struct vfe_output {
u8 wm_num; // 使用的 Write Master 数量
u8 wm_idx[3]; // WM 索引数组
// 双缓冲机制 (Ping-Pong Buffer)
struct camss_buffer *buf[2]; // 当前硬件正在使用的两个缓冲区
struct camss_buffer *last_buffer; // 最后一个完成的缓冲区
// 待处理缓冲队列
struct list_head pending_bufs; // 用户空间 Queue 但尚未交给硬件的缓冲
unsigned int drop_update_idx; // 丢帧计数器索引
// 代系特定数据,Gen1 和 Gen2 寄存器差异
union {
struct {
int active_buf;
int wait_sof;
} gen1;
struct {
int active_num;
} gen2;
};
enum vfe_output_state state; // 输出状态机 (OFF, ON, STOPPING 等)
unsigned int sequence; // 帧序列号,用于同步和调试
int wait_reg_update; // 等待寄存器更新标志
struct completion sof; // SOF (Start of Frame) 完成信号
struct completion reg_update; // 寄存器更新完成信号
};
struct vfe_hw_ops - 硬件抽象层(HAL),由于不同的高通芯片8x16,8x97,845等VFE寄存器定义不同,驱动使用此结构体进行抽象
c
struct vfe_hw_ops {
void (*enable_irq_common)(struct vfe_device *vfe); // 启用通用中断
void (*global_reset)(struct vfe_device *vfe); // 全局复位
u32 (*hw_version)(struct vfe_device *vfe); // 获取硬件版本
irqreturn_t (*isr)(int irq, void *dev); // 顶级中断入口
void (*isr_read)(struct vfe_device *vfe, u32 *value0, u32 *value1);
void (*pm_domain_off)(struct vfe_device *vfe); // 电源域开关
int (*pm_domain_on)(struct vfe_device *vfe); // 电源域开关
void (*reg_update)(struct vfe_device *vfe, enum vfe_line_id line_id); // 寄存器更新
void (*reg_update_clear)(struct vfe_device *vfe,
enum vfe_line_id line_id);
void (*subdev_init)(struct device *dev, struct vfe_device *vfe); // 子设备初始化
int (*vfe_disable)(struct vfe_line *line); // 禁用 VFE 线路上的流
int (*vfe_enable)(struct vfe_line *line); // 启用 VFE 线路上的流
int (*vfe_halt)(struct vfe_device *vfe); // 紧急停止
void (*violation_read)(struct vfe_device *vfe); // 读取违规状态(直接读取寄存器)
void (*vfe_wm_stop)(struct vfe_device *vfe, u8 wm); // 停止指定的 Write Master
};
主要函数
1.VFE实体注册
msm_vfe_register_entities是驱动探针阶段调用的核心函数,构建Media Controller拓扑
c
// v4l2 subdev core,处理子设备的基础控制
static const struct v4l2_subdev_core_ops vfe_core_ops = {
.s_power = vfe_set_power, // 电源管理:打开/关闭硬件时钟和电源
};
// v4l2 subdev video,控制数据流的启停
static const struct v4l2_subdev_video_ops vfe_video_ops = {
.s_stream = vfe_set_stream, // 流控制:启动/停止视频数据流 (对应 ioctl VIDIOC_STREAMON/OFF)
};
// v4l2 subdev pad格式与拓扑协商,处理媒体总线格式、分辨率、裁剪区域的协商与配置
static const struct v4l2_subdev_pad_ops vfe_pad_ops = {
.enum_mbus_code = vfe_enum_mbus_code, // 枚举支持的媒体总线代码(比如 MEDIA_BUS_FMT_SRGGB10_1X10)
.enum_frame_size = vfe_enum_frame_size, // 枚举支持的帧尺寸 (分辨率范围)
.get_fmt = vfe_get_format, // 获取当前配置的格式
.set_fmt = vfe_set_format, // 设置视频格式 (分辨率、像素格式等)
.get_selection = vfe_get_selection, // 获取裁剪/合成区域
.set_selection = vfe_set_selection, // 设置裁剪/合成区域
};
static const struct v4l2_subdev_ops vfe_v4l2_ops = {
.core = &vfe_core_ops, // 核心功能 (电源)
.video = &vfe_video_ops, // 视频流控制
.pad = &vfe_pad_ops, // 格式与拓扑协商
};
static const struct v4l2_subdev_internal_ops vfe_v4l2_internal_ops = {
.open = vfe_init_formats, // 当设备节点被打开时调用,用于初始化默认格式,属于内部操作集,仅供内核内部使用
};
static const struct media_entity_operations vfe_media_ops = {
.link_setup = vfe_link_setup, // 链路建立时的回调:检查并配置硬件链路
.link_validate = v4l2_subdev_link_validate, // 链路验证:使用 V4L2 标准验证链路格式是否匹配
};
// 注册 VFE 的 Subdev 和 Video 节点(vfe:设备私有结构体指针,v4l2_dev:全局 V4L2 设备指针)
int msm_vfe_register_entities(struct vfe_device *vfe,
struct v4l2_device *v4l2_dev)
{
struct device *dev = vfe->camss->dev; // 获取通用的 device 结构体,用于打印日志
struct v4l2_subdev *sd; // V4L2 子设备
struct media_pad *pads; // Media Pad
struct camss_video *video_out; // 视频输出节点
int ret;
int i;
// 遍历 VFE 硬件支持的所有处理线路 (Line)
// 高通 VFE 通常包含 1 条 Pix 线 (图像处理) 和多条 RDI 线 (Raw 数据直通)
for (i = 0; i < vfe->res->line_num; i++) {
char name[32];
// 获取当前线路 i 的结构体成员
sd = &vfe->line[i].subdev; // 子设备对象
pads = vfe->line[i].pads; // 端口数组 (Sink/Src)
video_out = &vfe->line[i].video_out; // 视频输出对象
//
// 1. 初始化 V4L2 Subdev (控制节点)
//
v4l2_subdev_init(sd, &vfe_v4l2_ops); // 绑定操作集,使内核知道如何调用此设备
sd->internal_ops = &vfe_v4l2_internal_ops; // 设置内部操作 (如 open 时的默认格式初始化)
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; // 标记该子设备需要创建对应的 /dev/v4l-subdevX 字符设备节点
// 如果是 Pix 线 (VFE_LINE_PIX): 命名为 "vfe{id}_pix" (例如 vfe0_pix)
if (i == VFE_LINE_PIX)
snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s",
MSM_VFE_NAME, vfe->id, "pix");
else
// 如果是 RDI 线: 命名为 "vfe{id}_rdi{i}" (例如 vfe0_rdi0, vfe0_rdi1)
snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s%d",
MSM_VFE_NAME, vfe->id, "rdi", i);
// 将当前线路的私有数据 (&vfe->line[i]) 绑定到 subdev 中
// 这样在回调函数中可以通过 v4l2_get_subdevdata() 取回
v4l2_set_subdevdata(sd, &vfe->line[i]);
// 初始化默认格式 (调用 internal_ops->open)
ret = vfe_init_formats(sd, NULL);
if (ret < 0) {
dev_err(dev, "Failed to init format: %d\n", ret);
goto error_init;
}
//
// 2. 初始化 Media Entity (拓扑节点)
//
// MSM_VFE_PAD_SINK: 输入端口,数据流入VFE实体(接收来自 CSID 的数据)
pads[MSM_VFE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
// MSM_VFE_PAD_SRC: 输出端口,数据流出VFE实体(发送数据给 Video Node 或 ISP)
pads[MSM_VFE_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
// 设置实体功能类型:视频像素格式化器
sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
// 绑定 Media Entity 的操作集 (用于链路建立和验证)
sd->entity.ops = &vfe_media_ops;
// 注册 Pads 到 Media Entity
// 这将使该设备出现在 media-ctl 的拓扑图中
ret = media_entity_pads_init(&sd->entity, MSM_VFE_PADS_NUM,
pads);
if (ret < 0) {
dev_err(dev, "Failed to init media entity: %d\n", ret);
goto error_init;
}
//
// 3. 注册 Subdev 到 V4L2 核心
//
// 此时 /dev/v4l-subdevX 节点正式可用
ret = v4l2_device_register_subdev(v4l2_dev, sd);
if (ret < 0) {
dev_err(dev, "Failed to register subdev: %d\n", ret);
goto error_reg_subdev;
}
//
// 4. 初始化 Video Output 节点 (数据捕获节点)
//
// 绑定视频节点的操作集 (buffer 队列管理等)
video_out->ops = &vfe->video_ops;
// 配置字节行对齐 (Bytes Per Line Alignment),确保 DMA 效率
video_out->bpl_alignment = vfe_bpl_align(vfe);
video_out->line_based = 0; // 默认非基于行处理
// Pix 线通常经过 ISP 处理,可能需要特定的对齐 (16 字节) 且支持基于行的处理
if (i == VFE_LINE_PIX) {
video_out->bpl_alignment = 16;
video_out->line_based = 1;
}
// 设置支持的格式列表
video_out->nformats = vfe->line[i].nformats;
video_out->formats = vfe->line[i].formats;
snprintf(name, ARRAY_SIZE(name), "%s%d_%s%d",
MSM_VFE_NAME, vfe->id, "video", i);
// 注册 Video 节点,生成 /dev/videoX
ret = msm_video_register(video_out, v4l2_dev, name);
if (ret < 0) {
dev_err(dev, "Failed to register video node: %d\n",
ret);
goto error_reg_video;
}
//
// 5. 建立内部硬连接 (Hard Link)
//
// 在 VFE Subdev 的源端口 (SRC) 和 Video 节点的输入端口之间建立连接,VFE Line 的输出物理上只能流向对应的 Video 节点
ret = media_create_pad_link(
&sd->entity, MSM_VFE_PAD_SRC,
&video_out->vdev.entity, 0,
MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); // MEDIA_LNK_FL_IMMUTABLE:链路不可变(用户空间不能断开或重连),MEDIA_LNK_FL_ENABLED:链路默认启用
if (ret < 0) {
dev_err(dev, "Failed to link %s->%s entities: %d\n",
sd->entity.name, video_out->vdev.entity.name,
ret);
goto error_link;
}
}
return 0;
//
// 错误处理路径 (反向清理已注册的资源)
//
error_link:
// 取消当前 video 节点的注册
msm_video_unregister(video_out);
error_reg_video:
// 注销当前 subdev
v4l2_device_unregister_subdev(sd);
error_reg_subdev:
// 清理当前 entity 的 pads
media_entity_cleanup(&sd->entity);
error_init:
// 如果第 i 条线失败,需要回滚之前所有成功注册的线 (0 到 i-1)
for (i--; i >= 0; i--) {
sd = &vfe->line[i].subdev;
video_out = &vfe->line[i].video_out;
msm_video_unregister(video_out);
v4l2_device_unregister_subdev(sd);
media_entity_cleanup(&sd->entity);
}
return ret;
}
v4l2_subdev和media_entity
v4l2_subdev负责"怎么控制硬件"(逻辑与控制),服务于v4l2子系统;media_entity负责"数据怎么流"(拓扑与连接),服务于Media Controller子系统
| 特性 | v4l2_subdev(V4L2 子系统) |
media_entity(Media Controller 子系统) |
|---|---|---|
| 核心职责 | 控制平面 (Control Plane) | 数据平面 (Data Plane) |
| 关注点 | 寄存器配置、格式协商、中断处理、用户空间 ioctl 调用。 | 硬件模块之间的物理连接关系、数据流向、拓扑图构建。 |
| 用户空间接口 | /dev/v4l-subdevX(如 /dev/v4l-subdev0) |
/dev/mediaX(如 /dev/media0) |
| 关键操作 | s_stream,s_fmt,g_fmt,subscribe_event |
MEDIA_IOC_SETUP_LINK,MEDIA_IOC_ENUM_ENTITIES |
| 形象比喻 | 工厂的操作员:负责按按钮启动机器、调节参数(分辨率、增益)。 | 工厂的管道工/建筑师:负责把管子(数据线)从 A 机器连到 B 机器,确保路是通的。 |
| 数据结构位置 | 嵌入在驱动私有结构体中 (如 csid->subdev) |
通常作为 v4l2_subdev的成员存在 (sd->entity) |
用户空间交互
内核注册阶段
驱动初始化阶段
内部包含
正式注册
调用 ioctl
连接 Links
vfe 驱动入口
v4l2_subdev_init
media_entity_pads_init
定义 ioctl 操作集
创建 /dev/v4l-subdevX
v4l2_subdev 对象
定义 Pads 端口
定义功能类型
media_entity 对象
v4l2_device_register_subdev
内核子系统
v4l2-ctl 工具
media-ctl 工具
2.VFE模块初始化
msm_vfe_subdev_init是VFE模块的核心的初始化函数,在probe阶段,根据设备树或平台数据的资源表,完成单个VFE硬件实例的底层资源映射和结构体初始化
c
int msm_vfe_subdev_init(struct camss *camss, struct vfe_device *vfe,
const struct camss_subdev_resources *res, u8 id)
{
struct device *dev = camss->dev;
struct platform_device *pdev = to_platform_device(dev);
int i, j;
int ret;
// 确保资源表中定义了有效的输出线,否则无法工作
if (!res->vfe.line_num)
return -EINVAL;
// 将资源表绑定到vfe结构体
vfe->res = &res->vfe;
// 调用特定硬件版本的初始化回调(VFE 4.x, 480, Titan)
vfe->res->hw_ops->subdev_init(dev, vfe);
/* 1.Power domain 初始化*/
// 如果设备树中指定了电源域名称,就通过名称绑定
if (res->vfe.pd_name) {
vfe->genpd = dev_pm_domain_attach_by_name(camss->dev,
res->vfe.pd_name);
if (IS_ERR(vfe->genpd)) {
ret = PTR_ERR(vfe->genpd);
return ret;
}
}
// 兼容模式,如果没有名称但还需要电源域,则使用传统的索引方式,这是为了兼容旧版设备树,通过
// ID索引来找对应的Power Domain
if (!vfe->genpd && res->vfe.has_pd) {
/*
* Legacy magic index.
* Requires
* power-domain = <VFE_X>,
* <VFE_Y>,
* <TITAN_TOP>
* id must correspondng to the index of the VFE which must
* come before the TOP GDSC. VFE Lite has no individually
* collapasible domain which is why id < vfe_num is a valid
* check.
*/
vfe->genpd = dev_pm_domain_attach_by_id(camss->dev, id);
if (IS_ERR(vfe->genpd))
return PTR_ERR(vfe->genpd);
}
/* 2.Memory 映射*/
// 获取平台资源中的寄存器内存区域,并映射到内核虚拟地址,
vfe->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]);
if (IS_ERR(vfe->base)) {
dev_err(dev, "could not map memory\n");
return PTR_ERR(vfe->base);
}
// 后续驱动就可以通过vfe->base + offset 来访问硬件寄存器
/* 3.Interrupt 注册*/
// 根据名称获取中断号,比如"vfe0"
ret = platform_get_irq_byname(pdev, res->interrupt[0]);
if (ret < 0)
return ret;
vfe->irq = ret;
// 构造中断名称,便于在 /proc/interrupts 中识别,格式如: "soc:vfe_0"
snprintf(vfe->irq_name, sizeof(vfe->irq_name), "%s_%s%d",
dev_name(dev), MSM_VFE_NAME, id);
// 注册中断处理函数,vfe->res->hw_ops->isr是中断入口函数,触发条件为上升沿IRQF_TRIGGER_RISING
// 私有数据传入vfe结构体
ret = devm_request_irq(dev, vfe->irq, vfe->res->hw_ops->isr,
IRQF_TRIGGER_RISING, vfe->irq_name, vfe);
if (ret < 0) {
dev_err(dev, "request_irq failed: %d\n", ret);
return ret;
}
/* 4.Clocks 管理 */
vfe->nclocks = 0;
// 统计资源表中定义的时钟数量 (以 NULL 结尾)
while (res->clock[vfe->nclocks])
vfe->nclocks++;
// 分配时钟数组内存
vfe->clock = devm_kcalloc(dev, vfe->nclocks, sizeof(*vfe->clock),
GFP_KERNEL);
if (!vfe->clock)
return -ENOMEM;
// 遍历每个时钟
for (i = 0; i < vfe->nclocks; i++) {
struct camss_clock *clock = &vfe->clock[i];
// 获取时钟句柄,clk_prepare_enable 的前置步骤
clock->clk = devm_clk_get(dev, res->clock[i]);
if (IS_ERR(clock->clk))
return PTR_ERR(clock->clk);
clock->name = res->clock[i];
// 统计该时钟支持的频率表大小
clock->nfreqs = 0;
while (res->clock_rate[i][clock->nfreqs])
clock->nfreqs++;
if (!clock->nfreqs) {
clock->freq = NULL;
continue;
}
// 分配频率表内存
clock->freq = devm_kcalloc(dev,
clock->nfreqs,
sizeof(*clock->freq),
GFP_KERNEL);
if (!clock->freq)
return -ENOMEM;
// 复制支持的频率列表到结构体中,供后续 vfe_set_clock_rates 使用
for (j = 0; j < clock->nfreqs; j++)
clock->freq[j] = res->clock_rate[i][j];
}
/* 5.锁与状态变量初始化 */
// 初始化电源锁,保护 power_count 引用计数
mutex_init(&vfe->power_lock);
vfe->power_count = 0;
// 初始化流控制锁,保护 stream_count 引用计数
mutex_init(&vfe->stream_lock);
vfe->stream_count = 0;
// 初始化输出队列锁 (在中断上下文中使用,故用 spin_lock)
spin_lock_init(&vfe->output_lock);
// 反向引用 CAMSS 主结构体,并记录 ID
vfe->camss = camss;
vfe->id = id;
vfe->reg_update = 0; // 寄存器更新标志位
/* 6.内部输出线(VFE Lines)初始化 */
// VFE 内部有多条处理管线,如 PIX (图像处理), RDI (原始数据接口)
for (i = VFE_LINE_RDI0; i < vfe->res->line_num; i++) {
struct vfe_line *l = &vfe->line[i];
// 设置视频捕获类型 (多平面)
l->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
l->video_out.camss = camss;
l->id = i;
// 初始化完成量,用于等待 SOF (Start of Frame) 或 寄存器更新完成
init_completion(&l->output.sof);
init_completion(&l->output.reg_update);
// 根据线的类型加载支持的格式表
if (i == VFE_LINE_PIX) {
l->nformats = res->vfe.formats_pix->nformats;
l->formats = res->vfe.formats_pix->formats;
} else {
l->nformats = res->vfe.formats_rdi->nformats;
l->formats = res->vfe.formats_rdi->formats;
}
}
// 初始化全局复位和停止操作的完成量
// 用于 vfe_reset 和 vfe_halt 函数中等待硬件操作完成
init_completion(&vfe->reset_complete);
init_completion(&vfe->halt_complete);
return 0;
}
3.电源管理
c
// 上电入口。增加电源引用计数。若首次上电,则开启电源域、启动时钟、计算并设置时钟频率、执行硬件复位
int vfe_get(struct vfe_device *vfe);
// 下电入口。减少电源引用计数。若计数归零,则停止流(若正在运行)、关闭时钟、关闭电源域
void vfe_put(struct vfe_device *vfe);
// 根据当前传感器像素时钟和总线需求,计算并设置 VFE 各时钟源的频率
static int vfe_set_clock_rates(struct vfe_device *vfe)
// 在已上电状态下,检查新请求的流是否需要更高的时钟频率,若需要则动态调整
static int vfe_check_clock_rates(struct vfe_device *vfe)
// 执行 VFE 全局硬件复位,等待复位完成信号量
int vfe_reset(struct vfe_device *vfe)
4.中断处理与缓冲区管理
处理硬件中断,管理 DMA 缓冲区的队列流转(Ping-Pong 机制),将数据从硬件搬运到用户空间
C
// 处理复合图像完成中断
void vfe_isr_comp_done(struct vfe_device *vfe, u8 comp)
// 将用户空间队列 (qbuf) 的缓冲区加入待处理链表 (pending_bufs)
void vfe_buf_add_pending(struct vfe_output *output,
struct camss_buffer *buffer)
// 从中断上下文中从待处理链表取出一个空闲缓冲区交给硬件 DMA
struct camss_buffer *vfe_buf_get_pending(struct vfe_output *output)
// 在停止流或发生错误时,清理所有 pending 和 active 的缓冲区,将其返回给用户空间(标记为 ERROR 或 DONE)
int vfe_flush_buffers(struct camss_video *vid,
enum vb2_buffer_state state)
5.功能流程
1.初始化阶段
驱动初始化时,调用msm_vfe_register_entities创建设备节点和Media拓扑
2.配置阶段
User Space通过vfe_set_format,vfe_set_selection配置图像参数
3.buffer准备
用户空间 QBUF -> 调用 vfe_buf_add_pending 排队缓冲区
4.硬件上电
调用 vfe_set_power (on) -> vfe_get -> 开时钟/复位
5.推流
调用 vfe_set_stream (on) -> vfe_enable -> 硬件开始工作
6.buffer数据循环
硬件产生中断 -> 调用 vfe_buf_get_pending 取缓冲 -> DMA 填充数据
帧完成中断 -> 调用 vfe_buf_done -> 用户空间 DQBUF 拿到数据
7.停流/下电
vfe_set_stream (off) -> vfe_flush_buffers -> vfe_set_power (off)
msm_vfe_register_entities
vfe_set_format/selection
QBUF / vfe_buf_add_pending
vfe_get
vfe_enable
IRQ: vfe_buf_get_pending
IRQ: vfe_buf_done
停止命令
vfe_flush_buffers
开始
1.初始化
2.配置参数
3.Buffer准备
4.硬件上电
5.开启推流
6.数据循环
DMA 填充
用户 DQBUF
7.停流/下电
结束