【V4L2】 v4l2框架分析之v4l2_subdev

文章目录

    • 一、v4l2_subdev简介
    • 二、初始化v4l2_subdev
    • 三、注册/注销subdev
    • 四、异步注册子设备

一、v4l2_subdev简介

相关源码文件:

  • /include/media/v4l2-subdev.h
  • /drivers/media/v4l2-core/v4l2-subdev.c

在linux内核中,许多驱动程序需要与子设备通信,这些子设备用于完成一些子任务,最常见的是:处理音频或视频的播放、编码或解码。例如:对于网络摄像机来说,常用的子设备是:传感器摄像机控制器。这些设备通常是I2C设备(但也不全是)。为了给驱动程序提供与这些子设备一致的接口,故创建了v4l2_subdev结构(定义在v4l2-subdev.h文件中),定义如下:

struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity; //指向struct media_entity的指针。
#endif
	struct list_head list;   //子设备链表
	struct module *owner; //子设备模块,其所有者与驱动程序的 struct device所有者相同。
	bool owner_v4l2_dev; //如果sd->owner与v4l2_dev->dev owner的所有者匹配,则为True。由v4l2_device_register_subdev()初始化。
	u32 flags;           //子设备标志。可选值有:V4L2_SUBDEV_FL_IS_I2C  - 如果子设备是i2c设备,设置这个标志;V4L2_SUBDEV_FL_IS_SPI - 如果子设备为spi设备,设置这个标志;V4L2_SUBDEV_FL_HAS_DEVNODE - 如果子设备需要设备节点,则设置这个标志;V4L2_SUBDEV_FL_HAS_EVENTS - 如果子设备生成事件,则设置这个标志。
	struct v4l2_device *v4l2_dev; //指向struct v4l2_device的指针。
	const struct v4l2_subdev_ops *ops; //指向struct v4l2_subdev_ops的指针。v4l2_subdev_ops是子设备的操作结合。
	/* Never call these internal ops from within a driver! */
	const struct v4l2_subdev_internal_ops *internal_ops;  //指向struct v4l2_subdev_internal_ops的指针。在驱动程序中不能调用这些函数指针。
	/* The control handler of this subdev. May be NULL. */
	struct v4l2_ctrl_handler *ctrl_handler; //子设备的control处理程序,可能为NULL。
	/* 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; //指向私有数据的指针
	void *host_priv; //指向连接的子设备所使用的私有数据的指针
	/* subdev device node */
	struct video_device *devnode; //子设备的设备节点。
	/* pointer to the physical device, if any */
	struct device *dev; //指向物理设备的指针(如果有的话)
	/* Links this subdev to a global subdev_list or @notifier->done list. */
	struct list_head async_list; //用于将子设备链接到全局subdev_list或notificer_done列表。
	/* Pointer to respective struct v4l2_async_subdev. */
	struct v4l2_async_subdev *asd; //指向各自struct v4l2_async_subdev的指针。
	/* Pointer to the managing notifier. */
	struct v4l2_async_notifier *notifier; //指向管理通知程序的指针
	/* common part of subdevice platform data */
	struct v4l2_subdev_platform_data *pdata; //子设备平台数据的公共部分。
};

在子设备驱动程序中必须包含一个v4l2_subdev结构。对于简单的子设备驱动程序,可以直接使用struct v4l2_subdev进行描述。但是如果需要存储更多的状态信息,就需要将v4l2_subdev嵌入到更大的结构中,这时候通常会有一个描述底层设备的结构(例如i2c_client),它包含内核设置的设备数据。可以使用v4l2_set_subdevdata()将指向该设备数据的指针存储在v4l2_subdev的私有数据中,通过这种方式,则可以方便从v4l2_subdev访问实际特定总线的底层设备的数据。还需要一种方法从底层结构转移到v4l2_subdev结构。对于公共的i2c_client结构,可以调用i2c_set_clientdata()存储指向v4l2_subdev的指针,对于其他总线,一般也会提供对应的函数完成底层结构到上层结构的关联操作。

