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