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配合控制这将在后面再进行说明