每个v4l2_subdev都包含子设备驱动程序可以实现的函数指针struct v4l2_subdev_ops,因为子设备可以做很多事情,所以函数指针根据类别进行排序,每个类别都有自己的ops结构。顶层ops结构包含指向各个类别ops结构的指针,如果子设备驱动程序不支持该类别中的内容,则struct v4l2_subdev_ops可以为NULL。如下代码所示:

struct v4l2_subdev_core_ops {
    int (*log_status)(struct v4l2_subdev *sd);
    int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n, struct v4l2_subdev_io_pin_config *pincfg);
    int (*init)(struct v4l2_subdev *sd, u32 val);
    int (*load_fw)(struct v4l2_subdev *sd);
    int (*reset)(struct v4l2_subdev *sd, u32 val);
    int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
    long (*command)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
    long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
#ifdef CONFIG_COMPAT;
    long (*compat_ioctl32)(struct v4l2_subdev *sd, unsigned int cmd, unsigned long arg);
#endif;
#ifdef CONFIG_VIDEO_ADV_DEBUG;
    int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
    int (*s_register)(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg);
#endif;
    int (*s_power)(struct v4l2_subdev *sd, int on);
    int (*interrupt_service_routine)(struct v4l2_subdev *sd, u32 status, bool *handled);
    int (*subscribe_event)(struct v4l2_subd、
    
    ev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub);
    int (*unsubscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub);
};

struct v4l2_subdev_tuner_ops {
    int (*standby)(struct v4l2_subdev *sd);
    int (*s_radio)(struct v4l2_subdev *sd);
    int (*s_frequency)(struct v4l2_subdev *sd, const struct v4l2_frequency *freq);
    int (*g_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq);
    int (*enum_freq_bands)(struct v4l2_subdev *sd, struct v4l2_frequency_band *band);
    int (*g_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt);
    int (*s_tuner)(struct v4l2_subdev *sd, const struct v4l2_tuner *vt);
    int (*g_modulator)(struct v4l2_subdev *sd, struct v4l2_modulator *vm);
    int (*s_modulator)(struct v4l2_subdev *sd, const struct v4l2_modulator *vm);
    int (*s_type_addr)(struct v4l2_subdev *sd, struct tuner_setup *type);
    int (*s_config)(struct v4l2_subdev *sd, const struct v4l2_priv_tun_config *config);
};

struct v4l2_subdev_audio_ops {
    int (*s_clock_freq)(struct v4l2_subdev *sd, u32 freq);
    int (*s_i2s_clock_freq)(struct v4l2_subdev *sd, u32 freq);
    int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);
    int (*s_stream)(struct v4l2_subdev *sd, int enable);
};

struct v4l2_subdev_video_ops {
    int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);
    int (*s_crystal_freq)(struct v4l2_subdev *sd, u32 freq, u32 flags);
    int (*g_std)(struct v4l2_subdev *sd, v4l2_std_id *norm);
    int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);
    int (*s_std_output)(struct v4l2_subdev *sd, v4l2_std_id std);
    int (*g_std_output)(struct v4l2_subdev *sd, v4l2_std_id *std);
    int (*querystd)(struct v4l2_subdev *sd, v4l2_std_id *std);
    int (*g_tvnorms)(struct v4l2_subdev *sd, v4l2_std_id *std);
    int (*g_tvnorms_output)(struct v4l2_subdev *sd, v4l2_std_id *std);
    int (*g_input_status)(struct v4l2_subdev *sd, u32 *status);
    int (*s_stream)(struct v4l2_subdev *sd, int enable);
    int (*g_pixelaspect)(struct v4l2_subdev *sd, struct v4l2_fract *aspect);
    int (*g_frame_interval)(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *interval);
    int (*s_frame_interval)(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *interval);
    int (*s_dv_timings)(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings);
    int (*g_dv_timings)(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings);
    int (*query_dv_timings)(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings);
    int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf, unsigned int *size);
    int (*pre_streamon)(struct v4l2_subdev *sd, u32 flags);
    int (*post_streamoff)(struct v4l2_subdev *sd);
};

