跳到主要内容

DVP开发指南

模块介绍

1.1. 术语定义

术语定义注释说明
CMAContiguous Memory Allocator连续内存分配器
DVPDigital Video Port用于接收视频数据数据,转换格式后存放到内存中
V4L2Video For Linux TwoLinux中的视频框架第二版
VBIVertical Blanking Interval垂直消隐期
ISPImage Signal Processing图像信号处理,一般指对前端图像传感器输出信号的处理
MBUSMedia BusV4L2框架中用到的一种媒体类型,用于两个V4L2设备之间的协商
Sensor即Camera本文中指摄像头

1.2. 模块简介

DVP模块负责从Sensor中获取到数据,然后经过格式转换、或者缩放,输出到DRAM。支持特性:

  • 最大支持 1080P@30帧 录像
  • 支持 5M 拍照
  • 支持 YUV422 和 BT.656 两种方式,BT.656支持隔行模式,最大支持8位输入
  • 支持针对图像帧中的行和列分别做裁剪

DVP的硬件框图:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/hw_structure5.png

图 6.23 DVP硬件架构示意图

从整个系统看,有两种应用场景:

  1. 从Sensor采集数据到内存中,然后让DE将其显示到屏幕上;
  2. 从Sensor采集数据到内存中,使用CPU或者VE进行编码,最后再将编码后的数据保存到内存中。

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/hw_data_flow1.jpg

图 6.24 DVP应用的数据流示意图

DVP驱动设计需要基于Linux中的成熟框架V4L2,详细介绍见 设计说明

2. 参数配置

2.1. 内核配置

DVP驱动依赖dma-buf、CMA、V4L2模块,所以需要提前打开。

2.1.1. 打开 CMA

在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,按如下选择:

Linux
Memory Management options
[*] Contiguous Memory Allocator

还需要设置CMA区域的大小,在内核配置的另外一个地方,如下配置为 16MB:

Linux
Library routines
[*] DMA Contiguous Memory Allocator
(16) Size in Mega Bytes

2.1.2. 打开 dma-buf

在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,按如下选择:

Linux
Device Drivers
DMABUF options
[*] Explicit Synchronization Framework
[*] Sync File Validation Framework
[*] userspace dmabuf misc driver
[*] DMA-BUF Userland Memory Heaps
[*] DMA-BUF CMA Heap

2.1.3. 打开 V4L2

在 kernel 的 menuconfig 中,按如下选择:

Linux
Device Drivers
Multimedia support
Media core support
<*> Video4Linux core

打开 V4L2 的相关选项:

Linux
Device Drivers
Multimedia support
Video4Linux options
[*] V4L2 sub-device userspace API
[*] Enable advanced debug functionality on V4L2 drivers

2.1.4. 打开 DVP

在 kernel 的 menuconfig 中,按如下选择:

Linux
Device Drivers
Multimedia support
Media drivers
[*] V4L platform devices
<*> Artinchip Digital Video Port Support

2.2. DTS 参数配置

2.2.1. D211 配置

DTS分别存放在两个目录:common/和board/,board中存放sensor配置、以及和DVP关联的配置。

common/d211.dtsi中的参数配置:

dvp0: dvp@18830000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "artinchip,aic-dvp-v1.0";
reg = <0x0 0x18830000 0x0 0x1000>;
interrupts-extended = <&plic0 57 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_DVP>;
clock-rate = <200000000>;
resets = <&rst RESET_DVP>;
};

xxx/board.dts中的配置参数:

&dvp0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&dvp_pins>; /* dvp 接口引脚 */

port@0 {
reg = <0>;
dvp0_in: endpoint {
remote-endpoint = <&ov5640_out>;
/* bus-type = <6>; /* V4L2_FWNODE_BUS_TYPE_BT656 */
bus-width = <8>;
hsync-active = <1>;
vsync-active = <1>;
field-even-active = <1>;
pclk-sample = <1>;
};
};
};

小技巧

DVP驱动支持标准的 V4L2参数,如上面的 bus-type、bus-type、hsync-active 等,更完整的参数定义可以见Linux中的代码 drivers/media/v4l2-core/v4l2-fwnode.c,v4l2_fwnode_endpoint_parse_parallel_bus()。

3. 调试指南

3.1. 调试开关

在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,可以打开DVP模块的DEBUG选项:

Linux
Kernel hacking
Artinchip Debug
[*] DVP driver debug

此DEBUG选项打开的影响:

  1. DVP驱动以-O0编译
  2. DVP的pr_dbg()和dev_dbg()调试信息会被编译

在系统运行时,如果要打印pr_dbg()和dev_dbg()信息,还需要调整loglevel为8,两个方法:

  1. 在board.dts中修改bootargs,增加“loglevel=8”
  2. 在板子启动到Linux shell后,执行命令:
echo 8 > /proc/sys/kernel/printk

3.2. Sysfs 节点

3.2.1. 查看V4L2设备

在 Sysfs 中查看video设备的信息:

# ls /dev/video0 -l
crw------- 1 root root 81, 0 Jan 1 1970 /dev/video0
# ls /sys/class/video4linux/v4l-subdev0/
dev dev_debug device index name subsystem uevent
#
# cat /sys/class/video4linux/v4l-subdev0/name
ov5640 3-003c
#
# cat /sys/class/video4linux/v4l-subdev1/name
aic-dvp-sd
# ls -l /sys/class/video4linux/
lrwxrwxrwx 1 root root 0 Jan 1 00:12 v4l-subdev0 -> ../../devices/platform/soc/99223000.i2c/i2c-3/3-003c/video4linux/v4l-subdev0
lrwxrwxrwx 1 root root 0 Jan 1 00:12 v4l-subdev1 -> ../../devices/platform/soc/98830000.dvp/video4linux/v4l-subdev1
lrwxrwxrwx 1 root root 0 Jan 1 00:12 video0 -> ../../devices/platform/soc/98830000.dvp/video4linux/video0

3.2.2. 打开V4L2的debug开关

在V4L2子系统中,用dprintk(level)接口来控制调试信息,大于代码中的dprintk(level)调用时的level就可以打印出信息。dprintk()一般用到两个debug level:

1显示ioctl名称
2显示API的传入参数

向对应的Sysfs节点写入一个整数值,可以修改该debug level:

echo 0x3 > /sys/module/videobuf2_v4l2/parameters/debug
echo 0x3 > /sys/module/videobuf2_common/parameters/debug
echo 0x3 > /sys/devices/platform/soc/18830000.dvp/video4linux/v4l-subdev1/dev_debug
echo 0x3 > /sys/devices/platform/soc/18830000.dvp/video4linux/video0/dev_debug

3.2.3. 查看 DVP 的 Buf 队列情况

DVP驱动中实现了一个sysfs节点buflist,查看当前三个Qbuf、DQbuf、DVP驱动中的buf list状态:

# cat /sys/devices/platform/soc/18830000.dvp/buflist
In dvp->buf_list, the current buf in list:
[0]: empty
[1]: empty
[2]: empty

