V4L2源代码之旅一:struct v4l2_subdev

转自:http://www.cnblogs.com/ronnydm/p/5774263.html

大多数的驱动程序需要和sub-devices通信。这些设备可以完成各种任务,但是通常是处理音频或视频的muxing,encoding,decoding。webcams通常子设备是:sensor和camera controllers。通常,他们是I2C设备。为了给这些sub-devices提供一致的驱动接口,结构体v4l2_subdev被创造出来(v4l2-subdev.h)。

  每一个sub-device的驱动程序必须有v4l2_subdev这个结构体。对于简单的sub-devices这个结构体可以独立表示或者如果需要大量的状态信息存储,这个结构体可以嵌入到一个更大的结构体中。通常会有一个包含设备数据的更底层的device struct(如:i2c_client)被安装到kernel中。推荐使用v4l2_set_subdevdata将指针存储为v4l2_subdev的私有数据。这样通过v4l2_subdev就可以更加方便的获取总线相关的设备信息。

  同时还需要从底层结构体到v4l2_subdev的方式。通常,i2c_client结构体的i2c_set_clientdata()函数被用来存储一个v4l2_subdev指针,其他总线需要使用其他方式。

  Bridges可能也需要存储per-subdev的私有数据,例如一个指向bridge相关的per-subdev私有数据指针。v4l2_subdev结构体提供了host private data,为了可以通过v4l2_get_subdev_hostdata() 和 v4l2_set_subdev_hostdata()函数访问。

  从bridge驱动的观点需要加载sub-device模块并且以某种方式获取v4l2_subdev指针。对于i2c设备是容易的:调用i2c_get_clientdata()。对于其他总线需要类似的方式。

  v4l2_subdev包含了sub-device驱动可以实现的函数指针(如果不可用置空)。由于sub-devices可以完成许多不同工作,并且你不想回调函数结构体太大,希望只是仅仅包含少量的可通用实现的回调函数指针。这些回调函数指针依据不同策略分类存储,每种策略有自己的ops struct。上层的ops struct包含一个指向所有策略ops struct的指针。如果部支持某种类型的策略,将其指针置空。

复制代码
/* kernel/include/media/v4l2-subde.h */
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_entity entity;
#endif
    struct list_head list;
    struct module *owner;
    u32 flags;
    struct v4l2_device *v4l2_dev;
    const struct v4l2_subdev_ops *ops;    // 各种策略的ops
    /* Never call these internal ops from within a driver! */
    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;
    void *host_priv;
    /* subdev device node */
    struct video_device devnode;
    /* number of events to be allocated on open */
    unsigned int nevents;
     
};
复制代码
复制代码
struct v4l2_subdev_ops {
    const struct v4l2_subdev_core_ops    *core;    // 所有sub-device通用
    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_vbi_ops    *vbi;
    const struct v4l2_subdev_ir_ops        *ir;
    const struct v4l2_subdev_sensor_ops    *sensor;
    const struct v4l2_subdev_pad_ops    *pad;
};
复制代码

sub-device驱动通过使用如下方式完成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
}
EXPORT_SYMBOL(v4l2_subdev_init);
复制代码

   然后你需要设置唯一的名字:v4l2_subdev->name。

  如果需要集成media framework,必须通过调用media_entity_init()函数来初始化v4l2_subdev->media_entity成员。

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

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

  pads array必须提前初始化。没必要手动设置media_entity的类型和名字,但是previously成员如果需要必须被初始化。【我们看v4l2_subdev_init关于media_entity却是手动初始化的,没有调用media_entity_init,为何?!】

  当subdev设备节点被打开/关闭时,entity的引用会自动的增加/减小。

  在sub-device被销毁时,记得清除media entity:

media_entity_cleanup(&sd->entity);

 

  v4l2_subdev的注册:

复制代码
/* kernel/drivers/media/video/v4l2_device.c */
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
                struct v4l2_subdev *sd)
{
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_entity *entity = &sd->entity;
#endif
    int err;

    /* Check for valid input */
    if (v4l2_dev == NULL || sd == NULL || !sd->name[0])
        return -EINVAL;

    /* Warn if we apparently re-register a subdev */
    WARN_ON(sd->v4l2_dev != NULL);

    if (!try_module_get(sd->owner))
        return -ENODEV;

    sd->v4l2_dev = v4l2_dev;   // sd->v4l2_dev指向了v4l2_dev;
    if (sd->internal_ops && sd->internal_ops->registered) {
        err = sd->internal_ops->registered(sd);
        if (err) {
            module_put(sd->owner);
            return err;
        }
    }

    /* This just returns 0 if either of the two args is NULL */
    err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler);
    if (err) {
        if (sd->internal_ops && sd->internal_ops->unregistered)
            sd->internal_ops->unregistered(sd);
        module_put(sd->owner);
        return err;
    }

