linux media v4l2,Overview of the V4L2 driver framework (v4l2_subdev)

5 struct v4l2_subdev

v4l2_device下面一个层次是v4l2_subdev,它需要和它的子设备进行通信,如果说camera

host是一个v4l2_device设备,那么就可以将camera模组称为一个v4l2_subdev设备,它们之间的通信可以采取多种方式常见的有I2C和SPI.

通常这些子设备都属于I2C设备,当然也有其他接口的比如SPI

Interface,为了给驱动程序提供一个一致性的接口,Linux内核为这些设备抽象出了一个struct

v4l2_subdev结构来描述一个v4l2

sub device,所以每个v4l2

sub-device都需要一个struct

v4l2_subdev结构实例,该结构的定义如下:

include/media/v4l2-subdev.h

点击(此处)折叠或打开

/* Each instance of a subdev driver should create this struct, either

stand-alone or embedded in a larger struct.

*/

struct v4l2_subdev {

#if defined(CONFIG_MEDIA_CONTROLLER)/*这系列文章暂不针对media多媒体设备*/

struct media_entity entity;

#endif

struct list_head list;      /*用于管理每个子设备*/

struct module *owner;       /*指向i2c_lient driver module*/

/*该子设备属于那种设备(如I2C),see V4L2_SUBDEV_FL_IS_XX and */

u32 flags;        /*是否包含设备节点 V4L2_SUBDEV_FL_HAS_DEVNODE 有设备节点*/

struct v4l2_device *v4l2_dev;

const struct v4l2_subdev_ops *ops;

/* Never call these internal ops from within a */

const struct v4l2_subdev_internal_ops *internal_ops;

/* The control handler of this subdev. May be NULL. */

struct v4l2_ctrl_handler *ctrl_handler;

/* name must be unique */

char name[V4L2_SUBDEV_NAME_SIZE];/*该模组的名字*/

/* can be used to group similar subdevs, value is driver-specific */

u32 grp_id;

/* pointer to private data */

void *dev_priv;              /*eg. point i2c_client data struct*/

void *host_priv;             /*eg. point camera host data struct*/

/* subdev device node */

struct video_device devnode;/*指向video device设备实例,后面会详细介绍*/

/* number of events to be allocated on open */

unsigned int nevents;

};

u32

flags:flags字段用于标志该sub_devs属于那一种设备(如I2C,SPI),另外还标致着该子设备是否产生设备节点,它一共有四种取值;V4L2_SUBDEV_FL_IS_I2C,V4L2_SUBDEV_FL_IS_SPI,

V4L2_SUBDEV_FL_HAS_DEVNODE,V4L2_SUBDEV_FL_HAS_EVENTS

v4l2_dev:指向struct

v4l2_device结构,在v4l2_subdev的注册过程会将该指针指向前面注册过的v4l2_device设备结构;

ops:指向struct

v4l2_subdev_ops *ops函数结构指针,这个结构指针是需要我们在sub

device模组驱动中去实现的;

dev_priv:如果该设备为I2C设备,则该字段用于保存i2c_client结构

host_priv:用于保存camera

host数据结构.

devnode:指向video设备结构实例

每一个subdev驱动程序都应该创建一个struct

v4l2_subdev结构实例,你可以在你的驱动程序中单独的为该结构申请内存,同样可以将这个结构嵌入到其他的驱动数据结构中去.一般性的我们将该结构嵌入到camera

sensor模组驱动的数据结构中去,同样为了该sub

device和i2c

client之间的相互引用,我们还常把i2c_client结构嵌入到模组驱动数据结构中去系统为我们提供了2个API,用于struct

v4l2_subdev结构和该子设备所属的设备类型(如I2C,SPI)等数据结构之间的相互引用,定义如下:

点击(此处)折叠或打开

static inline void v4l2_set_subdevdata(struct v4l2_subdev *sd, void *p)

{

sd->dev_priv = p;

}

你可以简单的调用v4l2_set_subdevdata(sd,

client)就可以将i2c_client结构保存到struct

v4l2_subdev结构中的dev_priv字段中去,这样你下次使用的时候只需要调用下面这个API就可以返回你保存过的i2c_client结构

同样的对于struct

v4l2_subdev结构的camera

host端,内核也为我们提供了两个API用于它和hos驱动结构之间的相互引用

点击(此处)折叠或打开

static inline void v4l2_set_subdev_hostdata(struct v4l2_subdev *sd, void *p)