In V4L2 Q-buf list:
[0]: empty
[1]: empty
[2]: empty

In V4L2 DQ-buf list:
[0], state: Done
[1], state: Done
[2], state: Done

3.3. V4L2 相关的其他工具

  • V4l2-ctl,v4l2的瑞士军刀
  • V4l2兼容性测试
  • V4l2-dbg,
  • qv4l2,QT测试程序

小技巧

TODO:以上工具的使用方法待验证和整理

4. 测试指南

4.1. 测试环境

4.1.1. 硬件

  • 开发板,或者D211的FPGA板
  • 可转接摄像头的子板
  • 摄像头,如OV5640

4.1.2. 软件

  • PC端的串口终端软件,用于PC和开发板进行串口通信
  • DVP模块的测试demo:test_dvp

4.1.3. 软件配置

4.1.3.1. 配置 OV5640 摄像头

测试中需要用到摄像头,以OV5640为例,在luban的根目录下通过make kernel-menuconfig,按如下选择:

Linux
Device Drivers
Multimedia support
Media ancillary drivers
Camera sensor devices
<*> OmniVision OV5640 sensor support

在board.dts中,也需要增加OV5640的配置,假设OV5640是接在I2C3通道:

&i2c3 {
pinctrl-names = "default";
pinctrl-0 = <&i2c3_pins_a>, <&clk_out1_pins>; /* i2c3 引脚及 clock 引脚 */
status = "okay";

ov5640: camera@3C {
compatible = "ovti,ov5640";
reg = <0x3C>;
clocks = <&cmu CLK_APB1>;
clock-names = "xclk";
reset-gpios = <&gpio_p 5 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio_p 6 GPIO_ACTIVE_HIGH>;

port {
ov5640_out: endpoint {
remote-endpoint = <&dvp0_in>;
/* V4L2_FWNODE_BUS_TYPE_BT656
bus-type = <6>; */
bus-width = <8>;
data-shift = <2>;
hsync-active = <0>;
vsync-active = <1>;
pclk-sample = <1>;
};
};
};
};

注解

  1. 如果要使用BT656模式,请打开bus-type参数;
  2. 如果是YUV422,不需要配置bus-type,代码中默认采用YUV422(即V4L2_FWNODE_BUS_TYPE_PARALLEL,定义在drivers/media/v4l2-core/v4l2-fwnode.c)。
4.1.3.2. test_dvp 配置

在luban根目录,运行make menuconfig,按如下选择:

Artinchip packages
Sample code
[*] test-dvp

4.2. test_dvp 测试

test_dvp的主要功能是将摄像头的数据采集后,传给DE模块的Video图层去显示。其设计详见 APP Demo

在打开test_dvp的编译后,板子上的test_dvp位于 /usr/local/bin/,无需进入该目录,直接运行test_dvp即可:

[aic@] # test_dvp -u
Usage: test_dvp [options]:
-f, --format format of input video, NV12/NV16 etc
-c, --count the number of capture frame
-w, --width the width of sensor
-h, --height the height of sensor
-r, --framerate the framerate of sensor
-u, --usage
-v, --verbose

Example: test_dvp -f nv16 -c 1

# 使用示例:
# 设置摄像头的分辨率为 720*576,帧率为15帧/s,
# 使用 NV12 格式(默认)输出到 Video Layer 显示,共采集并显示100帧数据
[aic@] # test_dvp -w 720 -h 576 -r 15 -c 100

5. 设计说明

5.1. 源码说明

本模块源代码在内核目录linux-5.4/drivers/media/platform/artinchip下,目录结构如下:

drivers/media/platform/artinchip/
├── aic_buf.c // 和buf管理相关的处理代码
├── aic_dvp.c // DVP驱动的初始化入口,主要实现了probe、Notifier接口
├── aic_dvp.h // DVP驱动共用的头文件,其中定义了寄存器、共用数据结构、全局函数等
├── aic_dvp_hw.c // 对寄存器的访问封装
├── aic_video.c // 和V4L2框架强相关的一些接口定义,如file_ops、ioctl_ops的接口实现等
├── Kconfig
└── Makefile

5.2. 模块架构

5.2.1. V4L2 软件框架

Linux中的V4L2框架是一个专门为视频输入输出设备而设计的成熟方案,DVP驱动需要基于V4L2。

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_system.jpg

图 6.25 Linux V4L2子系统架构图

  1. V4L2,Video For Linux 第2版,最早出现在1998年,一个针对无线广播(收音机)、视频捕获、视频输出设备的通用框架,源码目录drivers/media/v4l2-core。V4L2中支持的5大类接口设备:
  • Video capture interface:影像捕获接口;
  • Video output interface:视频输出接口,主要用于电视信号类;
  • Video overlay interface:视频覆盖接口,方便视频显示设备直接从捕获设备上获取数据;
  • VBI interface:垂直消隐接口,可提供垂直消隐区的数据接入,包括raw和sliced两种;
  • Radio interface:广播接口,主要是从AM或FM调谐器中获取音频数据。
  1. V4L2为用户空间提供了字符设备的通用接口,设备节点/dev/videoX,主设备号81,次设备号的分配跟设备类型有关,规则定义如下:

    设备类型次设备号
    视频设备0~63
    Radio设备64~127
    Teletext设备192~233
    VBI设备224~255

用户态APP通过ioctl控制video设备,通过mmap进行内存映射。在/dev目录中会产生videoX、radioX和vbiX设备节点。

  1. 在V4L2框架中,将每一个Sensor、DVP硬件设备都看作一个subdev,相应的有一个字符设备节点/dev/v4l-subdevX,用户态通过这些节点的ioctl接口可以完成subdev的格式协商、时序配置等功能。
  2. Notifier子模块是为了解决多个设备之间的初始化顺序、以及媒体流对接的匹配检查,如DVP需要等Sensor初始化完成后,才能去真正完成video device的注册。可见,DVP和Sensor要有一个绑定的关系,这个关系是由DTS中的remote-endpoint来指定的。Notifier会调用Fwnode系列接口来解析和获取remote-endpoint的属性字段。
  3. 为了进行数据流的管理,V4l2维护了一个Media device链表,每一个video device、v4l2 device、v4l2-subdev都是一个Media device实例,这些Media device在数据结构的设计上第一个成员变量都是一个struct media_entity,其中有list_head成员,借此互相链接起来。见下一节详述。

5.2.2. V4L2 的实例管理

一个完整的V4L2驱动涉及4种设备实例:V4l2 device、Video device、V4l2 subdev、Media device,这4个实例都需要在DVP驱动中去定义。它们的引用关系如下图:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_instance.jpg

图 6.26 Linux V4L2的实例关系示意图

    • V4l2 device

      可以理解为最高统帅,它纵览全局,用成员指针指向以下其他实例,只服务于内核态,不包含面向用户态的接口。 整个内核中只有一个v4l2 device实例。

    • V4l2 subdev

      V4L2将每一个硬件模块都看作一个subdev,如DVP、Sensor都各自注册一个subdev,并且每个subdev会向用户态透出一个/dev/v4l-subdevX设备节点(该设备节点的编号X取决于注册顺序)。这些subdev会形成一个链表,都挂在v4l2 device下面的subdevs链表中。在subdev的ops数据接口v4l2_subdev_ops将回调按设备类型进行划分(这里区分设备类型)

struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core;
const struct v4l2_subdev_tuner_ops *tuner;
const struct v4l2_subdev_audio_ops *audio;
const struct v4l2_subdev_video_ops *video;
const struct v4l2_subdev_vbi_ops *vbi;
const struct v4l2_subdev_ir_ops *ir;
const struct v4l2_subdev_sensor_ops *sensor;
const struct v4l2_subdev_pad_ops *pad;
};

对于DVP控制器来说,它对应的ops只提供pad_ops即可;对于Sensor设备来说,要提供pad_ops和video_ops。

    • Video device

      是给用户态提供/dev/videoX接口的设备实例,这里不区分设备类型,统一使用V4L2标准的80+个ioctl命令接口。video device更像是逻辑功能,如果未来我们的DVP增加了ISP功能,就需要注册两个video device,但是DVP对应的subdev只需要一个。

    • Media device

      主要作用是为了将有上下游关系的entity串联起来(通过连接各entity的pad或interface属性),形成一个媒体流(V4L2启动/停止命令称作start/stop stream)。并且会透出一个/dev/mediaX设备节点,目前用户态还没有用到,所以在上图中未体现。

    • Media entity

      以上实例除了最高统帅V4l2 device其他都可以看作一个media entity,都挂在Media device维护的一个media entity链表。

实际上,在上图中的每个subdev,注册的时候也是生成一个Video device,由Video device向用户态透一个/dev/v4l-subdevX设备过去,这样做的好处是由Video device统一处理对接用户态的接口。所以完整的实例关系图应该再加一层Video device,如下图:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_instance_full.jpg

图 6.27 Linux V4L2的完整实例关系示意图

5.2.3. V4L2 的 Media 管理

V4L2提供了一个Media Framework来管理Media数据组成pipeline,在运行过程中可以调整pipeline中各个节点的配置,达到“运行时设备控制”的效果。

需要用到5个关键数据结构:media_device、media_entity、media_link、media_pad、media interface。

  • Media device是框架管理者,下面维护4个链表:entity、pad、link、interface。
  • 将每个硬件设备、或者一个软件模块抽象成一个entity,如DVP控制器、Sensor、DMA通道、连接器
  • Entity之间通过link来连接,link的两端是pad。数据流是从一个source pad(源)到一个sink pad(目的/接收端)。Pad:硬件设备上的端口抽象,类似于芯片上面的管脚pad概念。

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_pad.png

图 6.28 Linux V4L2的pad和link关系示意图

  • 另外还有一个media_interface结构,表示提供给用户态什么接口,目前只有一种类型:device node
  • link有两种:pad to pad、Interface to entity(暂未用到)。从media_link的定义看它们的连接关系:
struct media_link {
struct media_gobj graph_obj; // 是对entity、pad、link、Interface的抽象,它们的公共头数据
struct list_head list;
union {
struct media_gobj *gobj0; // 是pad和Interface头部的公共数据
struct media_pad *source; // 源pad
struct media_interface *intf; // 需要连接到entity的interface
};
union {
struct media_gobj *gobj1; // 同上,是pad和entity头部的公共数据
struct media_pad *sink; // 目的pad
struct media_entity *entity; // 需要连接到Interface的entity
};
struct media_link *reverse; // 指向对称(两端pad相同方向相反)的那个media_link
unsigned long flags;
bool is_backlink; // 是否逆方向(相对于数据流方向)
};
  • 从数据结构定义来看一个entity,可以有多个pad(注意其中的pads成员是个指针,指向的实例需要DVP驱动先定义好),当然随之而来会有多个link,定义如下:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_media_entity.jpg

图 6.29 Linux V4L2的Media entity和pad的引用关系

结合我们的DVP硬件结构,media entity、pad和link的关系如下:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_media_entity.png

图 6.30 DVP驱动中的Media entity设计

从上图中可以看到,如果要设置DVP的 输入 格式,就通过DVP subdev;如果要设置DVP的 输出 格式,则通过Video device。

5.2.4. V4L2 的 ioctl 调用关系

在“实例管理”中已经知道,用户态看到的/dev/videoX和 /dev/v4l-subdevX两个设备节点,进入内核态后都是直接先跟Video device设备实例对接,那么当用户调用ioctl命令时,是如何传给下面V4L2 subdev呢?依靠注册时传入的参数struct v4l2_file_operations *fops。

  • V4L2 subdev在为自己注册Video device时(见v4l2-device.c中的v4l2_device_register_subdev_nodes())传入的fops是预定义好的v4l2_subdev_fops(定义见v4l2-subdev.c),其中的ioctl接口指向框架中提供的接口subdev_ioctl()。而subdev_ioctl会逐层调用到“实例管理”中提到的 v4l2_subdev_ops。
  • 而DVP驱动在为自己注册Video device时,传入的fops是自己定义的aic_dvp_fops,其中ioctl接口指向框架中的公共接口video_ioctl2()。
  • 为解决不同硬件设备有不同的ioctl处理需求,DVP驱动还需要另外提供一个专门为ioctl定义的扩展ops:aic_dvp_ioctl_ops(数据结构定义见struct v4l2_ioctl_ops)。

针对一个从用户态传来的ioctl()命令,其内部调用关系如下图:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_ioctl.jpg

图 6.31 Linux V4L2的ioctl处理关系图

5.2.5. V4L2 的 Buf 队列管理

V4L2的Buffer管理由videobuf子模块实现,从源头看分为两种方式的Buffer:

    • 驱动申请Buffer

      用户态通过VIDIOC_REQBUFS ioctl命令触发Buffer申请,然后使用mmap接口来获取用户态的Buffer地址。这种方式,Buffer个数一般有个最大值32 VIDEO_MAX_FRAME。

    • 用户态申请Buffer

      用户态根据实际需要知道要申请多少Buffer,然后借助其他机制(可以是dma-buf)在用户态完成Buffer(当然必须是物理连续的)申请,并将物理地址告诉Video驱动。

按照Buffer的物理连续,又可以分为三种情况:(详见Documentationmediakapiv4l2-videobuf.rst)

    • 物理连续、不连续的Buffer混用

      几乎所有用户空间的Buffer都属于这种情况,这样最大可能的发挥虚拟内存管理的灵活性。缺点也很明显,这些Buffer给硬件的话需要有支持scatter的DMA。

    • 物理不连续、虚拟地址连续的Buffer

      它们由vmalloc()分配,也用于支持scatter接口的DMA硬件。

    • 物理连续的Buffer

      非常适合DMA类硬件的访问。

驱动开发者必须三选一,对于我们的DVP模块来说,要选择3。并且底层用到dma-buf。

注解

V4L第二版不再支持Overlay类型,而V4L第一版不支持DMABUF类型。

