传统上,桥接驱动程序向用户空间公开一个或多个视频节点,并根据视频节点操作响应中的v4l2_subdev_ops操作来控制子设备。这将底层硬件的复杂性隐藏在应用程序之后。对于复杂的设备,可能需要比视频节点提供的更细粒度的设备控制。在这些情况下,实现媒体控制器API的桥接驱动程序可能选择直接从用户空间访问子设备操作。
可以在/dev中创建名为v4l-subdevX的设备节点以直接访问子设备。如果子设备支持直接用户空间配置,则必须在注册之前设置V4L2_SUBDEV_FL_HAS_DEVNODE标志。
注册子设备后,v4l2_device驱动程序可以通过调用v4l2_device_register_subdev_nodes()为所有标记有V4L2_SUBDEV_FL_HAS_DEVNODE的已注册子设备创建设备节点。这些设备节点将在注销子设备时自动删除。
设备节点处理V4L2 API的子集:
VIDIOC_QUERYCTRL、VIDIOC_QUERYMENU、VIDIOC_G_CTRL、VIDIOC_S_CTRL、VIDIOC_G_EXT_CTRLS、VIDIOC_S_EXT_CTRLS和VIDIOC_TRY_EXT_CTRLS:这些控件IOCTL与V4L2中定义的控件相同。它们的行为完全相同,唯一的例外是它们仅处理在子设备中实现的控件。根据驱动程序,这些控件也可以通过一个(或多个)V4L2设备节点访问。
VIDIOC_DQEVENT、VIDIOC_SUBSCRIBE_EVENT和VIDIOC_UNSUBSCRIBE_EVENT: 事件IOCTL与V4L2中定义的事件相同。它们的行为完全相同,唯一的例外是它们仅处理由子设备生成的事件。根据驱动程序,这些事件也可以由一个(或多个)V4L2设备节点报告。
要使用事件的子设备驱动程序需要在注册子设备之前设置V4L2_SUBDEV_FL_HAS_EVENTS v4l2_subdev.flags。在注册后,事件可以像往常一样排队在v4l2_subdev.devnode设备节点上。为了正确支持事件,还实现了poll()文件操作。
私有ioctl:所有不在上述列表中的ioctl都将通过core::ioctl操作直接传递给子设备驱动程序。
2.2.9 Read-only sub-device userspace API
通过对内核API的直接调用来控制其连接的子设备的桥接驱动程序,实现了由v4l2_subdev_ops结构体实现。这种情况下,通常不希望用户空间能够通过子设备设备节点更改相同的参数,因此通常不会注册任何节点。
有时,通过一个只读的API向用户空间报告当前子设备配置是有用的,该API不允许应用程序更改设备参数,但允许通过子设备设备节点与之进行交互以进行检查。例如,为了实现基于计算摄影的相机,用户空间需要知道每种支持的输出分辨率的详细摄像机传感器配置(跳跃、截取和缩放)。为了支持这样的用例,桥接驱动程序可以通过只读API向用户空间公开子设备操作。
为所有使用V4L2_SUBDEV_FL_HAS_DEVNODE标记注册的子设备创建一个只读设备节点,v4l2_device驱动程序应调用v4l2_device_register_ro_subdev_nodes()。
在使用v4l2_device_register_ro_subdev_nodes()注册的子设备设备节点上,用户空间应用程序对以下ioctl的访问受到限制:
VIDIOC_SUBDEV_S_FMT、VIDIOC_SUBDEV_S_CROP、VIDIOC_SUBDEV_S_SELECTION: 仅允许在只读的子设备设备节点上针对V4L2_SUBDEV_FORMAT_TRY格式和选择矩形进行使用。
VIDIOC_SUBDEV_S_FRAME_INTERVAL、VIDIOC_SUBDEV_S_DV_TIMINGS、VIDIOC_SUBDEV_S_STD: 不允许在只读子设备节点上进行使用。
如果ioctl不被允许,或要修改的格式设置为V4L2_SUBDEV_FORMAT_ACTIVE,则核心将返回负错误代码,并将errno变量设置为-EPERM。
2.2.10 I2C sub-device drivers
由于这些驱动程序非常普遍,因此可以使用特殊的辅助函数来简化对这些驱动程序的使用(v4l2-common.h)。
将v4l2_subdev结构体嵌入为每个I2C设备实例创建的状态结构体中是将v4l2_subdev支持添加到I2C驱动程序中的推荐方法。非常简单的设备没有状态结构体,在这种情况下,可以直接创建一个v4l2_subdev。
典型的状态结构体如下(其中“chipname”被替换为芯片的名称):
struct chipname_state {
struct v4l2_subdev sd;
... /* additional state fields */
};
按照如下初始化v4l2_subdev:
v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);
这个函数将填充v4l2_subdev的所有字段,并确保v4l2_subdev和i2c_client相互指向。您还应该添加一个帮助程序内联函数,以从v4l2_subdev指针转换为chip-name_state结构体:
static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
{
return container_of(sd, struct chipname_state, sd);
}
使用此函数从v4l2_subdev结构体转换为i2c_client结构体:
struct i2c_client *client = v4l2_get_subdevdata(sd);
使用此函数从i2c_client结构体转换为v4l2_subdev结构体:
struct v4l2_subdev *sd = i2c_get_clientdata(client);
在调用remove()回调函数时,请确保调用v4l2_device_unregister_subdev()(sd)。这将从桥接驱动程序中注销子设备。即使从未注册子设备,也可以安全地调用此函数。
需要这样做的原因是当桥接驱动程序销毁i2c适配器时,会调用该适配器上的i2c设备的remove()回调函数。之后,相应的v4l2_subdev结构将无效,因此必须首先取消注册它们。从remove()回调函数调用v4l2_device_unregister_subdev()(sd)可确保始终正确执行此操作。
桥接驱动程序还有一些可用的帮助函数:
struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,
"module_foo", "chipid", 0x36, NULL);
此函数加载给定的模块(如果不需要加载模块,则可以为NULL),并使用给定的i2c_adapter和芯片/地址参数调用i2c_new_client_device()。如果一切顺利,它将使用v4l2_device注册子设备。
您还可以使用v4l2_i2c_new_subdev()的最后一个参数传递可能要探测的I2C地址数组。仅当前一个参数为0时才使用这些探测地址。非零参数意味着您知道确切的i2c地址,因此在这种情况下不会进行探测。
两个函数如果有问题都将返回NULL。
请注意,您传递给v4l2_i2c_new_subdev()的chipid通常与模块名称相同。它允许您指定芯片变体,例如“saa7114”或“saa7115”。但是通常i2c驱动程序会自动检测此内容。芯片ID的使用需要在稍后更仔细地查看。它在i2c驱动程序之间存在差异,并且可能令人困惑。要查看支持哪些芯片变体,可以在i2c驱动程序代码中查找i2c_device_id表。该表列出了所有可能性。
还有一个辅助函数:
v4l2_i2c_new_subdev_board()使用传递给i2c驱动程序的i2c_board_info结构体,替换irq,platform_data和addr参数。如果子设备支持s_config core ops,则在设置好子设备后将使用irq和platform_data参数调用该操作。
v4l2_i2c_new_subdev()函数将调用v4l2_i2c_new_subdev_board(),在内部使用client_type和addr填充i2c_board_info结构体。