电子大神的日记本,供应链专家的功夫茶盘,在这里记录、分享与共鸣。

登录以开始

android驱动开发(二)——V4L2

Video for Linux Two
V4L2的是V4L的第二个版本。原来的V4L被引入到Linux内核2.1.x的开发周期后期。Video4Linux2修正了一些设计缺陷,并开始出现在2.5.X内核。Video4Linux2驱动程序包括Video4Linux1应用的兼容模式,但实际上,支持是不完整的,并建议V4L2的设备使用V4L2的模式。现在,该项目的DVB-Wiki托管在LinuxTV的网站上。
要想了解 V4l2 有几个重要的文档是必须要读的,Documentation/video4linux目录下的V4L2-framework.txt和videobuf、V4L2的官方API文档V4L2 API Specification 、drivers/media/video目录下的vivi.c(虚拟视频驱动程序 -此代码模拟一个真正的视频设备V4L2 API)。
V4l2可以支持多种设备,它可以有以下几种接口:

  1. 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.
  2. 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备.
  3. 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.
  4. 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号.
  5. 收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流.
    V4L2 驱动核心
    V4L2 的驱动源码在 drivers/media/video目录下,主要核心代码有:
    v4l2-dev.c //linux版本2视频捕捉接口,主要结构体 video_device 的注册
    v4l2-common.c //在Linux操作系统体系采用低级别的操作一套设备structures/vectors的通用视频设备接口。
    //此文件将替换videodev.c的文件配备常规的内核分配。
    v4l2-device.c //V4L2的设备支持。注册v4l2_device
    v4l22-ioctl.c //处理V4L2的ioctl命令的一个通用的框架。
    v4l2-subdev.c //v4l2子设备
    v4l2-mem2mem.c //内存到内存为Linux和videobuf视频设备的框架。设备的辅助函数,使用其源和目的地videobuf缓冲区。
    头文件linux/videodev2.h、media/v4l2-common.h、media/v4l2-device.h、media/v4l2-ioctl.h、media/v4l2-dev.h、media/v4l2-ioctl.h等。
    V4l2相关结构体
    1.V4l2_device
    struct V4l2_device{
    /* DEV-> driver_data指向这个结构。 注:DEV可能是空的,如果没有父设备是如同ISA设备。 */
    struct device *dev;
    /* 用于跟踪注册的subdevs */
    struct list_head subdevs;
    /*锁定此结构体;可以使用的驱动程序以及如果这个结构嵌入到一个更大的结构。 */
    spinlock_t lock;
    /* 独特的设备名称,默认情况下,驱动程序姓名+总线ID */
    char name[V4L2_DEVICE_NAME_SIZE];
    /*报告由一些子设备调用的回调函数。 */
    void (*notify)(struct v4l2_subdev *sd,
    unsigned int notification, void *arg);
    };
    v4l2_device注册和注销
    v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
    第一个参数‘dev’通常是一个pci_dev的struct device的指针,但它是ISA设备或一个设备创建多个PCI设备时这是罕见的DEV为NULL,因此makingit不可能联想到一个特定的父母v4l2_dev。 您也可以提供一个notify()回调子设备,可以通过调用通知你的事件。取决于你是否需要设置子设备。一个子设备支持的任何通知必须在头文件中定义 .
    注册时将初始化 v4l2_device 结构体. 如果 dev->driver_data字段是空, 它将连接到 v4l2_dev.
    v4l2_device_unregister(struct v4l2_device *v4l2_dev);
    注销也将自动注销设备所有子设备。
    2.video_device
    在/dev目录下的设备节点使用的 struct video_device(v4l2_dev.h)创建。
    struct video_device
    {
    /*设备操作函数 */
    const struct v4l2_file_operations *fops;
    /* 虚拟文件系统 */
    struct device dev; /* v4l 设备 */
    struct cdev *cdev; /* 字符设备 */
    struct device *parent; /*父设备 */
    struct v4l2_device *v4l2_dev; /* v4l2_device parent */
    /* 设备信息 */
    char name[32];
    int vfl_type;
    /* 'minor' is set to -1 if the registration failed */
    int minor;
    u16 num;
    /* use bitops to set/clear/test flags */
    unsigned long flags;
    /*属性来区分一个物理设备上的多个索引 */
    int index;
    /* V4L2 文件句柄 */
    spinlock_t fh_lock; /*锁定所有的 v4l2_fhs */
    struct list_head fh_list; /* List of struct v4l2_fh */
    int debug; /* Activates debug level*/
    /* Video standard vars */
    v4l2_std_id tvnorms; /* Supported tv norms */
    v4l2_std_id current_norm; /* Current tvnorm */
    /* 释放的回调函数 */
    void (*release)(struct video_device *vdev);
    /* 控制的回调函数 */
    const struct v4l2_ioctl_ops *ioctl_ops;
    }
    动态分配:
    struct video_device *vdev = video_device_alloc();
    结构体配置:
    fops:设置这个v4l2_file_operations结构,file_operations的一个子集。v4l2_dev: 设置这个v4l2_device父设备
    name:
    ioctl_ops:使用v4l2_ioctl_ops简化的IOCTL,然后设置v4l2_ioctl_ops结构。
    lock:如果你想要做的全部驱动程序锁定就保留为NULL。否则你给它一个指针指向一个mutex_lock结构体和任何v4l2_file_operations被调用之前核心应该释放释放锁。
    parent:一个硬件设备有多个PCI设备,都共享相同v4l2_device核心时,设置注册使用NULL v4l2_device作为父设备结构。
    flags:可选的。设置到V4L2_FL_USE_FH_PRIO如你想让框架处理VIDIOC_G/ S_PRIORITY的ioctl。这就需要您使用结构v4l2_fh。这个标志最终会消失,一旦所有的驱动程序使用的核心优先处理。但现在它必须明确设定。
    如果使用v4l2_ioctl_ops,那么你应该设置。unlocked_ioctlvideo_ioctl2在v4l2_file_operations结构。
    注册/注销 video_device:
    video_register_device(struct video_device *vdev, int type, int nr);
    __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use)
    参数:
    vdev:我们要注册的视频设备结构。
    type:设备类型注册
    nr:设备号(0==/dev/video0,1​​== /dev/video1,...-1==释放第一个)
    warn_if_nr_in_use:如果所需的设备节点号码已经在使用另一个号码代替选择。
    注册程式分配次设备号和设备节点的数字根据请求的类型和注册到内核新设备节点。如果无法找到空闲次设备号或设备节点编号,或者如果设备节点注册失败,就返回一个错误。
    video_unregister_device(struct video_device *vdev);
    3.v4l2_subdev
    每个子设备驱动程序必须有一个v4l2_subdev结构。这个结构可以独立简单的设备或者如果需要存储更多的状态信息它可能被嵌入在一个更大的结构。由于子设备可以做很多不同的东西,你不想结束一个巨大的OPS结构其中只有少数的OPS通常执行,函数指针进行排序按类别,每个类别都有其自己的OPS结构。顶层OPS结构包含的类别OPS结构,这可能是NULL如果在subdev驱动程序不支持任何从该类别指针。
    struct v4l2_subdev {
    #if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_entity entity;
    #endif
    struct list_head list;
    struct module *owner;
    u32 flags;
    struct v4l2_device *v4l2_dev;
    const struct v4l2_subdev_ops *ops;
    /* 从驱动程序中不要调用这些内部操作函数! */
    const struct v4l2_subdev_internal_ops *internal_ops;
    /*这个subdev控制处理程序。可能是NULL。 */
    struct v4l2_ctrl_handler *ctrl_handler;
    /* 名字必须是唯一 */
    char name[V4L2_SUBDEV_NAME_SIZE];
    /* 可用于到类似subdevs组,值是驱动程序特定的 */
    u32 grp_id;
    /* 私有数据的指针 */
    void *dev_priv;
    void *host_priv;
    /* subdev 设备节点*/
    struct video_device devnode;
    /* 事件的数量在打开的时候被分配 */
    unsigned int nevents;
    };
    4.v4l2_buffer
    struct v4l2_buffer {
    __u32 index;
    enum v4l2_buf_type type;
    __u32 bytesused;
    __u32 flags;
    enum v4l2_field field;
    struct timeval timestamp;
    struct v4l2_timecode timecode;
    __u32 sequence;
    /* memory location */
    enum v4l2_memory memory;
    union {
    __u32 offset;
    unsigned long userptr;
    } m;
    __u32 length;
    __u32 input;
    __u32 reserved;
    };
    V4L2核心API提供了一套标准方法的用于处理视频缓冲器(称为“videobuf”)。这些方法允许驱动程序以一致的方式来实现read(),mmap()和overlay()。目前使用的设备上的视频缓冲器,支持scatter/gather方法(videobuf-dma-SG),线性存取的DMA的(videobuf-DMA-contig),vmalloc分配的缓冲区,主要用于在USB驱动程序(DMA缓冲区的方法videobuf-vmalloc)。
    videobuf层的功能为一种V4L2驱动和用户空间之间的粘合层。它可以处理存储视频帧缓冲区的分配和管理。有一组可用于执行许多标准的POSIX I / O系统调用的功能,包括read(),poll()的,happily,mmap()。另一套功能可以用来实现大部分的V4L2的ioctl()调用相关的流式I/ O的,包括缓冲区分配,排队和dequeueing,流控制。驱动作者使用videobuf规定了一些设计决定,但回收期在驱动器和一个V4L2的用户空间API的贯彻实施在减少代码的形式。
    关于videobuf的层的更多信息,请参阅Documentation/video4linux/videobuf
    V4l2驱动架构
     