V4L2在数据流传输的时候需要多个Buf切换,通过struct vb2_queue结构中的Qbuf两个Buf队列来管理,DVP驱动中还需要维护一个buf_list来配合DVP控制器的地址更新。整个Buf流转的过程如下图:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_buf_list.jpg

图 6.32 DVP 驱动中的 Buf 队列管理

  • Qbuf队列(在代码中见struct vb2_queue->queued_list):是一些空闲Buf,等待Sensor的数据到来后,DVP驱动会从这个队列中找可用Buf来保存下一帧数据。
  • DQbuf队列(在代码中见struct vb2_queue->done_list):是一些填了视频数据的Buf,等待用户来处理这些数据,一般用户处理完后需要将Buf还给驱动,也就是还给Qbuf。
  • 从Qbuf和DQbuf来的buf格式是struct vb2_buffer,其中没有字段可以保存物理地址(DVP控制器需要),同时还需要为每个buf记录一个是否正在被DVP使用的状态,所以DVP驱动中定义了基于vb2_buffer结构的封装vb2_v4l2_buffer,并且维护一个和Qbuf几乎同步的队列。
  • 从图中的流转过程看,运行期间,在某一时刻,DVP需要使用一个Buf,APP需要使用一个Buf,QBuf需要有一个Buf在等待(否则DVP的done中断来了后发现没有等待的Qbuf会发生丢帧),一共至少要有3个Buf。
  • 以YUV422格式计算,有两个plane,在V4L2框架中这一组plane算一个Buf,3个Buf就需要申请6个plane,总大小 = 长 * 宽 * 2 * 3
  • DVP驱动中需要定义一个struct vb2_queue实例,Video device中会有一个指针*queue指向该实例。

5.2.6. DVP 驱动的子模块结构

基于以上对V4L2框架的分析,DVP驱动内部可以分为以下5个子模块:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_drv_structure.jpg

图 6.33 DVP 驱动的子模块结构

  • Video Dvice管理,主要实现和Video device相关的注册、ioctl处理;
  • Notifier管理,主要处理和Sensor设备的初始化次序的依赖关系;
  • Buf管理,主要实现Buf的入队、出队,以及在中断响应时切换DVP控制器的输出地址等;
  • Subdev管理,主要实现输入格式相关的接口;
  • 寄存器访问,封装了对DVP控制器寄存器的读写访问。

5.3. 关键流程设计

5.3.1. 初始化流程

总体上看,DVP驱动的初始化过程分为两大段:

  1. 阶段一:由probe()接口完成,完成资源申请、注册subdev、注册buf、注册notifier等;
  2. 阶段二:由notifier的complete()接口完成,是需要等Sensor执行完初始化(其probe()接口)后才能执行,完成的操作有:注册video device、注册media device、配置link等。
5.3.1.1. probe 过程

注册DVP控制器的注册过程:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_probe_flow.png

图 6.34 DVP 驱动的注册过程

  • 初始化media device,
  • 注册subdev,提供subdev_ops(其中定义了pad_ops);
  • 注册pad,包括为subdev注册两个pad:source + sink;为video device注册一个sink pad。
  • 注册buf,初始化vb2_queue,需要提供vb2_ops(驱动相关)和vb2_mem_ops(内存分配的回调)
  • 注册v4l2 device,主要是将DVP的dev关联到v4l2_device->dev
  • 注册notifier,为了解决sensor和DVP控制器之间的初始化顺序依赖问题,需要dts中定义好endpoint,并提供notifier_ops。

初始化notifier的时候,会去调用v4l2_fwnode_endpoint_parse ()解析DTS中关于endpoint中的配置,包括bus-type(BT656等)、极性等,将这些信息保存在vep->bus中(在aic_dvp->bus需要有备份)。

5.3.1.2. notifier 初始化过程

在Sensor的probe()过程中也会调用Notifier注册,因为DTS中两个设备用remote-endpoint已经有关联,DVP驱动注册过的notifier_ops->bound()接口首先会被触发,对方(Sensor)会传过来一个pad编号,DVP将其记录下来方便后续使用(调用Sensor的subdev接口完成stream启动、停止操作)。

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_notify_call.png

图 6.35 V4L2 中notify的调用过程

随后,DVP的notifier_ops->complete()接口也会被触发调用,DVP驱动中完成后续的初始化,包括:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/v4l2_notify_complete.png

图 6.36 V4L2 中notify的complete处理流程

  • 关联v4l2_device 和subdev
  • 注册video device,
  • 注册media device
  • 创建pad之间的link,会用到media_link结构

注解

media device出现了两次,是为了在所有media graph完全初始化之前就可以提供media device给用户态空间。所以一开始先用一部分entity初始化media device。

其中:

  • Master设备执行probe函数的时候,先使用component_match_add()接口声明一个match队列。
  • 然后,使用component_master_add_with_match函数将自己作为master注册到component框架。
  • 各component slave设备执行probe函数的时候,仅使用component_add()完成slave注册。
  • 以上各模块的probe()函数调用先后顺序并不影响。
  • 各个component都要实现自己的bind()和unbind()接口(struct component_ops),component框架在判断所有match队列中的模块都完成了probe,就会按 先slave、后master 的去调用他们的bind()接口。而各模块真正的初始化动作都是在各自的bind()中去实现。
  • 在执行各bind()接口时,各slave间的先后顺序和match队列一致。Component保证master最后执行。
  • aicfb->bind()中,主要完成framebuffer申请、fb设备注册、使能UI图层、使能panel等动作。

5.3.2. Buf 管理

DVP的Buf管理需要用到V4L2框架提供的Video queue机制外,还需要用到dma-buf和CMA(详见DE设计文档中的描述)。

对于每一帧图像数据来说,DVP的输出有两个plane:Y和UV。针对DVP的两种输出格式:YUV422_COMBINED_NV16和YUV420_COMBINED_NV12,两个plane的空间大小如下表:

YUV422_COMBINED_NV16YUV420_COMBINED_NV12
Plane YWidth * heightWidth * height
Plane UVWidth * heightWidth * height / 2

根据前面对“Buf队列管理”的分析可知:我们要分配的内存空间 至少要有3个Buf,每个Buf有两个Plane

对应到Buf的ioctl接口,我们要用到*_MPLANE结尾的接口。

注册video queue时提需要提供vb2_ops,其中需要DVP驱动实现的有五个接口:

    • queue_setup

      在APP发起申请buf时调用,这里面主要设置plane个数、各plane的大小;

    • buf_prepare和buf_queue

      在APP每次调用QBuf时会调用,分别完成获取Buf物理地址、同步Qbuf list的处理;

    • Stream start和stream stop

      启动和停止媒体数据(处理流程详见下节描述)。

5.3.3. Stream 启动流程

Stream的启动是由APP发起的,APP通过ioctl接口传入命令VIDIOC_STREAMON(相应的,停止的命令是VIDIOC_STREAMOFF)。

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_stream_on.png

图 6.37 DVP 驱动中Stream启动过程