struct v4l2_subdev_pad_ops {
    int (*init_cfg)(struct v4l2_subdev *sd, struct v4l2_subdev_state *state);
    int (*enum_mbus_code)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code);
    int (*enum_frame_size)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, struct v4l2_subdev_frame_size_enum *fse);
    int (*enum_frame_interval)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, struct v4l2_subdev_frame_interval_enum *fie);
    int (*get_fmt)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, struct v4l2_subdev_format *format);
    int (*set_fmt)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, struct v4l2_subdev_format *format);
    int (*get_selection)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel);
    int (*set_selection)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel);
    int (*get_edid)(struct v4l2_subdev *sd, struct v4l2_edid *edid);
    int (*set_edid)(struct v4l2_subdev *sd, struct v4l2_edid *edid);
    int (*dv_timings_cap)(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap);
    int (*enum_dv_timings)(struct v4l2_subdev *sd, struct v4l2_enum_dv_timings *timings);
#ifdef CONFIG_MEDIA_CONTROLLER;
    int (*link_validate)(struct v4l2_subdev *sd, struct media_link *link,struct v4l2_subdev_format *source_fmt, struct v4l2_subdev_format *sink_fmt);
#endif ;
    int (*get_frame_desc)(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_frame_desc *fd);
    int (*set_frame_desc)(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_frame_desc *fd);
    int (*get_mbus_config)(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_config *config);
    int (*set_routing)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state,enum v4l2_subdev_format_whence which, struct v4l2_subdev_krouting *route);
    int (*enable_streams)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, u32 pad, u64 streams_mask);
    int (*disable_streams)(struct v4l2_subdev *sd,struct v4l2_subdev_state *state, u32 pad, u64 streams_mask);
};

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;
};

在上述五种类别的ops中,v4l2_subdev_core_ops对所有的子设备都是通用的,而其他四种ops是否需要实现取决于子设备,例如:视频设备不太可能支持音频设备。这种设置限制了函数指针的数量,也便于添加新的操作和类别。

二、初始化v4l2_subdev

在实际驱动程序中,使用v4l2_subdev_init (sd, &ops)初始化v4l2_subdev结构,v4l2_subdev_init()实现如下:

【V4L2】 v4l2框架分析之v4l2_subdev_第1张图片

如果子设备驱动程序需要与媒体框架集成,即entity有pads字段,还必须调用media_entity_pads_init()来初始化嵌入在v4l2_subdev结构(entity字段)中的media_entity结构:

struct media_pad *pads = &my_sd->pads;
int err;

err = media_entity_pads_init(&sd->entity, npads, pads);

当子设备被销毁时需要使用:

media_entity_cleanup(&sd->entity);

来清除媒体实例。

三、注册/注销subdev

目前有两种向V4L2核心注册子设备的方法。第一种(传统的)是让bridge driver注册子设备。当bridge driver拥有连接到它的子设备的完整信息并且确切知道什么时候注册它们时,就可以完成这一点。这通常是内部子设备的情况,如soc或复杂PCI(e)板中的视频数据处理单元、USB摄像头中的摄像机传感器或连接到soc的摄像机传感器。通常在它们的platform数据中,它们会将有关它们的信息传递给bridge driver

然而,也存在子设备必须异步注册到桥接设备的情况。这种配置的一个例子是基于设备树的系统,其中关于子设备的信息可独立于桥接设备。例如,当子设备在DT中定义为I2C设备节点时。

使用不同的注册方法只会影响.probe过程,在两种情况下运行时,桥-子设备之间的交互是相同的。

在同步情况下,设备(网桥)驱动程序需要向v4l2_device注册v4l2_subdev

v4l2_device_register_subdev (v4l2_dev, sd)

如果子dev模块在注册之前消失,则此操作可能失败,在成功调用v4l2_device_register_subdev()函数后,subdev->dev字段指向v4l2_device。如果v4l2_device父设备有一个非NULL的mdev字段,那么子设备实体将自动注册到媒体设备。

如果需要注销一个子设备,则使用下列函数:

v4l2_device_unregister_subdev (sd).

在调用该函数后,将卸载子设备模块,并且将sd->dev设置为NULL

