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.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章