Stream的停止流程相对简单很多,会调用到Sensor的停止传输接口:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_stream_off.png

图 6.38 DVP 驱动中Stream停止过程

5.3.4. 中断处理流程

DVP的中断处理函数中主要处理Buf的队列切换操作。 DVP硬件提供的中断可以反映出多个状态(包括出错状态),其中有两个比较重要:

    • Update done

      表示硬件已经读走了当前的Register值(影子寄存器),软件可以为下一帧去修改了;

    • Frame done

      表示当前帧的数据传输完成。

可见,Update done会先于Frame done发生,驱动中用Update done判断当前Register是否可以修改,用Frame done判断当前buf是否完成(done状态),该buf就可以从QBuf list切换到DQbuf list了。

按照DVP硬件设计的逻辑,Update done和Frame done会间隔着产生(不会连续两个Update done): Update done -> Frame done -> Update done -> Frame done -> Update done -> Frame done ...

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_irq_flow1.png

图 6.39 DVP 驱动中IRQ处理流程

“处理Frame done事件”的子流程如下:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_frame_done_flow1.png

图 6.40 DVP 驱动中Frame done处理流程

“处理Update done事件”的子流程如下:

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/dvp_update_done_flow1.png

图 6.41 DVP 驱动中Update done处理流程

    • “异常!DVP同时使用了两个Buf”

      理论上不应该发生,可认为是DVP硬件异常,但因为DVP还在向Buf写数据,所以先不执行stop,软件上报错。

    • “DVP在使用”

      表示“DVP控制器硬件正在使用”。

5.4. 数据结构设计

DVP自定义的数据结构都在aic_dvp.h中。

5.4.1. aic_dvp

定义了DVP控制器的设备管理信息:

struct aic_dvp {
/* Device resources */
struct device *dev;

void __iomem *regs;
struct clk *clk;
struct reset_control *rst;
u32 clk_rate;
int irq;
int ch;

struct vb2_v4l2_buffer *vbuf[DVP_MAX_BUF];

struct aic_dvp_config cfg; /* The configuration of DVP HW */
struct v4l2_fwnode_bus_parallel bus; /* The format of input data */
struct v4l2_pix_format_mplane fmt; /* The format of output data */

/* Main Device */
struct v4l2_device v4l2;
struct media_device mdev;
struct video_device vdev;
struct media_pad vdev_pad;

/* Local subdev */
struct v4l2_subdev subdev;
struct media_pad subdev_pads[DVP_SUBDEV_PAD_NUM];
struct v4l2_mbus_framefmt subdev_fmt;

/* V4L2 Async variables */
struct v4l2_async_subdev asd;
struct v4l2_async_notifier notifier;
struct v4l2_subdev *src_subdev;
int src_pad;

/* V4L2 variables */
struct mutex lock;

/* Videobuf2 */
struct vb2_queue queue;
struct list_head buf_list;
spinlock_t qlock;
unsigned int sequence;
};

5.4.2. aic_dvp_config

定义了V4L2媒体数据的配置信息:

/**
* Save the configuration information for DVP controller.
* @code: media bus format code (MEDIA_BUS_FMT_*, in media-bus-format.h)
* @field: used interlacing type (enum v4l2_field)
* @width: frame width
* @height: frame height
*/
struct aic_dvp_config {
/* Input format */
enum dvp_input input;
enum dvp_input_yuv_seq input_seq;
enum v4l2_field field;

/* Output format */
enum dvp_output output;
u32 width;
u32 height;
u32 stride[DVP_MAX_PLANE];
u32 sizeimage[DVP_MAX_PLANE];
};

5.4.3. aic_dvp_buf

定义了DVP驱动的Buf管理信息:

struct aic_dvp_buf {
struct vb2_v4l2_buffer vb;
struct list_head list;
dma_addr_t paddr[DVP_MAX_PLANE];
bool dvp_using;
};

5.4.4. 输入输出的数据格式

5.4.4.1. enum dvp_input

定义了DVP输入数据的格式:

enum dvp_input {
DVP_IN_RAW = 0,
DVP_IN_YUV422 = 1,
DVP_IN_BT656 = 2,
};
5.4.4.2. enum dvp_input_yuv_seq

定义了DVP输入数据的YUV格式:

enum dvp_input_yuv_seq {
DVP_YUV_DATA_SEQ_YUYV = 0,
DVP_YUV_DATA_SEQ_YVYU = 1,
DVP_YUV_DATA_SEQ_UYVY = 2,
DVP_YUV_DATA_SEQ_VYUY = 3,
};
5.4.4.3. enum dvp_output

定义了DVP输出数据的格式:

enum dvp_output {
DVP_OUT_RAW_PASSTHROUGH = 0,
DVP_OUT_YUV422_COMBINED_NV16 = 1,
DVP_OUT_YUV420_COMBINED_NV12 = 2,
};

5.5. 接口设计

5.5.1. V4l2 device 的外部接口

5.5.1.1. ioctl 接口

用户态通过/dev/videoX节点的ioctl()接口与内核态V4L2框架、DVP驱动进行交互。主要功能有:

  • 获取、设置格式
  • 申请、释放Buf
  • QBuf、DQBuf
  • 导出dma-buf文件描述符
  • 启动、停止Stream

定义在include/uapi/linux/videodev2.h:

#define VIDIOC_QUERYCAP      _IOR('V',  0, struct v4l2_capability)
#define VIDIOC_ENUM_FMT _IOWR('V', 2, struct v4l2_fmtdesc)
#define VIDIOC_G_FMT _IOWR('V', 4, struct v4l2_format)
#define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format)
#define VIDIOC_REQBUFS _IOWR('V', 8, struct v4l2_requestbuffers)
#define VIDIOC_QUERYBUF _IOWR('V', 9, struct v4l2_buffer)
#define VIDIOC_G_FBUF _IOR('V', 10, struct v4l2_framebuffer)
#define VIDIOC_S_FBUF _IOW('V', 11, struct v4l2_framebuffer)
#define VIDIOC_OVERLAY _IOW('V', 14, int)
#define VIDIOC_QBUF _IOWR('V', 15, struct v4l2_buffer)
#define VIDIOC_EXPBUF _IOWR('V', 16, struct v4l2_exportbuffer)
#define VIDIOC_DQBUF _IOWR('V', 17, struct v4l2_buffer)
#define VIDIOC_STREAMON _IOW('V', 18, int)
#define VIDIOC_STREAMOFF _IOW('V', 19, int)
#define VIDIOC_G_PARM _IOWR('V', 21, struct v4l2_streamparm)
#define VIDIOC_S_PARM _IOWR('V', 22, struct v4l2_streamparm)
#define VIDIOC_G_STD _IOR('V', 23, v4l2_std_id)
#define VIDIOC_S_STD _IOW('V', 24, v4l2_std_id)
#define VIDIOC_ENUMSTD _IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_ENUMINPUT _IOWR('V', 26, struct v4l2_input)
#define VIDIOC_G_CTRL _IOWR('V', 27, struct v4l2_control)
#define VIDIOC_S_CTRL _IOWR('V', 28, struct v4l2_control)
#define VIDIOC_QUERYCTRL _IOWR('V', 36, struct v4l2_queryctrl)
#define VIDIOC_QUERYMENU _IOWR('V', 37, struct v4l2_querymenu)
#define VIDIOC_G_INPUT _IOR('V', 38, int)
#define VIDIOC_S_INPUT _IOWR('V', 39, int)
#define VIDIOC_G_OUTPUT _IOR('V', 46, int)
#define VIDIOC_S_OUTPUT _IOWR('V', 47, int)
#define VIDIOC_ENUMOUTPUT _IOWR('V', 48, struct v4l2_output)
#define VIDIOC_G_FREQUENCY _IOWR('V', 56, struct v4l2_frequency)
#define VIDIOC_S_FREQUENCY _IOW('V', 57, struct v4l2_frequency)
#define VIDIOC_CROPCAP _IOWR('V', 58, struct v4l2_cropcap)
#define VIDIOC_G_CROP _IOWR('V', 59, struct v4l2_crop)
#define VIDIOC_S_CROP _IOW('V', 60, struct v4l2_crop)
#define VIDIOC_G_JPEGCOMP _IOR('V', 61, struct v4l2_jpegcompression)
#define VIDIOC_S_JPEGCOMP _IOW('V', 62, struct v4l2_jpegcompression)
#define VIDIOC_QUERYSTD _IOR('V', 63, v4l2_std_id)
#define VIDIOC_TRY_FMT _IOWR('V', 64, struct v4l2_format)
#define VIDIOC_G_PRIORITY _IOR('V', 67, __u32) /* enum v4l2_priority */
#define VIDIOC_S_PRIORITY _IOW('V', 68, __u32) /* enum v4l2_priority */
#define VIDIOC_G_SLICED_VBI_CAP _IOWR('V', 69, struct v4l2_sliced_vbi_cap)
#define VIDIOC_LOG_STATUS _IO('V', 70)
#define VIDIOC_G_EXT_CTRLS _IOWR('V', 71, struct v4l2_ext_controls)
#define VIDIOC_S_EXT_CTRLS _IOWR('V', 72, struct v4l2_ext_controls)
#define VIDIOC_TRY_EXT_CTRLS _IOWR('V', 73, struct v4l2_ext_controls)
#define VIDIOC_ENUM_FRAMESIZES _IOWR('V', 74, struct v4l2_frmsizeenum)
#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)
#define VIDIOC_G_ENC_INDEX _IOR('V', 76, struct v4l2_enc_idx)
#define VIDIOC_ENCODER_CMD _IOWR('V', 77, struct v4l2_encoder_cmd)
#define VIDIOC_TRY_ENCODER_CMD _IOWR('V', 78, struct v4l2_encoder_cmd)

5.5.2. V4l2 subdev 的外部接口

用户态通过/dev/v4l-subdevX节点的ioctl()接口与内核态V4L2框架、DVP驱动、Sensor驱动进行交互。主要功能有:

  • 获取、设置格式
  • 获取、设置帧间隔
  • 枚举支持的mbus类型
  • 枚举支持的分辨率
  • 获取、设置区域裁剪

定义在 include/uapi/linux/v4l2-subdev.h:

#define VIDIOC_SUBDEV_G_FMT         _IOWR('V',  4, struct v4l2_subdev_format)
#define VIDIOC_SUBDEV_S_FMT _IOWR('V', 5, struct v4l2_subdev_format)
#define VIDIOC_SUBDEV_G_FRAME_INTERVAL _IOWR('V', 21, struct v4l2_subdev_frame_interval)
#define VIDIOC_SUBDEV_S_FRAME_INTERVAL _IOWR('V', 22, struct v4l2_subdev_frame_interval)
#define VIDIOC_SUBDEV_ENUM_MBUS_CODE _IOWR('V', 2, struct v4l2_subdev_mbus_code_enum)
#define VIDIOC_SUBDEV_ENUM_FRAME_SIZE _IOWR('V', 74, struct v4l2_subdev_frame_size_enum)
#define VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL _IOWR('V', 75, struct v4l2_subdev_frame_interval_enum)
#define VIDIOC_SUBDEV_G_CROP _IOWR('V', 59, struct v4l2_subdev_crop)
#define VIDIOC_SUBDEV_S_CROP _IOWR('V', 60, struct v4l2_subdev_crop)
#define VIDIOC_SUBDEV_G_SELECTION _IOWR('V', 61, struct v4l2_subdev_selection)
#define VIDIOC_SUBDEV_S_SELECTION _IOWR('V', 62, struct v4l2_subdev_selection)
/* The following ioctls are identical to the ioctls in videodev2.h */
#define VIDIOC_SUBDEV_G_STD _IOR('V', 23, v4l2_std_id)
#define VIDIOC_SUBDEV_S_STD _IOW('V', 24, v4l2_std_id)
#define VIDIOC_SUBDEV_ENUMSTD _IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_SUBDEV_G_EDID _IOWR('V', 40, struct v4l2_edid)
#define VIDIOC_SUBDEV_S_EDID _IOWR('V', 41, struct v4l2_edid)
#define VIDIOC_SUBDEV_QUERYSTD _IOR('V', 63, v4l2_std_id)
#define VIDIOC_SUBDEV_S_DV_TIMINGS _IOWR('V', 87, struct v4l2_dv_timings)
#define VIDIOC_SUBDEV_G_DV_TIMINGS _IOWR('V', 88, struct v4l2_dv_timings)
#define VIDIOC_SUBDEV_ENUM_DV_TIMINGS _IOWR('V', 98, struct v4l2_enum_dv_timings)
#define VIDIOC_SUBDEV_QUERY_DV_TIMINGS _IOR('V', 99, struct v4l2_dv_timings)
#define VIDIOC_SUBDEV_DV_TIMINGS_CAP _IOWR('V', 100, struct v4l2_dv_timings_cap)

5.6. APP Demo

5.6.1. APP层的处理流程

APP 中实现从Sensor -> DVP -> DE的数据通路,整体的处理流程如下图(图中按照访问对象分为三列,实际上整体是串行执行):

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/demo_flow.png

图 6.42 APP 中的处理流程

5.6.2. APP Demo参考实现

Demo代码见test-dvp/test_dvp.c,如下:

#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#include <linux/videodev2.h>
#include <linux/v4l2-subdev.h>
#include <video/artinchip_fb.h>
#include <artinchip/sample_base.h>

/* Global macro and variables */

#define VID_BUF_NUM 3
#define DVP_PLANE_NUM 2
#define CMA_BUF_MAX (8 * 1024 * 1024)
#define DMA_HEAP_DEV "/dev/dma_heap/reserved"
#define FB_DEV "/dev/fb0"
#define VIDEO_DEV "/dev/video0"
#define SENSOR_DEV "/dev/v4l-subdev0"
#define DVP_SUBDEV_DEV "/dev/v4l-subdev1"