驱动架构图

所有的驱动程序有以下结构:

1) 每个设备包含设备状态的实例结构。
2) 子设备的初始化和命令方式(如果有).
3) 创建V4L2的设备节点 (/dev/videoX, /dev/vbiX and /dev/radioX)和跟踪设备节点的具体数据。
4)文件句柄特定的结构,包含每个文件句柄数据;
5) 视频缓冲处理。
 

 

驱动源码分析

vivi.c 虚拟视频驱动程序
----- 此代码模拟一个真正的视频设备V4L2 API (位于drivers/media/video目录下)
入口:+int __init vivi_init(void)
 

  • vivi_create_instance(i) /*创建设备*//**/。

  • 分配一个vivi_dev的结构体 /*它嵌套这结构体v4l2_device 和video_device*/
  • v4l2_device_register(NULL, &dev->v4l2_dev);/*注册vivi_dev中的V4l2_device*/
  • 初始化视频的DMA队列
  • 初始化锁
  • video_device_alloc(); 动态分配video_device结构体
  • 构建一个video_device结构体 vivi_template 并赋给上面分配的video_device
    static struct video_device vivi_template = {
    . name = "vivi",
    .fops = &vivi_fops,
    .ioctl_ops = &vivi_ioctl_ops,
    .minor = -1,
    .release = video_device_release,
    .tvnorms = V4L2_STD_525_60,
    .current_norm = V4L2_STD_NTSC_M,
    };
  • video_set_drvdata(vfd, dev);设置驱动程序专有数据
  • 所有控件设置为其默认值
  • list_add_tail(&dev->vivi_devlist, &vivi_devlist);添加到设备列表
  • 构建 v4l2_file_operations 结构体vivi_fops 并实现.open .release .read .poll .mmap函数
    ----- .ioctl 用标准的v4l2控制处理程序
  • 构建 v4l2_ioctl_ops结构体 vivi_ioctl_ops
    static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
    .vidioc_querycap = vidioc_querycap,
    .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
    .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
    .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
    .vidioc_reqbufs = vidioc_reqbufs,
    .vidioc_querybuf = vidioc_querybuf,
    .vidioc_qbuf = vidioc_qbuf,
    .vidioc_dqbuf = vidioc_dqbuf,
    .vidioc_s_std = vidioc_s_std,
    .vidioc_enum_input = vidioc_enum_input,
    .vidioc_g_input = vidioc_g_input,
    .vidioc_s_input = vidioc_s_input,
    .vidioc_queryctrl = vidioc_queryctrl,
    .vidioc_g_ctrl = vidioc_g_ctrl,
    .vidioc_s_ctrl = vidioc_s_ctrl,
    .vidioc_streamon = vidioc_streamon,
    .vidioc_streamoff = vidioc_streamoff,
    #ifdef CONFIG_VIDEO_V4L1_COMPAT
    .vidiocgmbuf = vidiocgmbuf,
    #endif
    };
  • int vivi_open(struct file *file)
  • vivi_dev *dev = video_drvdata(file); 访问驱动程序专用数据
  • 分配+初始化句柄(vivi_fh)数据
  • 重置帧计数器
  • videobuf_queue_vmalloc_init(); 初始化视频缓冲队列
  • 开启一个新线程用于开始和暂停
  • 实现自定义的v4l2_ioctl_ops 函数
     