{

sd->host_priv = p;

}

这样你只要简单的调用v4l2_set_subdev_hostdata()就可以将camera

host那端的数据结构保存到struct

v4l2_subdev结构的host_priv字段,在另一地方要引用camera

host对应的数据结构的时候之需要调用v4l2_get_subdev_hostdata就可以返回了

5.1

v4l2_subdev initialized

一个子设备驱动使用如下函数初始化v4l2_subdev结构:

点击(此处)折叠或打开

v4l2_subdev_init(sd, &ops);

点击(此处)折叠或打开

void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops)

{

INIT_LIST_HEAD(&sd->list);

BUG_ON(!ops);

sd->ops = ops;

sd->v4l2_dev = NULL;

sd->flags = 0;

sd->name[0] = '\0';

sd->grp_id = 0;

sd->dev_priv = NULL;

sd->host_priv = NULL;

#if defined(CONFIG_MEDIA_CONTROLLER)

sd->entity.name = sd->name;

sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV;

#endif

}

其中的subdev->name字段是需要我们去初始化的,并且还需要设置这个module

owner,对于I2C设备系统为我们提供了相应的v4l2_i2c_subdev_init接口来初始化一个subdev子模块,这个接口实现如下:drivers/media/video/v4l2-common.c

点击(此处)折叠或打开

void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,

const struct v4l2_subdev_ops *ops)

{

v4l2_subdev_init(sd, ops);/*初始化struct v4l2_subdev结构,绑定操作函数指针*/

sd->flags |= V4L2_SUBDEV_FL_IS_I2C;/*设置该标志,指定该模组设备属于I2C设备*/

/* the owner is the same as the i2c_client's driver owner */

sd->owner = client->driver->driver.owner;/*init the sd->owner*/

/* i2c_client and v4l2_subdev point to one another */

v4l2_set_subdevdata(sd, client);

i2c_set_clientdata(client, sd);

/* 初始化subdev结构的name字段*/

snprintf(sd->name, sizeof(sd->name), "%s %d-%04x",

client->driver->driver.name, i2c_adapter_id(client->adapter),

client->addr);

}5.2

v4l2_subdev register:

一个设备v4l2_subdev驱动需要将v4l2_subdev结构注册到v4l2_device当中去(也就是将struct

v4l2_subdev和struct

v4l2_device结构关联起来)使用如下API:

点击(此处)折叠或打开

int err = v4l2_device_register_subdev(v4l2_dev, sd);

删除一部分信息,留下一部分我比较关注的信息这个API的实现如下:

点击(此处)折叠或打开

int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,

struct v4l2_subdev *sd)

{

int err;

/* Check for valid input */

if (v4l2_dev == NULL || sd == NULL || !sd->name[0])

return -EINVAL;

/* 检测是否重复注册 */

WARN_ON(sd->v4l2_dev != NULL);

/*通过try_module_get去获取sub_dev所属的内核模块是否已经成功被加载

*如果该sub_dev所属的内核模块还未被成功注册进内核则返回ENODEV

*try_module_get通过回调module_is_live来判断sd->owner的状态

*/

if (!try_module_get(sd->owner))

return -ENODEV;

/*关联v4l2_dev*/

sd->v4l2_dev = v4l2_dev;

/*将v4l2_subdev结构中的list链表挂到v4l2_device结构中的subdevs链表中去*/

spin_lock(&v4l2_dev->lock);

list_add_tail(&sd->list, &v4l2_dev->subdevs);

spin_unlock(&v4l2_dev->lock);

return 0;

}

现在你再返回去看这两个结构体,很明显子设备的注册就是将struct

v4l2_subdev结构中的list链表字段追加到struct

v4l2_device结构中的subdevs链表尾中.如果子设备模块在它成功注册之前消失了,那这个注册函数肯定会失败的。这个函数成功执行之后,这样subdev->dev就会指向v4l2_device。于是就关联起来了。

5.3

v4l2_subdev unregister:

通过\如下函数注销掉之前注册的子设备:

点击(此处)折叠或打开

v4l2_device_unregister_subdev(sd);

在这个函数执行之后,子设备模块将被卸载并且sd->dev==NULL;

点击(此处)折叠或打开

void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)