static const char sopts[] = "f:c:u";
static const struct option lopts[] = {
{"format", required_argument, NULL, 'f'},
{"capture", required_argument, NULL, 'c'},
{"usage", no_argument, NULL, 'u'},
{0, 0, 0, 0}
};

struct video_plane {
int fd;
int buf;
int len;
};

struct video_buf_info {
char *vaddr;
u32 len;
u32 offset;
struct video_plane planes[DVP_PLANE_NUM];
};

struct aic_video_data {
int w;
int h;
int frame_size;
int frame_cnt;
int fmt; // output format
struct v4l2_subdev_format src_fmt;
struct video_buf_info binfo[VID_BUF_NUM];
};

static int g_fb_fd = -1;
static int g_video_fd = -1;
static int g_sensor_fd = -1;
static int g_dvp_subdev_fd = -1;
static struct aic_video_data g_vdata = {0};

/* Functions */

void usage(char *program)
{
printf("Usage: %s [options]: \n", program);
printf("\t -f, --format\t\tformat of input video, NV16/NV12 etc\n");
printf("\t -c, --count\t\tthe number of capture frame \n");
printf("\t -u, --usage \n");
printf("\n");
printf("Example: %s -f yuv422 -c 1\n", program);
}

/* Open a device file to be needed. */
int device_open(char *_fname, int _flag)
{
s32 fd = -1;

fd = open(_fname, _flag);
if (fd < 0) {
ERR("Failed to open %s errno: %d[%s]\n",
_fname, errno, strerror(errno));
exit(0);
}
return fd;
}

int set_ui_layer_alpha(int val)
{
int ret = 0;
struct aicfb_alpha_config alpha = {0};

alpha.layer_id = 1;
alpha.enable = 1;
alpha.mode = 1;
alpha.value = val;
ret = ioctl(g_fb_fd, AICFB_UPDATE_ALPHA_CONFIG, &alpha);
if (ret < 0)
ERR("ioctl() failed! errno: %d[%s]\n", errno, strerror(errno));

return ret;
}

void vidbuf_dmabuf_begin(struct aic_video_data *vdata)
{
int i, j;
struct aicfb_dmabuf_fd fds = {0};

for (i = 0; i < VID_BUF_NUM; i++) {
struct video_plane *plane = (struct video_plane *)&vdata->binfo[i];
for (j = 0; j < DVP_PLANE_NUM; j++, plane++) {
fds.fd = plane->fd;
if (ioctl(g_fb_fd, AICFB_GET_DMABUF, &fds) < 0)
ERR("ioctl() failed! err %d[%s]\n",
errno, strerror(errno));
}
}
}

void vidbuf_dmabuf_end(struct aic_video_data *vdata)
{
int i, j;
struct aicfb_dmabuf_fd fds = {0};

for (i = 0; i < VID_BUF_NUM; i++) {
struct video_plane *plane = (struct video_plane *)&vdata->binfo[i];
for (j = 0; j < DVP_PLANE_NUM; j++, plane++) {
fds.fd = plane->fd;
if (ioctl(g_fb_fd, AICFB_PUT_DMABUF, &fds) < 0)
ERR("ioctl() failed! err %d[%s]\n",
errno, strerror(errno));
}
}
}

int sensor_get_fmt(void)
{
struct v4l2_subdev_format f = {0};

g_sensor_fd = device_open(SENSOR_DEV, O_RDWR);
if (g_sensor_fd < 0)
return -1;

f.pad = 0;
f.which = V4L2_SUBDEV_FORMAT_ACTIVE;
if (ioctl(g_sensor_fd, VIDIOC_SUBDEV_G_FMT, &f) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}
#if 0
f.format.code = MEDIA_BUS_FMT_YUYV8_2X8;
if (ioctl(g_sensor_fd, VIDIOC_SUBDEV_S_FMT, &f) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}
#endif
g_vdata.src_fmt = f;
g_vdata.w = g_vdata.src_fmt.format.width;
g_vdata.h = g_vdata.src_fmt.format.height;
return 0;
}