附:Sub-devices

Sub-devices 支持的头文件 V4l2-subdev.h

  • 子设备是某种程度上主桥设备连接的设备。这些设备通常是音频/视频流 合并器/编码器/解码器或传感器和摄像头控制器。

 

一般控制这些器件通过I2C总线,但也可用于其他总线。

  • v4l2_subdev结构提供了一个通用的方式访问这些设备, sub-devices支持的大部分操作分为以下几个类别:

核心操作、音频操作、视频操作、调谐器操作,每个类别都可以在实现subdev的驱动程序时设置自己的操作。

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;
};

  • 一个subdev驱动程序可以设置不执行的操作函数指针为空(如要产生音频subdev通常没有实现视频类操作)。唯一的例外是核心类:必须始终存在。

  • 这些OPS都在内部使用,所以它是没有必要去更改,添加或删除它们或从一个移动到另一个类别。目前这些OPS基于原有的控制命令,但OPS不仅限于一个参数,一旦所有的I2C驱动subdev转换为使用这些OPS,就有改进的余地。

1. 核心操作 v4l2_subdev_core_ops

struct v4l2_subdev_core_ops {
int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);
int (*log_status)(struct v4l2_subdev *sd);
int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);
int (*init)(struct v4l2_subdev *sd, u32 val);
int (*load_fw)(struct v4l2_subdev *sd);
int (*reset)(struct v4l2_subdev *sd, u32 val);
int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);
int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
int (*g_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
int (*s_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
int (*try_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm);
int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);
long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
#ifdef CONFIG_VIDEO_ADV_DEBUG
int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
int (*s_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
#endif
};

我们强烈建议至少实现这些操作:

g_chip_ident
log_status
g_register
s_register

这些提供了基本的调试支持。
ioctl:是指为仿制的ioctl类的命令。根据使用情况,它可能是更好地使用特定子设备操作(目前尚未实施),因为OPS提供适当的类型检查。

s_config:如果设置,那么它总是在v4l2_subdev被注册后由v4l2_i2c_new_subdev函数调用后。它用于将数据传递到平台,可用于subdev在初始化过程中。

init:一些适当的默认值初始化传感器寄存器。不使用新的驱动程序时现有的驱动程序应该删除它。

reset:复位通用的命令。参数选择复位哪一个子系统。传递0将复位整个芯片。使用新的驱动程序前要先挂载在linux-media 列表上再复位。

s_gpio:设置GPIO引脚。如果需要的话可能还需要扩展方向参数。

 

2.分类操作

struct v4l2_subdev_video_ops、

struct v4l2_subdev_audio_ops、

struct v4l2_subdev_tuner_ops

以上结构体都在V4l2-subdev.h 中定义。由于操作函数太多就没有必要一一列举。
 

3.v4l2_subde初始化

(1)子设备驱动初始化的v4l2_subdev结构使用:

v4l2_subdev_init(sd, &ops);

(2)V4l2 i2c子设备的初始化

void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, const struct v4l2_subdev_ops *ops)

(3)装载一个新的i2c 子设备
v4l2_i2c_new_subdev_board()
注:如果模块加载先加载这驱动程序发现后设置client->driver,如果模块不是先加载的,那么I2C核心试图延迟加载的模块,然后驱动程序的client->drive 默认为 NULL,直到加载模块。如果其他驱动程序要使用I2C设备,使明确的加载模块是最好的选择,这延迟加载机制不起作用。
(4)获取i2c subdev地址
short v4l2_i2c_subdev_addr(struct v4l2_subdev *sd)
返回I2C v4l2_subdev客户端地址。

博主
465474289@qq.com
玩驱动
Android、Linux底层驱动开发移植。驱动开发QQ群128986249
点击跳转