转自: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.