int dvp_subdev_set_fmt(void)
{
struct v4l2_subdev_format f = g_vdata.src_fmt;

g_dvp_subdev_fd = device_open(DVP_SUBDEV_DEV, O_RDWR);
if (g_dvp_subdev_fd < 0)
return -1;

f.pad = 0;
f.which = V4L2_SUBDEV_FORMAT_ACTIVE;
if (ioctl(g_dvp_subdev_fd, VIDIOC_SUBDEV_S_FMT, &f) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

return 0;
}

int dvp_cfg(int width, int height, int format)
{
struct v4l2_format f = {0};

f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
f.fmt.pix_mp.width = g_vdata.src_fmt.format.width;
f.fmt.pix_mp.height = g_vdata.src_fmt.format.height;
f.fmt.pix_mp.pixelformat = g_vdata.fmt;
f.fmt.pix_mp.num_planes = DVP_PLANE_NUM;
if (ioctl(g_video_fd, VIDIOC_S_FMT, &f) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

return 0;
}

int dvp_expbuf(int index)
{
int i;
struct video_buf_info *binfo = &g_vdata.binfo[index];
struct v4l2_exportbuffer expbuf = {0};

for (i = 0; i < DVP_PLANE_NUM; i++) {
memset(&expbuf, 0, sizeof(struct v4l2_exportbuffer));
expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
expbuf.index = index;
expbuf.plane = i;
if (ioctl(g_video_fd, VIDIOC_EXPBUF, &expbuf) < 0) {
ERR("ioctl() failed! err %d[%s]\n",
errno, strerror(errno));
return -1;
}
binfo->planes[i].fd = expbuf.fd;
}

return 0;
}

int dvp_request_buf(int num)
{
int i;
struct v4l2_buffer buf = {0};
struct v4l2_requestbuffers req = {0};
struct v4l2_plane planes[DVP_PLANE_NUM];

req.count = num;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
req.memory = V4L2_MEMORY_MMAP; // Only MMAP will do alloc memory
if (ioctl(g_video_fd, VIDIOC_REQBUFS, &req) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

for (i = 0; i < num; i++) {
if (dvp_expbuf(i) < 0)
return -1;

memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.index = i;
buf.length = DVP_PLANE_NUM;
buf.memory = V4L2_MEMORY_DMABUF;
buf.m.planes = planes;
if (ioctl(g_video_fd, VIDIOC_QUERYBUF, &buf) < 0) {
ERR("ioctl() failed! err %d[%s]\n",
errno, strerror(errno));
return -1;
}
}

return 0;
}

void dvp_release_buf(int num)
{
int i;
struct video_buf_info *binfo = NULL;

for (i = 0; i < num; i++) {
binfo = &g_vdata.binfo[i];
if (binfo->vaddr) {
munmap(binfo->vaddr, binfo->len);
binfo->vaddr = NULL;
}
}
}

int dvp_queue_buf(int index)
{
struct v4l2_buffer buf = {0};
struct v4l2_plane planes[DVP_PLANE_NUM] = {0};

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = index;
buf.length = DVP_PLANE_NUM;
buf.m.planes = planes;
if (ioctl(g_video_fd, VIDIOC_QBUF, &buf) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

return 0;
}

int dvp_dequeue_buf(int *index)
{
struct v4l2_buffer buf = {0};
struct v4l2_plane planes[DVP_PLANE_NUM] = {0};

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.length = DVP_PLANE_NUM;
buf.m.planes = planes;
if (ioctl(g_video_fd, VIDIOC_DQBUF, &buf) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

*index = buf.index;
return 0;
}

int dvp_start(void)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

if (ioctl(g_video_fd, VIDIOC_STREAMON, &type) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

return 0;
}

int dvp_stop(void)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

if (ioctl(g_video_fd, VIDIOC_STREAMOFF, &type) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

return 0;
}

int video_layer_set(struct aic_video_data *vdata, int index)
{
struct aicfb_layer_data layer = {0};
struct video_buf_info *binfo = &vdata->binfo[index];

layer.layer_id = 0;
layer.enable = 1;
#if 1
layer.scale_size.width = vdata->w;
layer.scale_size.height = vdata->h;
#else
layer.scale_size.width = 780;
layer.scale_size.height = 600;
#endif
layer.pos.x = 10;
layer.pos.y = 10;
layer.buf.size.width = vdata->w;
layer.buf.size.height = vdata->h;
layer.buf.format = AIC_FMT_NV16;
layer.buf.dmabuf_fd[0] = binfo->planes[0].fd;
layer.buf.dmabuf_fd[1] = binfo->planes[1].fd;
layer.buf.stride[0] = vdata->w;
layer.buf.stride[1] = vdata->w;

if (ioctl(g_fb_fd, AICFB_UPDATE_LAYER_CONFIG, &layer) < 0) {
ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
return -1;
}

return 0;
}

int main(int argc, char **argv)
{
int c, frame_cnt = 1;
int i, index = 0;

DBG("Compile time: %s\n", __TIME__);
g_vdata.fmt = V4L2_PIX_FMT_NV16;
while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
switch (c) {
case 'f':
if (strncasecmp("nv12", optarg, strlen(optarg)) == 0)
g_vdata.fmt = V4L2_PIX_FMT_NV12;
continue;
case 'c':
frame_cnt = str2int(optarg);
continue;
case 'u':
usage(argv[0]);
return 0;
default:
break;
}
}

if (sensor_get_fmt() < 0)
return -1;
if (dvp_subdev_set_fmt() < 0)
return -1;

if (g_vdata.fmt == V4L2_PIX_FMT_NV16)
g_vdata.frame_size = g_vdata.w * g_vdata.h * 2;
if (g_vdata.fmt == V4L2_PIX_FMT_NV12)
g_vdata.frame_size = (g_vdata.w * g_vdata.h * 3) >> 1;

g_fb_fd = device_open(FB_DEV, O_RDWR);
if (g_fb_fd < 0)
return -1;
if (set_ui_layer_alpha(128) < 0)
goto end;

g_video_fd = device_open(VIDEO_DEV, O_RDWR);
if (g_video_fd < 0)
goto end;
if (dvp_cfg(g_vdata.w, g_vdata.h, g_vdata.fmt) < 0)
goto end;
if (dvp_request_buf(VID_BUF_NUM) < 0)
goto end;

vidbuf_dmabuf_begin(&g_vdata);
for (i = 0; i < VID_BUF_NUM; i++)
if (dvp_queue_buf(i) < 0)
goto end;

if (dvp_start() < 0)
goto end;
for (i = 0; i < frame_cnt; i++ ) {
if (dvp_dequeue_buf(&index) < 0)
break;
DBG("Set the buf %d to video layer\n", index);
if (video_layer_set(&g_vdata, index) < 0)
break;
dvp_queue_buf(index);
}
dvp_stop();
vidbuf_dmabuf_end(&g_vdata);
dvp_release_buf(VID_BUF_NUM);

end:
if (g_fb_fd > 0)
close(g_fb_fd);
if (g_video_fd > 0)
close(g_video_fd);
if (g_sensor_fd > 0)
close(g_sensor_fd);
if (g_dvp_subdev_fd > 0)
close(g_dvp_subdev_fd);

return 0;
}

6. 常见问题

6.1. 摄像头初始化失败

6.1.1. 现象

板子启动后,摄像头的V4L2 device注册失败,此时DVP的注册流程也因此不完整,在Sysfs中会找不到 /dev/video0 不存在。

6.1.2. 原因分析

一般情况下,摄像头需要通过 I2C 来访问,所以要确保 I2C和摄像头两个模块是否打开。

同时,要确认以下配置是否正确:

  1. 硬件上,摄像头是连接到哪个I2C通道,对应的DTS配置是否正确,配置参见 配置 OV5640 摄像头
  2. 摄像头的I2C 设备地址是否正确,参见 配置 OV5640 摄像头 中的参数 camera@3C
  3. 摄像头的供电是否正常。

#ifdef AIC_ONLY

以下内容仅供内部使用

注意

在FPGA环境中,摄像头的电源GPIO在上电时已正常工作,不能去复位,否则会导致摄像头的寄存器读写失败。

屏蔽OV5640的GPIO复位的patch:

    ov5640: Must not set the power GPIO in FPGA.

Signed-off-by: matteo <duanmt@artinchip.com>
Change-Id: I55b163f8c59f257532e0c047931765bda6a89c85

diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c
index 8f0812e85..c240ead99 100644
--- a/drivers/media/i2c/ov5640.c
+++ b/drivers/media/i2c/ov5640.c
@@ -1857,7 +1857,7 @@ static void ov5640_power(struct ov5640_dev *sensor, bool enable)
{
gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1);
}
-
+#ifndef CONFIG_DEBUG_ON_FPGA_BOARD_ARTINCHIP
static void ov5640_reset(struct ov5640_dev *sensor)
{
if (!sensor->reset_gpio)
@@ -1877,6 +1877,7 @@ static void ov5640_reset(struct ov5640_dev *sensor)
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
usleep_range(20000, 25000);
}
+#endif

static int ov5640_set_power_on(struct ov5640_dev *sensor)
{
@@ -1898,8 +1899,10 @@ static int ov5640_set_power_on(struct ov5640_dev *sensor)
goto xclk_off;
}

+#ifndef CONFIG_DEBUG_ON_FPGA_BOARD_ARTINCHIP
ov5640_reset(sensor);
ov5640_power(sensor, true);
+#endif

ret = ov5640_init_slave_id(sensor);
if (ret)

>>>> 以上内容仅供内部使用

#endif

6.2. 画面的流畅度问题

6.2.1. 现象

界面显示的摄像头画面有明显卡顿情况。

6.2.2. 解决方法

  1. 如果DVP驱动中的调试信息打开了,每一帧数据处理都有输出log,会影响帧率,需要关掉。修改方法见 调试开关
  2. 尝试增加test_dvp中的buffer数量,保证buf队列中有充裕的空闲buf。buffer数量定义见:
#define VID_BUF_NUM     3