{

struct v4l2_device *v4l2_dev;

/* return if it isn't registered */

if (sd == NULL || sd->v4l2_dev == NULL)

return;

v4l2_dev = sd->v4l2_dev;

/*将v4l2_subdev结构中的list链表从v4l2_device结构的subdevs链表中删除*/

spin_lock(&v4l2_dev->lock);

list_del(&sd->list);

spin_unlock(&v4l2_dev->lock);

/*最后注销设备节点*/

video_unregister_device(&sd->devnode);

module_put(sd->owner);

}5.4

v4l2_subdev_xxx_ops:

每个v4l2_subdev结构包含了若干函数指针,子设备驱动可以实现这些函数(或者让它为NULL,如果不用到它的话)。这些函数指针已经按照分类排序,并且每个分类具有它自己的ops结构体。顶层的(top-level)的ops结构体包含了到分类ops结构体的指针,它们可以是NULL,如果子设备驱动(subdev

driver)不支持这个分类的功能。

点击(此处)折叠或打开

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;   /*video设备*/

};

下面我们主要来分析v4l2_subdev_ops结构指针中的core和video结构指针,核心ops(core_ops)是所有子设备(subdevs)公用的,其他分类的ops是否使用取决于子设备。例如,一个视频设备不太可能去支持一个audio

ops和vice

versa;

点击(此处)折叠或打开

struct v4l2_subdev_core_ops {

int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);

int (*init)(struct v4l2_subdev *sd, u32 val);

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

long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);

...

};

上面这个结构是我们在摄像头sensor模组驱动中要实现的精华部分,操作摄像头sensor就是通过这些接口来完成的,当然上面这些只是v4l2_subdev_core_ops一部分成员,下面结合v4l2子系统中的一些结构来简单的学习一下这些函数指针的使用

5.5.1

chip identifier

点击(此处)折叠或打开

struct v4l2_dbg_chip_ident {

struct v4l2_dbg_match match;

__u32 ident; /* chip identifier as specified in */

__u32 revision; /* chip revision, chip specific 0 */

} __attribute__ ((packed));

match:用于匹配,如果sensor是i2c设备那么里面保存着该设备的地址和名字,通过i2c地址以及设备名字来匹配

ident:我们每移植一款sensor设备,都需要在media/v4l2-chip-ident.h文件中声明一个唯一的标识符,用来说明你这套代码支持该sensor

revision:0

通过VIDIOC_DBG_G_CHIP_IDENT命令来枚举该结构int

(*g_chip_ident)函数指针的通用实现如下:

点击(此处)折叠或打开

static int sensor_g_chip_ident(struct v4l2_subdev *sd,

struct v4l2_dbg_chip_ident *id)

{

struct i2c_client *client = v4l2_get_subdevdata(sd);

/*Match against I2C 7-bit address*/

if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR)

return -EINVAL;

/*sensor设备从地址*/

if (id->match.addr != client->addr)

return -ENODEV;

id->ident = SENSOR_V4L2_IDENT;/*这个就是我们自己添加的*/

id->revision = 0;

return 0;

}

在驱动的初始化阶段,可以通过v4l2_subdev_call(sd,

core, g_chip_ident, id)来回调该函数指针来匹配驱动是否支持该sensor

5.5.2

sensor initialized

每个v4l2_subdev设备都需要初始化,调过摄像头的朋友都知道,没个sensor都有N多的初始化代码,这些初始化信息就是由camera

host通过v4l2_subdev_call(sd,core,

init,

0)来回调init函数指针将初始化代码段写进sensor芯片内,这些初始化信息包括sensor所支持的功能,如亮度调节,白平衡,聚焦,照片的像素等等的一系列信息。当然这部分信息一般由FAE提供。

5.5.3

v4l2 control

对于相机有很多的功能,我们需要对它们进行操作,比如说调焦,白平衡,效果调节等等.用户空间程序中通过VIDIOC_QUERYCTRL命令来枚举下面结构,获取可用的控制操作相应的由驱动程序中的vidioc_queryctrl()方法来实现用户空间的ioctl操作。该函数指针的原型如下:include/media/v4l2-ioctl.h

点击(此处)折叠或打开

int (*vidioc_queryctrl) (struct file *file, void *fh,

struct v4l2_queryctrl *a);

v4l2_queryctrl结构的定义如下:include/linux/video2.h

点击(此处)折叠或打开

/* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */

struct v4l2_queryctrl {

__u32         id;

enum v4l2_ctrl_type type;

__u8         name[32];    /* Whatever */

__s32         minimum;    /* Note signedness */

__s32         maximum;

__s32         step;

__s32         default_value;

__u32 flags;

__u32         reserved[2];

};

