[ZYNQ Linux] V4L2视频驱动
文章目录
- [[ZYNQ Linux] V4L2视频驱动](#[ZYNQ Linux] V4L2视频驱动)
-
- 数据流搭建过程及问题
- [AXI VDMA图像接收思路](#AXI VDMA图像接收思路)
- V4L2驱动
-
- V4L2驱动------视频设备
- [AXI VDMA实现细节](#AXI VDMA实现细节)
- 用户空间使用
-
- 代码使用步骤
- [命令行工具 v4l2-ctl](#命令行工具 v4l2-ctl)
- TODO
- 其它问题
数据流搭建过程及问题
HDMI输入 → ADV7611解码 →AXIVDMA →PS
-
ADV7611复位接到了PL需要进行复位
使用一个axi gpio进行复位,axi gpio会注册一个gpiochip出来。
bashecho 507 > /sys/class/gpio/export echo out > /sys/class/gpio/gpio507/direction echo 0 > /sys/class/gpio/gpio507/value sleep 1 echo 1 > /sys/class/gpio/gpio507/value -
ADV7611初始化
配置一些寄存器
-
i2cdetect -r -y扫描不到0x4C等地址,不过可以读到数据
可能是设备没有对i2cdetect的扫描进行响应
不要过度依赖和相信工具bashi2cget -y 1 0x4c 0x42 -
EDID设置
问题:插电脑不能识别屏幕。
HDMI接口的I2C没连接到解码芯片,需要连接到一个EEPROM的IP核。
EDID IP核需要配置1080的文件,里面的名称不能空着
添加之后连接到电脑,电脑会识别到一个屏幕。
-
时钟和缓冲问题
问题:VDMA传输的一帧图像只有前几行有效,其余全0。
数据流程中有一个IP核把视频时序转为了AXI Stream格式,需要考虑写入速率(写入时钟)和读取速率(读出时钟)的问题。
位宽一样的话,读出时钟的频率要高于写入时钟。
输入是148.5M(1080p60帧),AXIVDMA的时钟也要高于或者等于该时钟。
还要考虑缓冲区设置多大比较合适。
AXI VDMA图像接收思路
- 通过DMA接口申请连续的内存块(DMA传输需要特殊的内存块,不能是malloc常规申请的)
- 调用DMA接口接收数据到内存空间
- 内核空间提供mmap接口供用户空间使用
- 用户空间调用mmap获取图像
常规的图像接收思路原理都是类似的,提供的API接口或者使用方式有所区别。
还会有缓冲池、缓冲区、队列等等的区别。
V4L2驱动
V4L2驱动------视频设备
| 接口 | 说明 |
|---|---|
| int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); | 注册 V4L2 设备实例,管理设备的数据结构,用户空间不可见 |
| int video_register_device(struct video_device *vdev,int type, int nr); | 注册视频设备,由V4L2设备进行管理即和上面注册v4l2设备关联,创建/dev/videoX的设备节点 |
| struct video_device | 视频设备结构体,设备的核心操作接口fops和ioctl_ops,图像队列queue |
| const struct v4l2_file_operations *fops; | open、release、mmap等,内核有通用的vb2_fop_mmap操作接口等等 |
| const struct v4l2_ioctl_ops *ioctl_ops; | ioctl操作,一大堆,需要实现一些关键的 |
| struct vb2_queue *queue; | 管理视频缓冲区的队列,一个缓冲区或者缓冲池 |
| 内核fops通用接口 | |
| v4l2_fh_open | 打开文件 |
| vb2_fop_release | 释放与视频缓冲区相关的资源 |
| vb2_fop_mmap | 内存映射 |
| vb2_fop_poll | poll机制 |
| 内核ioctl_ops通用接口 | |
| vb2_ioctl_create_bufs | 创建视频缓冲区 |
| vb2_ioctl_expbuf | 导出视频缓冲区,可以与其他设备或进程共享内存 |
| ioctl_ops用户实现 | |
| vidioc_querycap | 查询设备能力 |
| vidioc_g_fmt_vid_cap | 当前数据格式,包括分辨率、像素格式等参数 get format |
| vidioc_try_fmt_vid_cap | 尝试设置视频捕获格式,但不实际应用 |
| vidioc_s_fmt_vid_cap | 设置视频捕获设备的格式(如分辨率、像素格式)set format |
| vidioc_enum_fmt_vid_cap | 枚举视频捕获设备支持的像素格式 enum format |
| vidioc_reqbufs | 申请视频缓冲区内存 |
| vidioc_querybuf | 查询已申请缓冲区的详细信息,包括内存偏移、长度等 |
| vidioc_qbuf | 将缓冲区放入队列或队列 queue buffer |
| vidioc_dqbuf | 从队列中取出缓冲区 dequeue buffer |
| vidioc_streamon | 启动视频流传输 |
| vidioc_streamoff | 停止视频流传输 |
| struct vb2_queue | 缓冲区队列需要实现对队列的操作vb2_ops,内存操作mem_ops |
| queue_setup | 初始化视频缓冲区队列,分配缓冲区内存 |
| buf_prepare | 准备缓冲区数据 |
| buf_queue | 将缓冲区加入队列 |
| start_streaming | 启动流式传输 |
| stop_streaming | 停止流式传输,清空缓冲区队列 |
| wait_prepare | 等待缓冲区准备完成 |
| wait_finish | 等待缓冲区处理完 |
| vb2(video buffer)的通用接口 | vb2_fop层、vb2_ioctls层、vb2层、vb2_ops层 |
| vb2_ops_wait_finish | 等待所有缓冲区操作完成 |
| vb2_ops_wait_prepare | 等待所有缓冲区准备完成 |
| vb2_plane_vaddr | 获取缓冲区的虚拟地址 |
| vb2_plane_size | 获取缓冲区平面的大小 |
| vb2_set_plane_payload | 设置缓冲区平面的有效载荷大小 |
| vb2_is_busy | 检查缓冲区队列是否正忙 |
| vb2_reqbufs | 请求分配缓冲区 |
| vb2_querybuf | 查询缓冲区信息 |
| vb2_qbuf | 将缓冲区加入队列 |
| vb2_dqbuf | 从队列中取出缓冲区 |
| vb2_streamon | 启动流式传输 |
| vb2_streamoff | 停止流式传输 |
| vb2_buffer_done | 标记缓冲区处理完成 |
| vb2_dma_contig_plane_dma_addr | 获取DMA连续缓冲区的物理地址 |
| vb2_is_streaming | 检查是否正在流式传输 |
| vb2_queue_init | 初始化视频缓冲区队列 |
| vb2_dma_contig_memops | DMA内存操作集合,可传给mem_ops |
ioctl_ops、vb2层与vb2_ops的调用关系
方向:用户空间 → ioctl_ops → vb2层 → vb2_ops。
- 用户空间通过ioctl系统调用,传递到驱动中定义的ioctl_ops结构体
- ioctl_ops调用vb2层,如vidioc_streamon等会进一步调用vb2层的通用接口vb2_streamon
- vb2层回调驱动实现的vb2_ops,vb2_streamon回调start_streaming
分工:
- ioctl_ops处理用户请求的解析与转发。
- vb2层实现通用缓冲区管理逻辑。
- vb2_ops由驱动实现硬件细节。
AXI VDMA实现细节
将缓冲区加入图像队列------buf_queue
- 获取缓冲区的物理地址,用于DMA传输------vb2_dma_contig_plane_dma_addr
- 准备交错传输DMA描述符,需要设置DMA地址等参数------dmaengine_prep_interleaved_dma
- 设置描述符的回调函数,如果DMA传输完成就把缓冲区标记为有图像数据(VB2_BUF_STATE_DONE)
- 将描述符提交到 DMA 控制器的工作队列(提交未开始)------dmaengine_submit
- 把缓冲区放到活动缓冲区队列里面(后续获取图像就是从这个队列里面获取)
- 触发 DMA 控制器开始执行已提交的传输------dma_async_issue_pending
启动流式传输------start_streaming
- 让DMA开始工作就行------dma_async_issue_pending
停止流式传输,清空缓冲区队列------stop_streaming
- 结束DMA传输------dmaengine_terminate_all
- 将活动缓冲区队列里面的缓冲区设置为无效(VB2_BUF_STATE_ERROR)
设备树设计
c
vdma_capture: vdma-capture {
compatible = "xlnx,axi-vdma-capture";
dmas = <&axi_vdma_1 1>;
dma-names = "port0";
};
用户空间使用
代码使用步骤
- 打开设备:使用open()函数打开V4L2设备文件,通常是/dev/video0。根据需要,可以选择阻塞或非阻塞模式。
- 查询设备能力:通过VIDIOC_QUERYCAP命令获取设备的能力,例如是否支持视频采集(V4L2_CAP_VIDEO_CAPTURE)和流式I/O(V4L2_CAP_STREAMING)。
- 设置图像格式:使用VIDIOC_S_FMT命令配置采集的图像格式,包括宽度、高度和像素格式(如YUYV、MJPEG等)。这是决定图像数据如何组织的关键一步。
- 申请与映射缓冲区:
- 申请缓冲区:通过VIDIOC_REQBUFS向驱动程序申请若干个视频缓冲区。
- 映射缓冲区:对于内存映射(mmap)方式,使用VIDIOC_QUERYBUF查询每个缓冲区信息,然后通过mmap()系统调用将内核空间的缓冲区映射到用户空间,以便应用程序可以直接访问。这是最常用且高效的方式。
- 缓冲区入队:在开始采集前,需要通过VIDIOC_QBUF命令将所有空闲的缓冲区放入驱动层的输入队列中。
- 开始/停止视频流:
- 通过VIDIOC_STREAMON命令启动视频采集,驱动程序开始向队列中的缓冲区填充数据。
- 在循环中,使用VIDIOC_DQBUF从驱动层的输出队列中取出已填充好数据的缓冲区。应用程序处理完该帧数据后,必须再次调用VIDIOC_QBUF将其放回输入队列,以便驱动程序循环使用。
- 采集完成后,通过VIDIOC_STREAMOFF命令停止视频流。
- 关闭设备:使用close()函数关闭设备文件,并释放所有资源。
命令行工具 v4l2-ctl
TODO
- 解码芯片在用户空间单独进行初始化和配置,未加入到驱动框架里边。可以使能内核相关驱动,作为v4l2子设备添加。
- 联动Framebuffer、DRM进行图像输出。
其它问题
内核有个VVIP的驱动,不是给AXI-VDMA用的,是给另外的IP核用的,不能直接使用。
- no VDMA channel found(VVIP驱动)
没找到DMA通道,设备树通道有问题,1是写,从DMA向外写(S2MM)。
bash
[ 2.246473] xilinx-video axi-video-cap: no VDMA channel found
[ 2.251733] xilinx-video axi-video-cap: /axi-video-cap/ports/port@0 initialization failed
[ 2.259907] xilinx-video axi-video-cap: DMA initialization failed
\linux-xlnx\Documentation\devicetree\bindings\dma\xilinx\xilinx_dma.txt
bash
* DMA client
Required properties:
- dmas: a list of <[Video DMA device phandle] [Channel ID]> pairs,
where Channel ID is '0' for write/tx and '1' for read/rx
channel. For MCMDA, MM2S channel(write/tx) ID start from
'0' and is in [0-15] range. S2MM channel(read/rx) ID start
from '16' and is in [16-31] range. These channels ID are
fixed irrespective of IP configuration.
- dma-names: a list of DMA channel names, one per "dmas" entry
- no subdev found in graph(VVIP驱动)
没有子设备,v4l2驱动需要子设备构成一个完整的数据流
bash
[ 2.253037] xilinx-video axi-video-cap: /axi-video-cap/ports/port@0 initialization failed
[ 2.260727] xilinx-video axi-video-cap: DMA initialization failed
[ 4.528033] xilinx-video axi-video-cap: no subdev found in graph
[ 4.539722] xilinx-video: probe of axi-video-cap failed with error -2
- 创建一个虚拟的子设备可以创建成功,但还是不能用(VVIP驱动)
驱动里面还是用不了,没能成功获取到图像格式,需要另一个IP核的支持。
bash
[ 2.373491] xilinx-video axi-video-cap: /axi-video-cap/ports/port@0 initialization failed
[ 2.381152] xilinx-video axi-video-cap: DMA initialization failed
[ 4.646649] xilinx-video axi-video-cap: device registered