在异步情况下进行子设备.probe时,可以不依赖于bridge driver是否可用。但是在子设备驱动程序中必须验证是否满足probe的所有条件:
可能包括检查主时钟是否可用,如果任何一个条件不满足,驱动程序可以返回-EPROBE_DEFER以请求尝试重新probe;如果所有的条件满足,接着应该使用v4l2_async_register_subdev()注册子设备,以这种方式注册的子设备存储在一个全局的子设备列表(subdev_list)中,准备由bridge driver拾取:

【V4L2】 v4l2框架分析之v4l2_subdev_第2张图片

bridge driver中必须使用v4l2_async_nf_register()注册一个通知器对象。当不需要通知器时,驱动程序还必须调用v4l2_async_nf_unregister()注销通知器。两个函数中都接收两个参数:一个指向struct v4l2_device的指针和一个指向struct v4l2_async_notifier的指针。

在注册通知器之前,bridge driver必须做两件事:

(1)必须使用v4l2_async_nf_init()初始化通知器。

(2)根据设备的类型和驱动程序的需求,bridge driver可以形成桥接设备运行所需的子设备描述符列表。例如:v4l2_async_nf_add_fwnode_remote()v4l2_async_nf_add_i2c()用于网桥和ISP驱动程序,用于向通知器注册它们的异步子设备。

如果需要注销异步子设备时,需使用v4l2_async_unregister_subdev()取消注册的子设备。

四、异步注册子设备

在早期的内核版本中,V4L2子设备的注册是通过静态定义和手动调用注册函数的方式完成的。然而,这种方法限制了子设备的动态添加和移除能力。所以为了支持更灵活的子设备注册和管理,以及异步通知机制,Linux内核引入了struct v4l2_async_notifier结构体,该结构体和相关的异步通知机制允许驱动程序在运行时动态添加或移除子设备,并通知V4L2核心进行注册或注销。

相关源码文件:

  • /include/media/v4l2-async.h

  • /include/media/v4l2-core/v4l2-async.c

struct v4l2_async_notifier结构定义如下:

struct v4l2_async_notifier {
	const struct v4l2_async_notifier_operations *ops;//异步通知操作的函数指针,用于处理注册和注销子设备的回调函数。
	struct v4l2_device *v4l2_dev;
	struct v4l2_subdev *sd;
	struct v4l2_async_notifier *parent;// 指向父级异步通知器的指针,用于在嵌套异步通知的情况下建立父子关系。
	struct list_head asd_list;
	struct list_head waiting;
	struct list_head done;
	struct list_head list;
};

异步通知机制允许驱动程序在子设备可用时通知V4L2核心进行注册,而不需要在驱动程序初始化阶段进行静态注册。这种机制非常适用于需要动态添加或移除子设备的情况,例如热插拔设备或根据特定条件动态注册设备的应用场景。

异步通知的使用流程如下:

  • (1)初始化异步通知器

通过调用void v4l2_async_nf_init(struct v4l2_async_notifier *notifier);初始化异步通知器。

  • (2)添加子设备

使用v4l2_async_nf_add_fwnode()将子设备添加到异步通知器的子设备列表中。

  • (3)设置异步通知回调

通过设置struct v4l2_async_notifier_operations结构体中的回调函数指针,定义注册和注销子设备时的操作。在注册或注销子设备的过程中,异步通知器的回调函数会被调用,从而通知V4L2核心进行相应的操作。这些回调函数的实现由驱动程序开发人员完成,并根据需要执行必要的操作,如分配资源、初始化子设备、连接管道等。

  • (4)注册异步通知器

调用int v4l2_async_nf_register(struct v4l2_device *v4l2_dev, struct v4l2_async_notifier *notifier);将异步通知器注册到V4L2设备。

  • (5)注销异步通知器

通过调用void v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier);注销异步通知器。

通过使用struct v4l2_async_notifier结构体和相应的函数,在驱动程序中可以实现子设备的异步注册和注销。这为动态管理和控制子设备提供了灵活性和可扩展性。

参考链接:
https://docs.kernel.org/driver-api/media/v4l2-subdev.html

你可能感兴趣的:(小生聊【嵌入式linux】,小生聊【linux,kernel】,linux,V4L2,linux内核,v4l2_subdev,音视频)