linux V4L2子系统——v4l2架构(4)之v4l2_subdev
V4L2从设备使用struct v4l2_subdev结构体表示。一个V4L2主设备可能对应多个V4L2从设备,所有主设备对应的从设备都挂到v4l2_device结构体的subdevs链表中。对于视频设备,从设备就是摄像头,通常情况下是I2C设备,主设备可通过I2C总线控制从设备,例如控制摄像头的焦距、闪光灯等。...
linux V4L2子系统——v4l2架构(4)之v4l2_subdev
备注:
1. Kernel版本:5.4
2. 使用工具:Source Insight 4.0
3. 参考博客:
(1)Linux V4L2子系统分析(一)
(2)linux v4l2 学习之-v4l2设备注册过程及各个设备之间的联系
文章目录
概述
V4L2从设备使用struct v4l2_subdev结构体表示。一个V4L2主设备可能对应多个V4L2从设备,所有主设备对应的从设备都挂到v4l2_device结构体的subdevs链表中。对于视频设备,从设备就是摄像头,通常情况下是I2C设备,主设备可通过I2C总线控制从设备,例如控制摄像头的焦距、闪光灯等。
主要结构体介绍
详见:linux V4L2子系统——v4l2的结构体(3)之v4l2_subdev
v4l2_subdev初始化
使用 v4l2_subdev_init 初始化v4l2_subdev结构体。然后必须用一个唯一的名字初始化 subdev->name,同时初始化模块的owner域。若从设备是I2C设备,则可使用 v4l2_i2c_subdev_init 函数进行初始;若从设备是SPI设备,则可使用 v4l2_spi_subdev_init 函数进行初始化。该函数内部会调用 v4l2_subdev_init ,同时设置flags、owner、dev、name等成员。
以从设备是I2C设备为例——v4l2_i2c_subdev_init:
// 源码: drivers/media/i2c/v4l2-i2c.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); // 初始化 v4l2_subdev
sd->flags |= V4L2_SUBDEV_FL_IS_I2C; // 配置 I2C flag
/* the owner is the same as the i2c_client's driver owner */
sd->owner = client->dev.driver->owner;
sd->dev = &client->dev;
/* i2c_client and v4l2_subdev point to one another */
v4l2_set_subdevdata(sd, client);
i2c_set_clientdata(client, sd);
// 配置 v4l2_subdev 设备名
v4l2_i2c_subdev_set_name(sd, client, NULL, NULL);
}
EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_init);
// 源码: drivers/media/v4l2-core/v4l2-subdev.c
void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops)
{
INIT_LIST_HEAD(&sd->list); // 初始化 v4l2_subdev 挂载链表
BUG_ON(!ops);
sd->ops = ops; // 初始化 v4l2_subdev_ops
sd->v4l2_dev = NULL;
sd->flags = 0;
sd->name[0] = '\0';
sd->grp_id = 0; // 设置默认组为0
sd->dev_priv = NULL;
sd->host_priv = NULL;
#if defined(CONFIG_MEDIA_CONTROLLER)
sd->entity.name = sd->name;
sd->entity.obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV;
sd->entity.function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
#endif
}
EXPORT_SYMBOL(v4l2_subdev_init);
若需同媒体框架整合,必须调用 media_entity_init 初始化 v4l2_subdev 结构体中的 media_entity 结构体(entity域)。pads数组必须预先初始化。无须手动设置media_entity的type和name域,但如有必要,revision域必须初始化。当(任何)从设备节点被打开/关闭,对entity的引用将被自动获取/释放。在从设备被注销之后,使用 media_entity_cleanup 清理 media_entity 结构体。
如果从设备驱动趋向于处理视频并整合进了媒体框架,必须使用 v4l2_subdev_pad_ops 替代v4l2_subdev_video_ops 实现格式相关的功能。这种情况下,子设备驱动应该设置 link_validate 域,以提供它自身的链接验证函数。链接验证函数应对管道(两端链接的都是V4L2从设备)中的每个链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。如果link_validate操作没有设置,默认的 v4l2_subdev_link_validate_default 函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行上面提到的检查。
v4l2_subdev注册与释放
v4l2_device_register_subdev()注册过程
//源码:divers/media/v4l2-core/v4l2-device.c
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
struct v4l2_subdev *sd)
{
#if defined(CONFIG_MEDIA_CONTROLLER)
//获取到子设备的入口对象,方面后面注册到media_device上面
struct media_entity *entity = &sd->entity;
#endif
int err;
/* Check for valid input */
if (!v4l2_dev || !sd || sd->v4l2_dev || !sd->name[0])
return -EINVAL;
/*
* The reason to acquire the module here is to avoid unloading
* a module of sub-device which is registered to a media
* device. To make it possible to unload modules for media
* devices that also register sub-devices, do not
* try_module_get() such sub-device owners.
*/
sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver &&
sd->owner == v4l2_dev->dev->driver->owner;
if (!sd->owner_v4l2_dev && !try_module_get(sd->owner))
return -ENODEV;
//绑定v4l2_dev,下面v4l2_dev一般为系统根v4l2_device设备。
sd->v4l2_dev = v4l2_dev;
/* This just returns 0 if either of the two args is NULL */
// 印证了之前说的,子设备的ctrl_handler都会
// 挂载到根设备v4l2_device的ctrl_handler上面
err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler,
NULL, true);
if (err)
goto error_module;
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Register the entity. */
if (v4l2_dev->mdev) {
err = media_device_register_entity(v4l2_dev->mdev, entity);
if (err < 0)
goto error_module;
}
#endif
// 这里对应具体的子设备,可以发现调用了registered()回调,
// 如果有需要可以在对应的设备驱动中实现
if (sd->internal_ops && sd->internal_ops->registered) {
err = sd->internal_ops->registered(sd);
if (err)
goto error_unregister;
}
spin_lock(&v4l2_dev->lock);
// 将子设备链接到跟设备的,subdevs链表上
list_add_tail(&sd->list, &v4l2_dev->subdevs);
spin_unlock(&v4l2_dev->lock);
return 0;
error_unregister:
#if defined(CONFIG_MEDIA_CONTROLLER)
media_device_unregister_entity(entity);
#endif
error_module:
if (!sd->owner_v4l2_dev)
module_put(sd->owner);
sd->v4l2_dev = NULL;
return err;
}
-
- 获取到子设备的entity对象,并将当前根设备对象赋值给subdev->v4l2_dev域。
-
- 将子设备的ctrl_handler对象,添加到根设备的v4l2_dev->ctrl_handler域。
-
- 将子设备的entity注册到根设备上的media_device设备中。
-
- 将当前 v4l2_subdev 添加到 v4l2_dev 中的 subdevs 链表中。
v4l2_device_register_subdev_nodes()注册设备节点
//源码:divers/media/v4l2-core/v4l2-device.c
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
{
struct video_device *vdev;
struct v4l2_subdev *sd;
int err;
/* Register a device node for every subdev marked with the
* V4L2_SUBDEV_FL_HAS_DEVNODE flag.
*/
// 遍历 v4l2_dev 下的 subdevs 链表,查找 subdev
list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
// 检查当前 subdev 是否有创建设备节点的标志
if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
continue;
// 当前 subdev 是否已创建设备节点
if (sd->devnode)
continue;
// 申请 video_device
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
if (!vdev) {
err = -ENOMEM;
goto clean_up;
}
// 初始化 video_device
video_set_drvdata(vdev, sd);
strscpy(vdev->name, sd->name, sizeof(vdev->name));
vdev->dev_parent = sd->dev;
vdev->v4l2_dev = v4l2_dev;
vdev->fops = &v4l2_subdev_fops;
vdev->release = v4l2_device_release_subdev_node;
vdev->ctrl_handler = sd->ctrl_handler;
// 注册 video_device,类型为 VFL_TYPE_SUBDEV
err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
sd->owner);
if (err < 0) {
kfree(vdev);
goto clean_up;
}
sd->devnode = vdev;
#if defined(CONFIG_MEDIA_CONTROLLER)
sd->entity.info.dev.major = VIDEO_MAJOR;
sd->entity.info.dev.minor = vdev->minor;
/* Interface is created by __video_register_device() */
if (vdev->v4l2_dev->mdev) {
struct media_link *link;
link = media_create_intf_link(&sd->entity,
&vdev->intf_devnode->intf,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (!link) {
err = -ENOMEM;
goto clean_up;
}
}
#endif
}
return 0;
clean_up:
list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
if (!sd->devnode)
break;
video_unregister_device(sd->devnode);
}
return err;
}
v4l2_subdev释放过程
//源码:divers/media/v4l2-core/v4l2-device.c
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;
spin_lock(&v4l2_dev->lock);
list_del(&sd->list); // 移除当前 subdev 节点
spin_unlock(&v4l2_dev->lock);
if (sd->internal_ops && sd->internal_ops->unregistered)
sd->internal_ops->unregistered(sd);
sd->v4l2_dev = NULL;
#if defined(CONFIG_MEDIA_CONTROLLER)
if (v4l2_dev->mdev) {
/*
* No need to explicitly remove links, as both pads and
* links are removed by the function below, in the right order
*/
media_device_unregister_entity(&sd->entity);
}
#endif
// 若注册 video_device 设备节点,则unregister video_device
if (sd->devnode)
video_unregister_device(sd->devnode);
else
v4l2_subdev_release(sd); // 释放 subdev
}
示例——OV7251
//源码:drivers/media/i2c/ov7251.c
static int ov7251_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct fwnode_handle *endpoint;
struct ov7251 *ov7251;
u8 chip_id_high, chip_id_low, chip_rev;
int ret;
ov7251 = devm_kzalloc(dev, sizeof(struct ov7251), GFP_KERNEL);
if (!ov7251)
return -ENOMEM;
......
// 初始化 ctrl_handler
v4l2_ctrl_handler_init(&ov7251->ctrls, 7);
ov7251->ctrls.lock = &ov7251->lock;
v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
V4L2_CID_HFLIP, 0, 1, 1, 0);
v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
V4L2_CID_VFLIP, 0, 1, 1, 0);
ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
V4L2_CID_EXPOSURE, 1, 32, 1, 32);
ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
V4L2_CID_GAIN, 16, 1023, 1, 16);
v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops,
V4L2_CID_TEST_PATTERN,
ARRAY_SIZE(ov7251_test_pattern_menu) - 1,
0, 0, ov7251_test_pattern_menu);
ov7251->pixel_clock = v4l2_ctrl_new_std(&ov7251->ctrls,
&ov7251_ctrl_ops,
V4L2_CID_PIXEL_RATE,
1, INT_MAX, 1, 1);
ov7251->link_freq = v4l2_ctrl_new_int_menu(&ov7251->ctrls,
&ov7251_ctrl_ops,
V4L2_CID_LINK_FREQ,
ARRAY_SIZE(link_freq) - 1,
0, link_freq);
if (ov7251->link_freq)
ov7251->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
ov7251->sd.ctrl_handler = &ov7251->ctrls;
if (ov7251->ctrls.error) {
dev_err(dev, "%s: control initialization error %d\n",
__func__, ov7251->ctrls.error);
ret = ov7251->ctrls.error;
goto free_ctrl;
}
v4l2_i2c_subdev_init(&ov7251->sd, client, &ov7251_subdev_ops);
ov7251->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
ov7251->pad.flags = MEDIA_PAD_FL_SOURCE;
ov7251->sd.dev = &client->dev;
ov7251->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
ret = media_entity_pads_init(&ov7251->sd.entity, 1, &ov7251->pad);
if (ret < 0) {
dev_err(dev, "could not register media entity\n");
goto free_ctrl;
}
......
// 异步注册 v4l2_subdev
ret = v4l2_async_register_subdev(&ov7251->sd);
if (ret < 0) {
dev_err(dev, "could not register v4l2 device\n");
goto free_entity;
}
ov7251_entity_init_cfg(&ov7251->sd, NULL);
return 0;
......
return ret;
}
//源码:drivers/media/i2c/ov7251.c
static const struct v4l2_subdev_core_ops ov7251_core_ops = {
.s_power = ov7251_s_power,
};
static const struct v4l2_subdev_video_ops ov7251_video_ops = {
.s_stream = ov7251_s_stream,
.g_frame_interval = ov7251_get_frame_interval,
.s_frame_interval = ov7251_set_frame_interval,
};
static const struct v4l2_subdev_pad_ops ov7251_subdev_pad_ops = {
.init_cfg = ov7251_entity_init_cfg,
.enum_mbus_code = ov7251_enum_mbus_code,
.enum_frame_size = ov7251_enum_frame_size,
.enum_frame_interval = ov7251_enum_frame_ival,
.get_fmt = ov7251_get_format,
.set_fmt = ov7251_set_format,
.get_selection = ov7251_get_selection,
};
static const struct v4l2_subdev_ops ov7251_subdev_ops = {
.core = &ov7251_core_ops,
.video = &ov7251_video_ops,
.pad = &ov7251_subdev_pad_ops,
};
更多推荐
所有评论(0)