这个结构包含了许多字段,下面一个一个分析

id:每一个功能都有一个唯一的ID,它们都形如V4L2_CID_XXX,定义在include/linux/video2.h中,比较常见的有如下几种

V4L2_CID_DO_WHITE_BALANCE/*白平衡*/

V4L2_CID_EFFECT/*效果*/

V4L2_CID_FLASH/*闪光灯*/

V4L2_CID_BRIGHTNESS/*亮度*/

V4L2_CID_EXPOSURE/*曝光*/

V4L2_CID_SATURATION/*色彩饱和度*/

V4L2_CID_CONTRAST/*对比度*/

V4L2_CID_HFLIP/*水平镜像*/

V4L2_CID_VFLIP/*垂直镜像*/

V4L2_CID_SCENE/*场景,白天黑夜*/

V4L2_CID_FOCUS_AUTO/*自动聚焦*/

V4L2_CID_FOCUS_RELATIVE/*相对聚焦*/

V4L2_CID_FOCUS_ABSOLUTE/*绝对聚焦*/

V4L2_CID_FOCUS_CONTINUOUS/*持续聚焦*/

V4L2_CID_ZOOM_RELATIVE/*数字变焦*/

V4L2_CID_ZOOM_ABSOLUTE

name:控制名字如Focus

Control只是一个标识,可以随意填\

minimum:支持调节的最小值

maximum:支持调节的最大值

step:每次调节的步进

default_value:默认值在minimum和maximum之间(对整型、布尔型和菜单控制适用)

type:该字段表明了一组固定的选项,针对没一组固定的选项定义了如下枚举结构

点击(此处)折叠或打开

enum v4l2_ctrl_type {

V4L2_CTRL_TYPE_INTEGER     = 1,

V4L2_CTRL_TYPE_BOOLEAN     = 2,

V4L2_CTRL_TYPE_MENU          = 3,    /*菜单型的*/

V4L2_CTRL_TYPE_BUTTON     = 4,

V4L2_CTRL_TYPE_INTEGER64 = 5,

V4L2_CTRL_TYPE_CTRL_CLASS = 6,

V4L2_CTRL_TYPE_STRING = 7,

};

flags:表示控制的标志,有如下定义

V4L2_CTRL_FLAG_DISABLED/*控制操作不可用,应用应忽略它*/

V4L2_CTRL_FLAG_GRABBED/*控制暂时不可变,可能是因为另一个应用正在使用*/

V4L2_CTRL_FLAG_READ_ONLY

/*可以查看,但不可以操作*/

V4L2_CTRL_FLAG_UPDATE

/*调整这个参数可以会对其他控指造成影响*/

V4L2_CTRL_FLAG_INACTIVE

/*与当前设备配置无关的操作*/

V4L2_CTRL_FLAG_SLIDER

/*暗示应用在使用这个操作的时候可以使用类似滚动条的接口*/

V4L2_CTRL_FLAG_WRITE_ONLY

当然该结构可以代表那么多的功能是用id字段来区分的,对于菜单型的诸多控制操作来说(type=V4L2_CTRL_TYPE_MENU)用户空间程序可以使用VIDIOC_QUERYMENU命令通过驱动程序当中的vidioc_querymenu函数来完成相应的ioctl操作

点击(此处)折叠或打开

int (*vidioc_querymenu)(struct file *file, void *fh,

struct v4l2_querymenu *a);

struct

v4l2_querymenu结构的定义如下:

点击(此处)折叠或打开

/* Used in the VIDIOC_QUERYMENU ioctl for querying menu items */

struct v4l2_querymenu {

__u32        id;

__u32        index;

__u8        name[32];    /* Whatever */

__u32        reserved;/*永远都等于0*/

};

id:相关控制菜单的ID值和上面v4l2_queryctrl结构中的ID是保持一致的这更进一步表明了可用控制的遍历过程

index:菜单ID值的索引(每打开一个菜单是不是有很长的一列?就是这个),从0开始一直到上面那个结构定义的maximum字段为止

name:由驱动程序填充(并出现在菜单中,比如android系统中的闪光灯中的auto,on,off..)

到这里应用程序已经遍历到驱动中支持的可用操作了,现在我们可以对它进行查询已经操作了,对于这些操作v4l2定义了如下结构:

