2.2.7 V4L2 sub-devices

许多驱动程序需要与子设备通信。这些设备可以执行各种任务,但最常见的是处理音频和/或视频复用、编码或解码。对于网络摄像头,常见的子设备是传感器和相机控制器。
通常,这些都是I2C设备,但不一定。为了为驱动程序提供与这些子设备的一致接口,创建了v4l2_subdev结构体(v4l2-subdev.h)。
每个子设备驱动程序必须具有v4l2_subdev结构体。对于简单的子设备,该结构可以是独立的,或者如果需要存储更多状态信息,则可以嵌入到较大的结构体中。通常有一个低级设备结构(例如i2c_client),其中包含由内核设置的设备数据。建议使用v4l2_set_subdevdata()将该指针存储在v4l2_subdev的私有数据中。这使得从v4l2_subdev到实际的特定总线低级设备数据变得容易。
还需要一种从低级结构体到v4l2_subdev的方法。对于常见的i2c_client结构,使用i2c_set_clientdata()调用来存储v4l2_subdev指针,对于其他总线,您可能需要使用其他方法。
桥接器可能还需要存储每个子设备的私有数据,例如指向桥接器特定的每个子设备的私有数据的指针。v4l2_subdev结构提供了主机私有数据,可以通过v4l2_get_subdev_hostdata()和v4l2_set_subdev_hostdata()访问。
从桥接驱动程序的角度来看,您需要加载子设备模块并以某种方式获取v4l2_subdev指针。对于i2c设备,这很容易:您调用i2c_get_clientdata()。对于其他总线,需要进行类似的操作。为在I2C总线上的子设备存在辅助函数,可以为您完成大部分复杂的工作。
每个v4l2_subdev包含函数指针,子设备驱动程序可以实现(如果不适用,则保留为NULL)。由于子设备可以执行许多不同的操作,而您不希望最终得到一个包含大量仅有少数常见操作的巨大ops结构体,因此根据不同的操作类别对函数指针进行排序,每个类别都有自己的ops结构体。顶级ops结构体包含指向类别ops结构体的指针,如果子设备驱动程序不支持该类别的任何操作,则该指针可能为NULL。
它看起来像这样:

struct v4l2_subdev_core_ops {
int (*log_status)(struct v4l2_subdev *sd);
int (*init)(struct v4l2_subdev *sd, u32 val);
...
};
struct v4l2_subdev_tuner_ops {
...
};
struct v4l2_subdev_audio_ops {
...
};
struct v4l2_subdev_video_ops {
...
};
struct v4l2_subdev_pad_ops {
...
};
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;
const struct v4l2_subdev_pad_ops *video;
};

core ops对所有子设备都是通用的,其他类别的实现取决于子设备。例如,视频设备不太可能支持音频操作,反之亦然。这种设置限制了函数指针的数量,同时使添加新操作和类别变得容易。
子设备驱动程序使用以下命令初始化v4l2_subdev结构:
v4l2_subdev_init (sd, &ops)。
此后,您需要使用唯一名称初始化sd->name,并设置模块所有者。如果使用i2c辅助函数,则会为您完成此操作。
如果需要与媒体框架集成,则必须通过调用media_entity_pads_init()来初始化嵌入在v4l2_subdev结构中的media_entity结构(实体字段),如果实体具有端口:

struct media_pad *pads = &my_sd->pads;
int err;
err = media_entity_pads_init(&sd->entity, npads, pads);

pads数组必须先前初始化过。无需手动设置struct media_entity函数和名称字段,但如果需要,则必须初始化revision字段。
当打开/关闭子设备设备节点(如果有)时,实体的引用将自动获取/释放。
在销毁子设备之前,不要忘记清理媒体实体:

media_entity_cleanup(&sd->entity);

如果子设备驱动程序实现了接收端口,子设备驱动程序可以设置v4l2_subdev_pad_ops中的link_validate字段,以提供自己的链接验证函数。对于管道中的每个链接,调用链接结束处的接收端口操作的link_validate pad操作。在这两种情况下,驱动程序仍需负责验证子设备和视频节点之间格式配置的正确性。
如果未设置link_validate op,则默认函数v4l2_subdev_link_validate_default()将代替。此函数确保链接源和接收端口上的宽度、高度和媒体总线像素代码相等。子设备驱动程序也可以自由使用此函数执行上述检查以及他们自己的检查。
2.2.7.1 Subdev registration
目前,有两种将子设备注册到V4L2 core的方法。第一种(传统的)可能性是由桥接驱动程序注册子设备。当桥接驱动程序具有连接到其上的子设备的完整信息并且知道何时注册它们时,可以执行此操作。这通常适用于SoC内部的视频数据处理单元或复杂的PCI(e)板、USB相机中的摄像头传感器或连接到SoC中的情况,它们向桥接驱动程序传递有关它们的信息,通常在其平台数据中。
但是,在某些情况下,子设备必须异步注册到桥接设备。这种配置的示例是基于设备树的系统,其中与子设备相关的信息独立于桥接设备提供系统,例如,在DT中将子设备定义为I2C设备节点时。在第二种情况下使用的API将在下面进一步描述。
使用任何一种注册方法都只会影响探测过程,两种情况下运行时桥接-子设备的交互方式是相同的。
在同步情况下,设备(桥接)驱动程序需要使用以下代码将`v4l2_subdev`注册到`v4l2_device`:

v4l2_device_register_subdev(v4l2_dev, sd)

如果在注册之前子设备模块消失,则此操作可能失败。成功调用此函数后,`subdev->dev`字段将指向`v4l2_device`。
如果`v4l2_device`父设备具有非空`mdev`字段,则子设备实体将自动在媒体设备中注册。
您可以使用以下代码取消注册子设备:

v4l2_device_unregister_subdev(sd)

取消注册后,子设备将从`v4l2_device`注销,`sd->dev`变为`NULL`。这意味着可以安全地卸载子设备模块。重要的是在卸载其模块之前正确注销子设备,以防止任何潜在的问题或冲突。
在异步情况下,子设备探测可以独立于桥接驱动的可用性进行调用。然后,子设备驱动程序必须验证是否满足成功探测的所有要求。其中可能包括检查主时钟是否可用。如果其中任何一个条件不满足,驱动程序可能会决定返回`-EPROBE_DEFER`,请求进一步的探测尝试。一旦满足所有条件,应使用`v4l2_async_register_subdev()`函数注册子设备。取消注册通过调用`v4l2_async_unregister_subdev()`完成。以这种方式注册的子设备将存储在全局子设备列表中,准备由桥接驱动程序选择。
桥接驱动程序必须注册通知对象。这是通过调用`v4l2_async_nf_register()`来完成的。取消注册通知程序需要调用`v4l2_async_nf_unregister()`。前面两个函数都采用两个参数:指向`struct v4l2_device`的指针和指向`struct v4l2_async_notifier`的指针。
在注册通知程序之前,桥接驱动程序必须完成两件事:首先,必须使用`v4l2_async_nf_init()`初始化通知程序。其次,桥接驱动程序可以开始形成子设备描述符列表,该列表是桥接设备运行所需的。根据设备类型和驱动程序的需求,可以使用多个函数将子设备描述符添加到通知程序中。
`v4l2_async_nf_add_fwnode_remote()`和`v4l2_async_nf_add_i2c()`用于桥接和ISP驱动程序,用于向通知程序注册其异步子设备。
`v4l2_async_register_subdev_sensor()`是一个助手函数,用于传感器驱动程序注册自己的异步子设备,但它还会注册通知程序,并为固件中找到的镜头和闪光灯设备进一步注册异步子设备。子设备的通知程序与异步子设备一起取消注册。
这些函数分配一个异步子设备描述符,其类型为嵌入到特定于驱动程序的结构体中的`struct v4l2_async_subdev`。结构体的`&struct v4l2_async_subdev`应该是该结构体的第一个成员:

struct my_async_subdev {
struct v4l2_async_subdev asd;
...
};
struct my_async_subdev *my_asd;
struct fwnode_handle *ep;
...
my_asd = v4l2_async_nf_add_fwnode_remote(&notifier, ep,
struct my_async_subdev);
fwnode_handle_put(ep);
if (IS_ERR(asd))
return PTR_ERR(asd);

接下来,V4L2 core将使用这些描述符异步匹配已注册的子设备。如果检测到匹配,则会调用`.bound()`通知回调函数。在定位所有子设备之后,将调用`.complete()`回调函数。当从系统中删除子设备时,将调用`.unbind()`方法。这三个回调都是可选的。
2.2.7.2 Calling subdev operations
使用`v4l2_subdev`的优点是它是一个通用的结构体,不包含任何有关底层硬件的知识。因此,驱动程序可能包含几个使用I2C总线的子设备,但也可能包含通过GPIO引脚控制的子设备。这种区别只有在设置设备时才是相关的,但是一旦子设备被注册,它就是完全透明的。
一旦子设备已注册,您可以直接调用ops函数:

err = sd->ops->core->g_std(sd, &norm);

但最好最简单的方法是使用这个宏:

err = v4l2_subdev_call(sd, core, g_std, &norm);

该宏将执行正确的NULL指针检查,并返回-ENODEV(如果sd为空)、-ENOIOCTLCMD(如果sd->core或sd->core->g_std为空),或实际结果sd->ops->core->g_std ops。
还可以调用所有或子设备的子集:

v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm);

不支持此操作的任何子设备都将被跳过,并且错误结果将被忽略。如果要检查错误,请使用此代码:

err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm);

除了-ENOIOCTLCMD之外的任何错误都会导致退出循环并显示该错误。如果没有错误(除了-ENOIOCTLCMD之外),则返回0。
这两个调用的第二个参数是一个组ID。如果是0,那么将调用所有子设备。如果非零,则只调用其组ID与该值匹配的子设备。在桥接驱动程序注册子设备之前,它可以将sd->grp_id设置为任何值(默认为0)。该值由桥接驱动程序拥有,子设备驱动程序永远不会修改或使用它。
组ID赋予了桥接驱动程序更多控制回调如何被调用的能力。例如,板子上可能有多个音频芯片,每个芯片都可以改变音量。但通常只有一个芯片会在用户要改变音量时实际使用。您可以将该子设备的组ID设置为例如AUDIO_CONTROLLER,并在调用v4l2_device_call_all()时指定该组ID值。这确保它只会去到需要它的子设备处。
如果子设备需要通知其v4l2_device父级发生的事件,则可以调用v4l2_subdev_notify(sd, notification, arg)。此宏会检查是否定义了notify()回调函数,如果没有,则返回-ENODEV。否则,返回notify()调用的结果。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值