#if defined(CONFIG_MEDIA_CONTROLLER)
    /* Register the entity. */
    if (v4l2_dev->mdev) {
        err = media_device_register_entity(v4l2_dev->mdev, entity);
        if (err < 0) {
            if (sd->internal_ops && sd->internal_ops->unregistered)
                sd->internal_ops->unregistered(sd);
            module_put(sd->owner);
            return err;
        }
    }
#endif

    spin_lock(&v4l2_dev->lock);
    list_add_tail(&sd->list, &v4l2_dev->subdevs);
    spin_unlock(&v4l2_dev->lock);

    return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev);
复制代码

  如果 v4l2_device的mdev成员非空,那么sub-devie的entity成员将会被自动的使用media device进行注册 。  

v4l2_subdev的注销:

复制代码
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);
    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)
        media_device_unregister_entity(&sd->entity);
#endif
    video_unregister_device(&sd->devnode);
    module_put(sd->owner);
}
EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev);
复制代码

 

ops的调用:

  1. 直接调用:

err = sd->ops->core->g_chip_ident(sd, &chip);

  2. 宏调用

err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);
复制代码
/* Call an ops of a v4l2_subdev, doing the right checks against
   NULL pointers.

   Example: err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);
 */
#define v4l2_subdev_call(sd, o, f, args...)                \
    (!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ?    \
        (sd)->ops->o->f((sd) , ##args) : -ENOIOCTLCMD))
/*
  宏调用优点:1. 对sd是否为空的检测
       2. 调用函数f所在的ops是否为空的检测
   3. 调用函数f是否为空的检测
       4. 调用是被返回负值,成功返回函数f的返回值
*/
复制代码

  3. 子集调用

       v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip); // 调用一个子集
复制代码
/* Call the specified callback for all subdevs matching grp_id (if 0, then
   match them all). Ignore any errors. Note that you cannot add or delete
   a subdev while walking the subdevs list. */
#define v4l2_device_call_all(v4l2_dev, grpid, o, f, args...)        \
    do {                                \
        struct v4l2_subdev *__sd;                \
                                    \
        __v4l2_device_call_subdevs_p(v4l2_dev, __sd,        \
            !(grpid) || __sd->grp_id == (grpid), o, f ,    \
            ##args);                    \
    } while (0)
复制代码

  第二个参数称为:group ID。如果是0,所有的subdevs都被调用。如果非0,仅仅调用相对应的。在bridge driver注册subdev之前,可以设置 sd->grp_id 是为任何值(默认为0).这个值是bridge driver拥有,sub-device的驱动永远不会修改及使用。

  group ID给了bridge driver更多的回调控制能力。例如,在一个板级上可能有多个audio chips,每个都可以调声。但是通常仅仅只有真正使用的才想调声。你可以设置group ID为每一个subdev。

If the sub-device needs to notify its v4l2_device parent of an event, then it can call v4l2_subdev_notify(sd, notification, arg). This macro checks whether there is a notify() callback defined and returns -ENODEV if not. Otherwise the result of the notify() call is returned.
The advantage of using v4l2_subdev is that it is a generic struct and does not contain any knowledge about the underlying hardware. So a driver might contain several subdevs that use an I2C bus, but also a subdev that is controlled through GPIO pins. This distinction is only relevant when setting up the device, but once the subdev is registered it is completely transparent.

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
V4L2子设备驱动程序注册v4l2_subdev_s_ctrl的流程如下: 1. 在v4l2_subdev_ops结构体中定义v4l2_subdev_s_ctrl函数指针,指向驱动程序中的实现函数。 ```c struct v4l2_subdev_ops { ... int (*s_ctrl) (struct v4l2_subdev *sd, struct v4l2_control *ctrl); ... }; ``` 2. 在驱动程序的probe函数中,初始化v4l2_subdev_ops结构体,将v4l2_subdev_s_ctrl函数指针指向驱动程序中的实现函数。 ```c static int mysubdev_probe(struct i2c_client *client, const struct i2c_device_id *id) { ... sd->ops = &mysubdev_ops; ... } static const struct v4l2_subdev_ops mysubdev_ops = { .s_ctrl = mysubdev_s_ctrl, }; ``` 3. 实现v4l2_subdev_s_ctrl函数,该函数将被V4L2框架调用,用于设置V4L2控制器的值。 ```c static int mysubdev_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) { int ret = 0; ... // 根据控制器类型和ID设置控制器的值 switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: ret = mysubdev_set_brightness(sd, ctrl->value); break; case V4L2_CID_CONTRAST: ret = mysubdev_set_contrast(sd, ctrl->value); break; ... default: ret = -EINVAL; break; } ... return ret; } ``` 在上述流程中,驱动程序需要实现v4l2_subdev_s_ctrl函数,该函数将被V4L2框架调用,用于设置V4L2控制器的值。在该函数中,驱动程序需要根据控制器类型和ID设置控制器的值,并返回相应的错误码。 需要注意的是,V4L2控制器的类型和ID是由应用程序定义的,驱动程序需要根据具体应用程序的控制器类型和ID来实现v4l2_subdev_s_ctrl函数。同时,V4L2控制器的值也需要根据应用程序的要求来进行设置。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值