点击(此处)折叠或打开

/*

*    include/linux/video2.h

*/

struct v4l2_control {

__u32         id;

__s32         value;

};

现在假设我们要查询某一个可用操作,当然这是由用户空间通过VIDIOC_G_CTRL命令发出的,在我们的驱动程序中由vidioc_g_ctrl函数指针来响应这个命令,最后回调v4l2_subdev中的函数指针g_ctrl来实现这个命令(再回首5.1中的那个pos),这两个函数指针的原型分别是:

点击(此处)折叠或打开

int (*vidioc_g_ctrl)(struct file *file, void *fh, struct v4l2_control *a);

int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);

从它们的原型我们可以知道,用户空间通过对设备文件节点的操作,最后转化到实际硬件的操作,懂linux的朋友一定很清晰,如果查询的操作不存在将返回-EINVAL,如果查询成功则返回当前控制设定的值.

如果我们试图在上层通过按钮试图改变当前的控制,比如说把曝光调大或小一点,则在用户空间通过发送VIDIOC_S_CTRL命令请求来实现,在驱动中对应的vidioc_s_ctrl函数指针首先相应该请求,最后通过会调用v4l2_subdev中对应的s_ctrl函数指针来操作相应的sensor硬件,它们的原型分别是:

点击(此处)折叠或打开

int (*vidioc_s_ctrl)(struct file *file, void *private_data,

struct v4l2_control *ctrl);

int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);

对于具体的操作我们可以查看V4L2应用API官方文档:这些ioctl属于user

contrl,除此之外还有其他一些扩展控制,扩展控制可以同时原子的对多个ID进行控制,在用户空间程序中使用ioctl配套的三个命令如下:VIDIOC_G_EXT_CTRLS,

VIDIOC_S_EXT_CTRLS, VIDIOC_TRY_EXT_CTRLS如果操作成功则返回0否则返回EINVAL,驱动中对应的v4l2_device函数指针如下:

点击(此处)折叠或打开

int (*vidioc_g_ext_ctrls) (struct file *file, void *fh,

struct v4l2_ext_controls *a);

int (*vidioc_s_ext_ctrls) (struct file *file, void *fh,

struct v4l2_ext_controls *a);

int (*vidioc_try_ext_ctrls) (struct file *file, void *fh,

struct v4l2_ext_controls *a);

驱动中对应的v4l2_subdev中的ops函数指针如下:

点击(此处)折叠或打开

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

struct

v4l2_ext_controls结构的应以如下:include/linux/video2.h

点击(此处)折叠或打开

struct v4l2_ext_controls {

__u32 ctrl_class;

__u32 count;

__u32 error_idx;

__u32 reserved[2];//0

struct v4l2_ext_control *controls;

};

ctrl_class:控制类,每一种类代表一种特性,如CLASS_USER,CLASS_MPEG等等

count:controls数组的个数

controls:指向struct

v4l2_ext_control结构

对于__u32

ctrl_class字段有如下定义

点击(此处)折叠或打开

/* Values for ctrl_class field */

V4L2_CTRL_CLASS_USER     /*'user' controls VIDIOC_G_CTRL和VIDIOC_S_CTRL属于该范畴*/

V4L2_CTRL_CLASS_MPEG         /*MPEG-compression controls */

V4L2_CTRL_CLASS_CAMERA     /* Camera class controls */

V4L2_CTRL_CLASS_FM_TX     /* FM Modulator control class */

V4L2_CTRL_CLASS_JPEG          /*JPEG compression controls*/

V4L2_CTRL_CLASS_IMAGE_SOURCE     /*Image source controls*/

V4L2_CTRL_CLASS_IMAGE_PROC    /*Image processing controls*/

具体的意思可以通过V4L2提供的官方文档查看:

struct

v4l2_ext_control结构的定义如下:

点击(此处)折叠或打开

struct v4l2_ext_control {

__u32 id;

__u32 size;

__u32 reserved2[1];/*0*/

union {

__s32 value;

__s64 value64;

char *string;

};

} __attribute__ ((packed));

id:Identifies

the control,和上面结构中的ID一样

value:就是要控制值应该和v4l2_querymenu结构中的index字段保持一致

上面这些控制都是一些功能特效的控制,属于基本控制.对于subdev还有许多的内容需要修改,比如流控等,一些数据的格式,这需要和camera

host配合控制这将在后面再进行说明

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值