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 <media/v4l2-chip-ident.h> */
- __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];
- };
这个结构包含了许多字段,下面一个一个分析
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,
- };
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));
上面这些控制都是一些功能特效的控制,属于基本控制.对于subdev还有许多的内容需要修改,比如流控等,一些数据的格式,这需要和camera
host配合控制这将在后面再进行说明