[QCOM] Linux下qcom camera驱动框架分析

框架总览

源码仓库:基于树莓派代码仓库,分析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.停流/下电
结束

相关推荐
浮若于心2 小时前
WSL2 Ubuntu 占用 C 盘空间清理指南
linux·c语言·ubuntu
请输入蚊子2 小时前
《操作系统真象还原》 第九章 线程
linux·操作系统·bochs·操作系统真像还原
bai_lan_ya2 小时前
Linux 输入系统应用编程完全指南
linux·运维·服务器
UP_Continue3 小时前
Linux--UDP/TCP客户端与服务端模拟实现计算器原理
linux·tcp/ip·udp
FightingHg3 小时前
和claude、openclaw交互的一些杂七杂八记录
linux·运维·服务器
深念Y3 小时前
魅蓝Note5 Root + 改内核激活命名空间:让Docker跑在安卓上
android·linux·服务器·docker·容器·root·服务
新兴AI民工3 小时前
【Linux内核二十五】进程管理模块:CFS调度器pick_next_task_fair(一):pick_next_task_fair方法
linux·linux内核
我是一个对称矩阵3 小时前
分区安装Ubuntu系统
linux·运维·ubuntu
小捏哩3 小时前
死锁检测组件的设计
linux·网络·数据结构·c++·后端