[alsa-devel] [PATCH v3 00/21] Update ALSA, and au0828 drivers to use Managed Media Controller API
This patch series updates ALSA driver, and au0828 core driver to use Managed Media controller API to share tuner. Please note that Managed Media Controller API and DVB and V4L2 drivers updates to use Media Controller API have been added in a prior patch series.
Media Controller API is enhanced with two new interfaces to register and unregister entity_notify hooks to allow drivers to take appropriate actions when as new entities get added to the shared media device.
Tested exclusion between digital, analog, and audio to ensure when tuner has an active link to DVB FE, analog, and audio will detect and honor the tuner busy conditions and vice versa.
Changes since v2: http://www.spinics.net/lists/linux-media/msg91926.html
1. An important change in this patch series is made to ALSA to check for resources and hold in snd_usb_hw_params(), and release from snd_usb_hw_free(). This change fixed the lockdep warnings seen when resources were held in TRIGGER_START and released from TRIGGER_STOP which could run in IRQ context. I acknowledge Clemens Ladisch for suggesting the correct places to hold/free resources to avoid IRQ path complications. 2. With the above change, the patch series is simpler without the need to change the graph_mutex into a spinlock. 3. I split the patches up differently for easy reviews - no code bloat from v2. 4. A second important change is now the Bridge driver (au0828) owns and drives the graph creation as well as enabling and disabling tuner. It also keeps state information to avoid graph walks in enable_source and disable_source handler. I acknowledge Hans Verkuil for his suggestions and ideas for this change.
History: This patch series has been updated to address comments from 3 previous versions of this series. Links to v3 version for reference are:
https://www.mail-archive.com/linux-media%40vger.kernel.org/msg89491.html https://www.mail-archive.com/linux-media@vger.kernel.org/msg89492.html https://www.mail-archive.com/linux-media%40vger.kernel.org/msg89493.html
Shuah Khan (21): Revert "[media] media: media controller entity framework enhancements for ALSA" media: Media Controller register/unregister entity_notify API media: Add ALSA Media Controller devnodes media: Media Controller enable/disable source handler API media: Media Controller fix to not let stream_count go negative media: Media Controller export non locking __media_entity_setup_link() media: Media Controller non-locking __media_entity_pipeline_start/stop() media: v4l-core add v4l_enable/disable_media_tuner() helper functions media: Move au8522_media_pads enum to au8522.h from au8522_priv.h media: au8522 change to create MC pad for ALSA Audio Out media: au0828 Use au8522_media_pads enum for pad defines media: au0828 fix au0828_create_media_graph() entity checks media: Change v4l-core to check for tuner availability media: dvb-frontend invoke enable/disable_source handlers media: au0828 video remove au0828_enable_analog_tuner() media: au0828 video change to use v4l_enable_media_tuner() media: au0828 change to use Managed Media Controller API media: au0828 change to register/unregister entity_notify hook media: au0828 implement enable_source and disable_source handlers media: media: dvb-frontend fix enable_source error legs sound/usb: Update ALSA driver to use Managed Media Controller API
drivers/media/dvb-core/dvb_frontend.c | 142 ++----------- drivers/media/dvb-core/dvb_frontend.h | 3 + drivers/media/dvb-frontends/au8522.h | 8 + drivers/media/dvb-frontends/au8522_decoder.c | 1 + drivers/media/dvb-frontends/au8522_priv.h | 8 - drivers/media/media-device.c | 46 +++- drivers/media/media-entity.c | 64 ++++-- drivers/media/usb/au0828/au0828-core.c | 305 ++++++++++++++++++++++----- drivers/media/usb/au0828/au0828-video.c | 76 ++----- drivers/media/usb/au0828/au0828.h | 8 + drivers/media/v4l2-core/v4l2-dev.c | 27 +++ drivers/media/v4l2-core/v4l2-fh.c | 1 + drivers/media/v4l2-core/v4l2-ioctl.c | 29 +++ drivers/media/v4l2-core/videobuf2-core.c | 3 + include/media/media-device.h | 41 ++++ include/media/media-entity.h | 7 +- include/media/v4l2-dev.h | 4 + include/uapi/linux/media.h | 5 + sound/usb/Makefile | 15 +- sound/usb/card.c | 5 + sound/usb/card.h | 1 + sound/usb/media.c | 178 ++++++++++++++++ sound/usb/media.h | 51 +++++ sound/usb/pcm.c | 29 ++- sound/usb/quirks-table.h | 1 + sound/usb/quirks.c | 9 +- sound/usb/stream.c | 2 + sound/usb/usbaudio.h | 1 + 28 files changed, 790 insertions(+), 280 deletions(-) create mode 100644 sound/usb/media.c create mode 100644 sound/usb/media.h
This reverts commit ed64cf1e182fb30fe67652386c0880fcf3302f97. This patch is no longer necessary as the entity register callback is implemented at media_device level.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/media-device.c | 7 ------- include/media/media-entity.h | 4 ---- 2 files changed, 11 deletions(-)
diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c index 76590ba..c55ab50 100644 --- a/drivers/media/media-device.c +++ b/drivers/media/media-device.c @@ -428,8 +428,6 @@ EXPORT_SYMBOL_GPL(media_device_unregister); int __must_check media_device_register_entity(struct media_device *mdev, struct media_entity *entity) { - struct media_entity *eptr; - /* Warn if we apparently re-register an entity */ WARN_ON(entity->parent != NULL); entity->parent = mdev; @@ -442,11 +440,6 @@ int __must_check media_device_register_entity(struct media_device *mdev, list_add_tail(&entity->list, &mdev->entities); spin_unlock(&mdev->lock);
- media_device_for_each_entity(eptr, mdev) { - if (eptr != entity) - media_entity_call(eptr, register_notify); - } - return 0; } EXPORT_SYMBOL_GPL(media_device_register_entity); diff --git a/include/media/media-entity.h b/include/media/media-entity.h index 0bc4c2f..0c003d8 100644 --- a/include/media/media-entity.h +++ b/include/media/media-entity.h @@ -46,7 +46,6 @@ struct media_pad {
/** * struct media_entity_operations - Media entity operations - * @register_notify Notify entity of newly registered entity * @link_setup: Notify the entity of link changes. The operation can * return an error, in which case link setup will be * cancelled. Optional. @@ -55,7 +54,6 @@ struct media_pad { * validates all links by calling this operation. Optional. */ struct media_entity_operations { - int (*register_notify)(struct media_entity *entity); int (*link_setup)(struct media_entity *entity, const struct media_pad *local, const struct media_pad *remote, u32 flags); @@ -103,8 +101,6 @@ struct media_entity { /* Sub-device specifications */ /* Nothing needed yet */ } info; - - void *private; /* private data for the entity */ };
static inline u32 media_entity_type(struct media_entity *entity)
Add new interfaces to register and unregister entity_notify hook to media device to allow drivers to take appropriate actions when as new entities get added to the shared media device.When a new entity is registered, all registered entity_notify hooks are invoked to allow drivers or modules that registered hook to take appropriate action. For example, ALSA driver registers an entity_notify hook to parse the list of registered entities to determine if decoder has been linked to ALSA entity. au0828 bridge driver registers an entity_notify hook to create media graph for the device.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/media-device.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ include/media/media-device.h | 23 ++++++++++++++++++++++ 2 files changed, 68 insertions(+)
diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c index c55ab50..22565a8 100644 --- a/drivers/media/media-device.c +++ b/drivers/media/media-device.c @@ -381,6 +381,7 @@ int __must_check __media_device_register(struct media_device *mdev,
mdev->entity_id = 1; INIT_LIST_HEAD(&mdev->entities); + INIT_LIST_HEAD(&mdev->entity_notify); spin_lock_init(&mdev->lock); mutex_init(&mdev->graph_mutex);
@@ -411,9 +412,12 @@ void media_device_unregister(struct media_device *mdev) { struct media_entity *entity; struct media_entity *next; + struct media_entity_notify *notify, *nextp;
list_for_each_entry_safe(entity, next, &mdev->entities, list) media_device_unregister_entity(entity); + list_for_each_entry_safe(notify, nextp, &mdev->entity_notify, list) + media_device_unregister_entity_notify(mdev, notify);
device_remove_file(&mdev->devnode.dev, &dev_attr_model); media_devnode_unregister(&mdev->devnode); @@ -421,6 +425,39 @@ void media_device_unregister(struct media_device *mdev) EXPORT_SYMBOL_GPL(media_device_unregister);
/** + * media_device_register_entity_notify - Register a media entity notify + * callback with a media device. When a new entity is registered, all + * the registered media_entity_notify callbacks are invoked. + * @mdev: The media device + * @nptr: The media_entity_notify + */ +int __must_check media_device_register_entity_notify(struct media_device *mdev, + struct media_entity_notify *nptr) +{ + spin_lock(&mdev->lock); + list_add_tail(&nptr->list, &mdev->entity_notify); + spin_unlock(&mdev->lock); + return 0; +} +EXPORT_SYMBOL_GPL(media_device_register_entity_notify); + +/** + * media_device_unregister_entity_notify - Unregister a media entity notify + * callback with a media device. When a new entity is registered, all + * the registered media_entity_notify callbacks are invoked. + * @mdev: The media device + * @nptr: The media_entity_notify + */ +void media_device_unregister_entity_notify(struct media_device *mdev, + struct media_entity_notify *nptr) +{ + spin_lock(&mdev->lock); + list_del(&nptr->list); + spin_unlock(&mdev->lock); +} +EXPORT_SYMBOL_GPL(media_device_unregister_entity_notify); + +/** * media_device_register_entity - Register an entity with a media device * @mdev: The media device * @entity: The entity @@ -428,6 +465,8 @@ EXPORT_SYMBOL_GPL(media_device_unregister); int __must_check media_device_register_entity(struct media_device *mdev, struct media_entity *entity) { + struct media_entity_notify *notify, *next; + /* Warn if we apparently re-register an entity */ WARN_ON(entity->parent != NULL); entity->parent = mdev; @@ -440,6 +479,11 @@ int __must_check media_device_register_entity(struct media_device *mdev, list_add_tail(&entity->list, &mdev->entities); spin_unlock(&mdev->lock);
+ /* invoke entity_notify callbacks */ + list_for_each_entry_safe(notify, next, &mdev->entity_notify, list) { + (notify)->notify(entity, notify->notify_data); + } + return 0; } EXPORT_SYMBOL_GPL(media_device_register_entity); @@ -462,6 +506,7 @@ void media_device_unregister_entity(struct media_entity *entity) list_del(&entity->list); spin_unlock(&mdev->lock); entity->parent = NULL; + /* invoke entity_notify callbacks to handle entity removal?? */ } EXPORT_SYMBOL_GPL(media_device_unregister_entity);
diff --git a/include/media/media-device.h b/include/media/media-device.h index a44f18f..a3854f6 100644 --- a/include/media/media-device.h +++ b/include/media/media-device.h @@ -32,6 +32,12 @@
struct device;
+struct media_entity_notify { + struct list_head list; + void *notify_data; + void (*notify)(struct media_entity *entity, void *notify_data); +}; + /** * struct media_device - Media device * @dev: Parent device @@ -70,6 +76,8 @@ struct media_device {
u32 entity_id; struct list_head entities; + /* notify callback list invoked when a new entity is registered */ + struct list_head entity_notify;
/* Protects the entities list */ spinlock_t lock; @@ -94,6 +102,10 @@ int __must_check __media_device_register(struct media_device *mdev, #define media_device_register(mdev) __media_device_register(mdev, THIS_MODULE) void media_device_unregister(struct media_device *mdev);
+int __must_check media_device_register_entity_notify(struct media_device *mdev, + struct media_entity_notify *nptr); +void media_device_unregister_entity_notify(struct media_device *mdev, + struct media_entity_notify *nptr); int __must_check media_device_register_entity(struct media_device *mdev, struct media_entity *entity); void media_device_unregister_entity(struct media_entity *entity); @@ -112,6 +124,17 @@ static inline int media_device_register(struct media_device *mdev) static inline void media_device_unregister(struct media_device *mdev) { } +static inline int media_device_register_entity_notify( + struct media_device *mdev, + struct media_entity_notify *nptr) +{ + return 0; +} +static inline void media_device_unregister_entity_notify( + struct media_device *mdev, + struct media_entity_notify *nptr) +{ +} static inline int media_device_register_entity(struct media_device *mdev, struct media_entity *entity) {
Add ALSA Media Controller capture, playback, and mixer devnode defines.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- include/uapi/linux/media.h | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 4e816be..4a30ea3 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -49,12 +49,17 @@ struct media_device_info { #define MEDIA_ENT_T_DEVNODE (1 << MEDIA_ENT_TYPE_SHIFT) #define MEDIA_ENT_T_DEVNODE_V4L (MEDIA_ENT_T_DEVNODE + 1) #define MEDIA_ENT_T_DEVNODE_FB (MEDIA_ENT_T_DEVNODE + 2) +/* Legacy ALSA symbol. Keep it to avoid userspace compilation breakages */ #define MEDIA_ENT_T_DEVNODE_ALSA (MEDIA_ENT_T_DEVNODE + 3) #define MEDIA_ENT_T_DEVNODE_DVB_FE (MEDIA_ENT_T_DEVNODE + 4) #define MEDIA_ENT_T_DEVNODE_DVB_DEMUX (MEDIA_ENT_T_DEVNODE + 5) #define MEDIA_ENT_T_DEVNODE_DVB_DVR (MEDIA_ENT_T_DEVNODE + 6) #define MEDIA_ENT_T_DEVNODE_DVB_CA (MEDIA_ENT_T_DEVNODE + 7) #define MEDIA_ENT_T_DEVNODE_DVB_NET (MEDIA_ENT_T_DEVNODE + 8) +/* ALSA devnodes */ +#define MEDIA_ENT_T_DEVNODE_ALSA_CAPTURE (MEDIA_ENT_T_DEVNODE + 9) +#define MEDIA_ENT_T_DEVNODE_ALSA_PLAYBACK (MEDIA_ENT_T_DEVNODE + 10) +#define MEDIA_ENT_T_DEVNODE_ALSA_MIXER (MEDIA_ENT_T_DEVNODE + 11)
/* Legacy symbol. Use it to avoid userspace compilation breakages */ #define MEDIA_ENT_T_DEVNODE_DVB MEDIA_ENT_T_DEVNODE_DVB_FE
Add new fields to struct media_device to add enable_source, and disable_source handlers, and source_priv to stash driver private data that is need to run these handlers. The enable_source handler finds source entity for the passed in entity and check if it is available, and activate the link using __media_entity_setup_link() interface. Bridge driver is expected to implement and set these handlers and private data when media_device is registered or when bridge driver finds the media_device during probe. This is to enable the use-case to find tuner entity connected to the decoder entity and check if it is available, and activate it and start pipeline between the source and the entity. The disable_source handler deactivates the link and stops the pipeline. This handler can be invoked from the media core (v4l-core, dvb-core) as well as other drivers such as ALSA that control the media device.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- include/media/media-device.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/include/media/media-device.h b/include/media/media-device.h index a3854f6..117c169 100644 --- a/include/media/media-device.h +++ b/include/media/media-device.h @@ -84,6 +84,24 @@ struct media_device { /* Serializes graph operations. */ struct mutex graph_mutex;
+ /* Handlers to find source entity for the sink entity and + * check if it is available, and activate the link using + * media_entity_setup_link() interface and start pipeline + * from the source to the entity. + * Bridge driver is expected to implement and set the + * handler when media_device is registered or when + * bridge driver finds the media_device during probe. + * Bridge driver sets source_priv with information + * necessary to run enable/disable source handlers. + * + * Use-case: find tuner entity connected to the decoder + * entity and check if it is available, and activate the + * using media_entity_setup_link() if it is available. + */ + void *source_priv; + int (*enable_source)(struct media_entity *entity, + struct media_pipeline *pipe); + void (*disable_source)(struct media_entity *entity); int (*link_notify)(struct media_link *link, u32 flags, unsigned int notification); };
Add a range check to not let the stream_count become negative. Wthout this check, calls to stop pipeline when there is no active pipeline will result in stream_count < 0 condition and lock and preventing link state (activate/deactivate) changes. This will happen from error leg in start pipeline interface.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/media-entity.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c index 4d8e01c..68d5ec2 100644 --- a/drivers/media/media-entity.c +++ b/drivers/media/media-entity.c @@ -315,9 +315,12 @@ error: media_entity_graph_walk_start(&graph, entity_err);
while ((entity_err = media_entity_graph_walk_next(&graph))) { - entity_err->stream_count--; - if (entity_err->stream_count == 0) - entity_err->pipe = NULL; + /* don't let the stream_count go negative */ + if (entity->stream_count > 0) { + entity_err->stream_count--; + if (entity_err->stream_count == 0) + entity_err->pipe = NULL; + }
/* * We haven't increased stream_count further than this @@ -355,9 +358,12 @@ void media_entity_pipeline_stop(struct media_entity *entity) media_entity_graph_walk_start(&graph, entity);
while ((entity = media_entity_graph_walk_next(&graph))) { - entity->stream_count--; - if (entity->stream_count == 0) - entity->pipe = NULL; + /* don't let the stream_count go negative */ + if (entity->stream_count > 0) { + entity->stream_count--; + if (entity->stream_count == 0) + entity->pipe = NULL; + } }
mutex_unlock(&mdev->graph_mutex);
Export __media_entity_setup_link() to be used from code paths that hold the graph_mutex.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/media-entity.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c index 68d5ec2..03b3836 100644 --- a/drivers/media/media-entity.c +++ b/drivers/media/media-entity.c @@ -612,6 +612,7 @@ int __media_entity_setup_link(struct media_link *link, u32 flags)
return ret; } +EXPORT_SYMBOL_GPL(__media_entity_setup_link);
int media_entity_setup_link(struct media_link *link, u32 flags) {
Add non-locking __media_entity_pipeline_start/stop() interfaces to be called from code paths that hold the graph_mutex. For this change, the media_entity_pipeline_start() routine is renamed to __media_entity_pipeline_start() minus the graph_mutex lock and unlock. media_entity_pipeline_start() now calls the non-locking __media_entity_pipeline_start() holding the graph_lock. The stop interface, media_entity_pipeline_stop() routine is renamed to __media_entity_pipeline_stop() minus the graph_mutex lock and unlock. media_entity_pipeline_stop() now calls the non-locking __media_entity_pipeline_stop() holding the graph_lock.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/media-entity.c | 45 ++++++++++++++++++++++++++++++-------------- include/media/media-entity.h | 3 +++ 2 files changed, 34 insertions(+), 14 deletions(-)
diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c index 03b3836..b87e773 100644 --- a/drivers/media/media-entity.c +++ b/drivers/media/media-entity.c @@ -210,6 +210,7 @@ EXPORT_SYMBOL_GPL(media_entity_graph_walk_next);
/** * media_entity_pipeline_start - Mark a pipeline as streaming + * __media_entity_pipeline_start - Mark a pipeline as streaming * @entity: Starting entity * @pipe: Media pipeline to be assigned to all entities in the pipeline. * @@ -220,18 +221,17 @@ EXPORT_SYMBOL_GPL(media_entity_graph_walk_next); * Calls to this function can be nested, in which case the same number of * media_entity_pipeline_stop() calls will be required to stop streaming. The * pipeline pointer must be identical for all nested calls to - * media_entity_pipeline_start(). + * __media_entity_pipeline_start(). + * User is expected to hold the graph_mutex. If not user can call + * media_entity_pipeline_start() */ -__must_check int media_entity_pipeline_start(struct media_entity *entity, - struct media_pipeline *pipe) +__must_check int __media_entity_pipeline_start(struct media_entity *entity, + struct media_pipeline *pipe) { - struct media_device *mdev = entity->parent; struct media_entity_graph graph; struct media_entity *entity_err = entity; int ret;
- mutex_lock(&mdev->graph_mutex); - media_entity_graph_walk_start(&graph, entity);
while ((entity = media_entity_graph_walk_next(&graph))) { @@ -303,8 +303,6 @@ __must_check int media_entity_pipeline_start(struct media_entity *entity, } }
- mutex_unlock(&mdev->graph_mutex); - return 0;
error: @@ -330,14 +328,25 @@ error: break; }
- mutex_unlock(&mdev->graph_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(__media_entity_pipeline_start);
+__must_check int media_entity_pipeline_start(struct media_entity *entity, + struct media_pipeline *pipe) +{ + struct media_device *mdev = entity->parent; + int ret; + + mutex_lock(&mdev->graph_mutex); + ret = __media_entity_pipeline_start(entity, pipe); + mutex_unlock(&mdev->graph_mutex); return ret; } EXPORT_SYMBOL_GPL(media_entity_pipeline_start);
/** - * media_entity_pipeline_stop - Mark a pipeline as not streaming + * __media_entity_pipeline_stop - Mark a pipeline as not streaming * @entity: Starting entity * * Mark all entities connected to a given entity through enabled links, either @@ -347,14 +356,13 @@ EXPORT_SYMBOL_GPL(media_entity_pipeline_start); * If multiple calls to media_entity_pipeline_start() have been made, the same * number of calls to this function are required to mark the pipeline as not * streaming. + * User is expected to hold the graph_mutex. If not user can call + * media_entity_pipeline_stop() */ -void media_entity_pipeline_stop(struct media_entity *entity) +void __media_entity_pipeline_stop(struct media_entity *entity) { - struct media_device *mdev = entity->parent; struct media_entity_graph graph;
- mutex_lock(&mdev->graph_mutex); - media_entity_graph_walk_start(&graph, entity);
while ((entity = media_entity_graph_walk_next(&graph))) { @@ -366,6 +374,15 @@ void media_entity_pipeline_stop(struct media_entity *entity) } }
+} +EXPORT_SYMBOL_GPL(__media_entity_pipeline_stop); + +void media_entity_pipeline_stop(struct media_entity *entity) +{ + struct media_device *mdev = entity->parent; + + mutex_lock(&mdev->graph_mutex); + __media_entity_pipeline_stop(entity); mutex_unlock(&mdev->graph_mutex); } EXPORT_SYMBOL_GPL(media_entity_pipeline_stop); diff --git a/include/media/media-entity.h b/include/media/media-entity.h index 0c003d8..cc0bbd0 100644 --- a/include/media/media-entity.h +++ b/include/media/media-entity.h @@ -148,8 +148,11 @@ void media_entity_graph_walk_start(struct media_entity_graph *graph, struct media_entity *entity); struct media_entity * media_entity_graph_walk_next(struct media_entity_graph *graph); +__must_check int __media_entity_pipeline_start(struct media_entity *entity, + struct media_pipeline *pipe); __must_check int media_entity_pipeline_start(struct media_entity *entity, struct media_pipeline *pipe); +void __media_entity_pipeline_stop(struct media_entity *entity); void media_entity_pipeline_stop(struct media_entity *entity);
#define media_entity_call(entity, operation, args...) \
Add a new interfaces to be used by v4l-core to invoke enable source and disable_source handlers in the media_device. The enable_source helper function invokes the enable_source handler to find tuner entity connected to the decoder and check is it is available or busy. If tuner is available, link is activated and pipeline is started. The disable_source helper function invokes the disable_source handler to deactivate and stop the pipeline.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/v4l2-core/v4l2-dev.c | 27 +++++++++++++++++++++++++++ include/media/v4l2-dev.h | 4 ++++ 2 files changed, 31 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index 71a1b93..8e93c99 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -230,6 +230,33 @@ struct video_device *video_devdata(struct file *file) } EXPORT_SYMBOL(video_devdata);
+int v4l_enable_media_tuner(struct video_device *vdev) +{ +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device *mdev = vdev->entity.parent; + int ret; + + if (!mdev || !mdev->enable_source) + return 0; + ret = mdev->enable_source(&vdev->entity, &vdev->pipe); + if (ret) + return -EBUSY; + return 0; +#endif /* CONFIG_MEDIA_CONTROLLER */ + return 0; +} +EXPORT_SYMBOL_GPL(v4l_enable_media_tuner); + +void v4l_disable_media_tuner(struct video_device *vdev) +{ +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device *mdev = vdev->entity.parent; + + if (mdev && mdev->disable_source) + mdev->disable_source(&vdev->entity); +#endif /* CONFIG_MEDIA_CONTROLLER */ +} +EXPORT_SYMBOL_GPL(v4l_disable_media_tuner);
/* Priority handling */
diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h index acbcd2f..8f1884f 100644 --- a/include/media/v4l2-dev.h +++ b/include/media/v4l2-dev.h @@ -86,6 +86,7 @@ struct video_device { #if defined(CONFIG_MEDIA_CONTROLLER) struct media_entity entity; + struct media_pipeline pipe; #endif /* device ops */ const struct v4l2_file_operations *fops; @@ -178,6 +179,9 @@ struct video_device * __must_check video_device_alloc(void); /* this release function frees the vdev pointer */ void video_device_release(struct video_device *vdev);
+int v4l_enable_media_tuner(struct video_device *vdev); +void v4l_disable_media_tuner(struct video_device *vdev); + /* this release function does nothing, use when the video_device is a static global struct. Note that having a static video_device is a dubious construction at best. */
Move the au8522_media_pads enum to au8522.h from au8522_priv.h. This will allow au0828-core to use these defines instead of hard-coding the pad values when it creates media graph linking decode pads to other entities.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/dvb-frontends/au8522.h | 7 +++++++ drivers/media/dvb-frontends/au8522_priv.h | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/drivers/media/dvb-frontends/au8522.h b/drivers/media/dvb-frontends/au8522.h index dde6158..3c72f40 100644 --- a/drivers/media/dvb-frontends/au8522.h +++ b/drivers/media/dvb-frontends/au8522.h @@ -90,4 +90,11 @@ enum au8522_audio_input { AU8522_AUDIO_SIF, };
+enum au8522_media_pads { + AU8522_PAD_INPUT, + AU8522_PAD_VID_OUT, + AU8522_PAD_VBI_OUT, + + AU8522_NUM_PADS +}; #endif /* __AU8522_H__ */ diff --git a/drivers/media/dvb-frontends/au8522_priv.h b/drivers/media/dvb-frontends/au8522_priv.h index d6209d9..4c2a6ed 100644 --- a/drivers/media/dvb-frontends/au8522_priv.h +++ b/drivers/media/dvb-frontends/au8522_priv.h @@ -39,14 +39,6 @@ #define AU8522_DIGITAL_MODE 1 #define AU8522_SUSPEND_MODE 2
-enum au8522_media_pads { - AU8522_PAD_INPUT, - AU8522_PAD_VID_OUT, - AU8522_PAD_VBI_OUT, - - AU8522_NUM_PADS -}; - struct au8522_state { struct i2c_client *c; struct i2c_adapter *i2c;
Add new pad for ALSA Audio Out to au8522_media_pads.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/dvb-frontends/au8522.h | 1 + drivers/media/dvb-frontends/au8522_decoder.c | 1 + 2 files changed, 2 insertions(+)
diff --git a/drivers/media/dvb-frontends/au8522.h b/drivers/media/dvb-frontends/au8522.h index 3c72f40..d7a997f 100644 --- a/drivers/media/dvb-frontends/au8522.h +++ b/drivers/media/dvb-frontends/au8522.h @@ -94,6 +94,7 @@ enum au8522_media_pads { AU8522_PAD_INPUT, AU8522_PAD_VID_OUT, AU8522_PAD_VBI_OUT, + AU8522_PAD_AUDIO_OUT,
AU8522_NUM_PADS }; diff --git a/drivers/media/dvb-frontends/au8522_decoder.c b/drivers/media/dvb-frontends/au8522_decoder.c index 24990db..5fc9a39 100644 --- a/drivers/media/dvb-frontends/au8522_decoder.c +++ b/drivers/media/dvb-frontends/au8522_decoder.c @@ -775,6 +775,7 @@ static int au8522_probe(struct i2c_client *client, state->pads[AU8522_PAD_INPUT].flags = MEDIA_PAD_FL_SINK; state->pads[AU8522_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE; state->pads[AU8522_PAD_VBI_OUT].flags = MEDIA_PAD_FL_SOURCE; + state->pads[AU8522_PAD_AUDIO_OUT].flags = MEDIA_PAD_FL_SOURCE; sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_DECODER;
ret = media_entity_init(&sd->entity, ARRAY_SIZE(state->pads),
Change au0828-core to use au8522_media_pads enum defines instead of hard-coding the pad values when it creates media graph linking decode pads to other entities.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/usb/au0828/au0828-core.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index 0378a2c..276598b 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -20,6 +20,7 @@ */
#include "au0828.h" +#include "au8522.h"
#include <linux/module.h> #include <linux/slab.h> @@ -263,11 +264,13 @@ static void au0828_create_media_graph(struct au0828_dev *dev) media_entity_create_link(tuner, 0, decoder, 0, MEDIA_LNK_FL_ENABLED); if (dev->vdev.entity.links) - media_entity_create_link(decoder, 1, &dev->vdev.entity, 0, - MEDIA_LNK_FL_ENABLED); + media_entity_create_link(decoder, AU8522_PAD_VID_OUT, + &dev->vdev.entity, 0, + MEDIA_LNK_FL_ENABLED); if (dev->vbi_dev.entity.links) - media_entity_create_link(decoder, 2, &dev->vbi_dev.entity, 0, - MEDIA_LNK_FL_ENABLED); + media_entity_create_link(decoder, AU8522_PAD_VBI_OUT, + &dev->vbi_dev.entity, 0, + MEDIA_LNK_FL_ENABLED); #endif }
au0828_create_media_graph() checks if entity.links is null or not to determine, if vbi_dev and vdev entities have been registered. Checking entity.parent field is right way, as parent field gets initialized when entity is registered. Fix it to check entity.parent field.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/usb/au0828/au0828-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index 276598b..0b8dc49 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -263,11 +263,11 @@ static void au0828_create_media_graph(struct au0828_dev *dev) if (tuner) media_entity_create_link(tuner, 0, decoder, 0, MEDIA_LNK_FL_ENABLED); - if (dev->vdev.entity.links) + if (dev->vdev.entity.parent) media_entity_create_link(decoder, AU8522_PAD_VID_OUT, &dev->vdev.entity, 0, MEDIA_LNK_FL_ENABLED); - if (dev->vbi_dev.entity.links) + if (dev->vbi_dev.entity.parent) media_entity_create_link(decoder, AU8522_PAD_VBI_OUT, &dev->vbi_dev.entity, 0, MEDIA_LNK_FL_ENABLED);
Change s_input, s_fmt, s_tuner, s_frequency, querystd, s_hw_freq_seek, and vb2_internal_streamon interfaces that alter the tuner configuration to check for tuner availability by calling v4l_enable_media_tuner(). If tuner isn't free, return -EBUSY. v4l_disable_media_tuner() is called from v4l2_fh_exit() to release the tuner.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/v4l2-core/v4l2-fh.c | 1 + drivers/media/v4l2-core/v4l2-ioctl.c | 29 +++++++++++++++++++++++++++++ drivers/media/v4l2-core/videobuf2-core.c | 3 +++ 3 files changed, 33 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-fh.c b/drivers/media/v4l2-core/v4l2-fh.c index c97067a..538db62 100644 --- a/drivers/media/v4l2-core/v4l2-fh.c +++ b/drivers/media/v4l2-core/v4l2-fh.c @@ -92,6 +92,7 @@ void v4l2_fh_exit(struct v4l2_fh *fh) { if (fh->vdev == NULL) return; + v4l_disable_media_tuner(fh->vdev); v4l2_event_unsubscribe_all(fh); fh->vdev = NULL; } diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 85de455..deffc1b 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1035,6 +1035,12 @@ static int v4l_querycap(const struct v4l2_ioctl_ops *ops, static int v4l_s_input(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { + struct video_device *vfd = video_devdata(file); + int ret; + + ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; return ops->vidioc_s_input(file, fh, *(unsigned int *)arg); }
@@ -1433,6 +1439,9 @@ static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, bool is_tx = vfd->vfl_dir != VFL_DIR_RX; int ret;
+ ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; v4l_sanitize_format(p);
switch (p->type) { @@ -1612,7 +1621,11 @@ static int v4l_s_tuner(const struct v4l2_ioctl_ops *ops, { struct video_device *vfd = video_devdata(file); struct v4l2_tuner *p = arg; + int ret;
+ ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; return ops->vidioc_s_tuner(file, fh, p); @@ -1650,7 +1663,11 @@ static int v4l_s_frequency(const struct v4l2_ioctl_ops *ops, struct video_device *vfd = video_devdata(file); const struct v4l2_frequency *p = arg; enum v4l2_tuner_type type; + int ret;
+ ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; if (vfd->vfl_type == VFL_TYPE_SDR) { if (p->type != V4L2_TUNER_ADC && p->type != V4L2_TUNER_RF) return -EINVAL; @@ -1705,7 +1722,11 @@ static int v4l_s_std(const struct v4l2_ioctl_ops *ops, { struct video_device *vfd = video_devdata(file); v4l2_std_id id = *(v4l2_std_id *)arg, norm; + int ret;
+ ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; norm = id & vfd->tvnorms; if (vfd->tvnorms && !norm) /* Check if std is supported */ return -EINVAL; @@ -1719,7 +1740,11 @@ static int v4l_querystd(const struct v4l2_ioctl_ops *ops, { struct video_device *vfd = video_devdata(file); v4l2_std_id *p = arg; + int ret;
+ ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; /* * If no signal is detected, then the driver should return * V4L2_STD_UNKNOWN. Otherwise it should return tvnorms with @@ -1738,7 +1763,11 @@ static int v4l_s_hw_freq_seek(const struct v4l2_ioctl_ops *ops, struct video_device *vfd = video_devdata(file); struct v4l2_hw_freq_seek *p = arg; enum v4l2_tuner_type type; + int ret;
+ ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; /* s_hw_freq_seek is not supported for SDR for now */ if (vfd->vfl_type == VFL_TYPE_SDR) return -EINVAL; diff --git a/drivers/media/v4l2-core/videobuf2-core.c b/drivers/media/v4l2-core/videobuf2-core.c index d835814..f7304d8 100644 --- a/drivers/media/v4l2-core/videobuf2-core.c +++ b/drivers/media/v4l2-core/videobuf2-core.c @@ -2251,6 +2251,9 @@ static int vb2_internal_streamon(struct vb2_queue *q, enum v4l2_buf_type type) * are available. */ if (q->queued_count >= q->min_buffers_needed) { + ret = v4l_enable_media_tuner(q->owner->vdev); + if (ret) + return ret; ret = vb2_start_streaming(q); if (ret) { __vb2_queue_cancel(q);
Checking for tuner availability from frontend thread start disrupts video stream. Change to check for tuner and start pipeline from frontend open instead and stop pipeline from frontend release. In addition, make a change to invoke enable_source and disable_source handlers to check for tuner availability. The enable_source handler finds tuner entity connected to the decoder and check is it is available or busy. If tuner is available, link is activated and pipeline is started. The disable_source handler to deactivate and stop the pipeline. dvb_enable_media_tuner() is removed as it is no longer necessary with dvb invoking enable_source and disable_source handlers. pipe_start_entity field is removed and pipe field is moved to dvb_frontend from dvb_frontend_private.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/dvb-core/dvb_frontend.c | 135 ++++------------------------------ drivers/media/dvb-core/dvb_frontend.h | 3 + 2 files changed, 18 insertions(+), 120 deletions(-)
diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c index 842b9c8..c06dd61 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -132,11 +132,6 @@ struct dvb_frontend_private { int quality; unsigned int check_wrapped; enum dvbfe_search algo_status; - -#if defined(CONFIG_MEDIA_CONTROLLER_DVB) - struct media_pipeline pipe; - struct media_entity *pipe_start_entity; -#endif };
static void dvb_frontend_wakeup(struct dvb_frontend *fe); @@ -597,107 +592,12 @@ static void dvb_frontend_wakeup(struct dvb_frontend *fe) wake_up_interruptible(&fepriv->wait_queue); }
-/** - * dvb_enable_media_tuner() - tries to enable the DVB tuner - * - * @fe: struct dvb_frontend pointer - * - * This function ensures that just one media tuner is enabled for a given - * frontend. It has two different behaviors: - * - For trivial devices with just one tuner: - * it just enables the existing tuner->fe link - * - For devices with more than one tuner: - * It is up to the driver to implement the logic that will enable one tuner - * and disable the other ones. However, if more than one tuner is enabled for - * the same frontend, it will print an error message and return -EINVAL. - * - * At return, it will return the error code returned by media_entity_setup_link, - * or 0 if everything is OK, if no tuner is linked to the frontend or if the - * mdev is NULL. - */ -#ifdef CONFIG_MEDIA_CONTROLLER_DVB -static int dvb_enable_media_tuner(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dvb_adapter *adapter = fe->dvb; - struct media_device *mdev = adapter->mdev; - struct media_entity *entity, *source; - struct media_link *link, *found_link = NULL; - int i, ret, n_links = 0, active_links = 0; - - fepriv->pipe_start_entity = NULL; - - if (!mdev) - return 0; - - entity = fepriv->dvbdev->entity; - fepriv->pipe_start_entity = entity; - - for (i = 0; i < entity->num_links; i++) { - link = &entity->links[i]; - if (link->sink->entity == entity) { - found_link = link; - n_links++; - if (link->flags & MEDIA_LNK_FL_ENABLED) - active_links++; - } - } - - if (!n_links || active_links == 1 || !found_link) - return 0; - - /* - * If a frontend has more than one tuner linked, it is up to the driver - * to select with one will be the active one, as the frontend core can't - * guess. If the driver doesn't do that, it is a bug. - */ - if (n_links > 1 && active_links != 1) { - dev_err(fe->dvb->device, - "WARNING: there are %d active links among %d tuners. This is a driver's bug!\n", - active_links, n_links); - return -EINVAL; - } - - source = found_link->source->entity; - fepriv->pipe_start_entity = source; - for (i = 0; i < source->num_links; i++) { - struct media_entity *sink; - int flags = 0; - - link = &source->links[i]; - sink = link->sink->entity; - - if (sink == entity) - flags = MEDIA_LNK_FL_ENABLED; - - ret = media_entity_setup_link(link, flags); - if (ret) { - dev_err(fe->dvb->device, - "Couldn't change link %s->%s to %s. Error %d\n", - source->name, sink->name, - flags ? "enabled" : "disabled", - ret); - return ret; - } else - dev_dbg(fe->dvb->device, - "link %s->%s was %s\n", - source->name, sink->name, - flags ? "ENABLED" : "disabled"); - } - return 0; -} -#endif - static int dvb_frontend_thread(void *data) { struct dvb_frontend *fe = data; struct dvb_frontend_private *fepriv = fe->frontend_priv; enum fe_status s; enum dvbfe_algo algo; -#ifdef CONFIG_MEDIA_CONTROLLER_DVB - int ret; -#endif - bool re_tune = false; bool semheld = false;
@@ -710,20 +610,6 @@ static int dvb_frontend_thread(void *data) fepriv->wakeup = 0; fepriv->reinitialise = 0;
-#ifdef CONFIG_MEDIA_CONTROLLER_DVB - ret = dvb_enable_media_tuner(fe); - if (ret) { - /* FIXME: return an error if it fails */ - dev_info(fe->dvb->device, - "proceeding with FE task\n"); - } else if (fepriv->pipe_start_entity) { - ret = media_entity_pipeline_start(fepriv->pipe_start_entity, - &fepriv->pipe); - if (ret) - return ret; - } -#endif - dvb_frontend_init(fe);
set_freezable(); @@ -833,12 +719,6 @@ restart: } }
-#ifdef CONFIG_MEDIA_CONTROLLER_DVB - if (fepriv->pipe_start_entity) - media_entity_pipeline_stop(fepriv->pipe_start_entity); - fepriv->pipe_start_entity = NULL; -#endif - if (dvb_powerdown_on_sleep) { if (fe->ops.set_voltage) fe->ops.set_voltage(fe, SEC_VOLTAGE_OFF); @@ -2616,6 +2496,17 @@ static int dvb_frontend_open(struct inode *inode, struct file *file) fepriv->tone = -1; fepriv->voltage = -1;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB + if (fe->dvb->mdev && fe->dvb->mdev->enable_source) { + ret = fe->dvb->mdev->enable_source(dvbdev->entity, + &fe->pipe); + if (ret) { + dev_err(fe->dvb->device, + "Tuner is busy. Error %d\n", ret); + goto err1; + } + } +#endif ret = dvb_frontend_start (fe); if (ret) goto err2; @@ -2659,6 +2550,10 @@ static int dvb_frontend_release(struct inode *inode, struct file *file) wake_up(&fepriv->wait_queue); if (fe->exit != DVB_FE_NO_EXIT) wake_up(&dvbdev->wait_queue); +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + if (fe->dvb->mdev && fe->dvb->mdev->disable_source) + fe->dvb->mdev->disable_source(dvbdev->entity); +#endif if (fe->ops.ts_bus_ctrl) fe->ops.ts_bus_ctrl(fe, 0); } diff --git a/drivers/media/dvb-core/dvb_frontend.h b/drivers/media/dvb-core/dvb_frontend.h index 4816947..03d328e 100644 --- a/drivers/media/dvb-core/dvb_frontend.h +++ b/drivers/media/dvb-core/dvb_frontend.h @@ -428,6 +428,9 @@ struct dvb_frontend { int (*callback)(void *adapter_priv, int component, int cmd, int arg); int id; unsigned int exit; +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + struct media_pipeline pipe; +#endif };
extern int dvb_register_frontend(struct dvb_adapter *dvb,
au0828_enable_analog_tuner() is no longer needed with v4l2-core and au0828-video invoking enable_source and disable_source handlers. In addition, it is unnecessary to check for tuner availability in queue_setup() as v4l2-core handles the tuner availability checks.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/usb/au0828/au0828-video.c | 62 --------------------------------- 1 file changed, 62 deletions(-)
diff --git a/drivers/media/usb/au0828/au0828-video.c b/drivers/media/usb/au0828/au0828-video.c index 939b2ad..b63ae78 100644 --- a/drivers/media/usb/au0828/au0828-video.c +++ b/drivers/media/usb/au0828/au0828-video.c @@ -637,66 +637,6 @@ static inline int au0828_isoc_copy(struct au0828_dev *dev, struct urb *urb) return rc; }
-static int au0828_enable_analog_tuner(struct au0828_dev *dev) -{ -#ifdef CONFIG_MEDIA_CONTROLLER - struct media_device *mdev = dev->media_dev; - struct media_entity *entity, *source; - struct media_link *link, *found_link = NULL; - int i, ret, active_links = 0; - - if (!mdev || !dev->decoder) - return 0; - - /* - * This will find the tuner that is connected into the decoder. - * Technically, this is not 100% correct, as the device may be - * using an analog input instead of the tuner. However, as we can't - * do DVB streaming while the DMA engine is being used for V4L2, - * this should be enough for the actual needs. - */ - for (i = 0; i < dev->decoder->num_links; i++) { - link = &dev->decoder->links[i]; - if (link->sink->entity == dev->decoder) { - found_link = link; - if (link->flags & MEDIA_LNK_FL_ENABLED) - active_links++; - break; - } - } - - if (active_links == 1 || !found_link) - return 0; - - source = found_link->source->entity; - for (i = 0; i < source->num_links; i++) { - struct media_entity *sink; - int flags = 0; - - link = &source->links[i]; - sink = link->sink->entity; - - if (sink == entity) - flags = MEDIA_LNK_FL_ENABLED; - - ret = media_entity_setup_link(link, flags); - if (ret) { - pr_err( - "Couldn't change link %s->%s to %s. Error %d\n", - source->name, sink->name, - flags ? "enabled" : "disabled", - ret); - return ret; - } else - au0828_isocdbg( - "link %s->%s was %s\n", - source->name, sink->name, - flags ? "ENABLED" : "disabled"); - } -#endif - return 0; -} - static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) @@ -712,8 +652,6 @@ static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, *nplanes = 1; sizes[0] = size;
- au0828_enable_analog_tuner(dev); - return 0; }
au0828 is changed to use v4l_enable_media_tuner() to check for tuner availability from vidioc_g_tuner(), and au0828_v4l2_close(), before changing tuner settings. If tuner isn't free, return busy condition from vidioc_g_tuner() and in au0828_v4l2_close() tuner is left untouched without powering down to save energy.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/usb/au0828/au0828-video.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/media/usb/au0828/au0828-video.c b/drivers/media/usb/au0828/au0828-video.c index b63ae78..6680aeb 100644 --- a/drivers/media/usb/au0828/au0828-video.c +++ b/drivers/media/usb/au0828/au0828-video.c @@ -1005,8 +1005,12 @@ static int au0828_v4l2_close(struct file *filp) goto end;
if (dev->users == 1) { - /* Save some power by putting tuner to sleep */ - v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0); + /* Save some power by putting tuner to sleep, if it is free */ + /* What happens when radio is using tuner?? */ + ret = v4l_enable_media_tuner(vdev); + if (ret == 0) + v4l2_device_call_all(&dev->v4l2_dev, 0, core, + s_power, 0); dev->std_set_in_tuner_core = 0;
/* When close the device, set the usb intf0 into alt0 to free @@ -1407,10 +1411,16 @@ static int vidioc_s_audio(struct file *file, void *priv, const struct v4l2_audio static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) { struct au0828_dev *dev = video_drvdata(file); + struct video_device *vfd = video_devdata(file); + int ret;
if (t->index != 0) return -EINVAL;
+ ret = v4l_enable_media_tuner(vfd); + if (ret) + return ret; + dprintk(1, "%s called std_set %d dev_state %d\n", __func__, dev->std_set_in_tuner_core, dev->dev_state);
Change au0828 to use Managed Media Controller API to coordinate creating/deleting media device on parent usb device it shares with the snd-usb-audio driver. With this change, au0828 uses media_device_get_devres() to allocate a new media device devres or return an existing one, if it finds one.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/usb/au0828/au0828-core.c | 45 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index 0b8dc49..63e31e2 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -132,9 +132,9 @@ static void au0828_unregister_media_device(struct au0828_dev *dev) {
#ifdef CONFIG_MEDIA_CONTROLLER - if (dev->media_dev) { + if (dev->media_dev && + media_devnode_is_registered(&dev->media_dev->devnode)) { media_device_unregister(dev->media_dev); - kfree(dev->media_dev); dev->media_dev = NULL; } #endif @@ -204,31 +204,30 @@ static void au0828_media_device_register(struct au0828_dev *dev, struct media_device *mdev; int ret;
- mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); + mdev = media_device_get_devres(&udev->dev); if (!mdev) return;
- mdev->dev = &udev->dev; - - if (!dev->board.name) - strlcpy(mdev->model, "unknown au0828", sizeof(mdev->model)); - else - strlcpy(mdev->model, dev->board.name, sizeof(mdev->model)); - if (udev->serial) - strlcpy(mdev->serial, udev->serial, sizeof(mdev->serial)); - strcpy(mdev->bus_info, udev->devpath); - mdev->hw_revision = le16_to_cpu(udev->descriptor.bcdDevice); - mdev->driver_version = LINUX_VERSION_CODE; - - ret = media_device_register(mdev); - if (ret) { - pr_err( - "Couldn't create a media device. Error: %d\n", - ret); - kfree(mdev); - return; + if (!media_devnode_is_registered(&mdev->devnode)) { + /* register media device */ + mdev->dev = &udev->dev; + + if (udev->product) + strlcpy(mdev->model, udev->product, + sizeof(mdev->model)); + if (udev->serial) + strlcpy(mdev->serial, udev->serial, + sizeof(mdev->serial)); + strcpy(mdev->bus_info, udev->devpath); + mdev->hw_revision = le16_to_cpu(udev->descriptor.bcdDevice); + ret = media_device_register(mdev); + if (ret) { + dev_err(&udev->dev, + "Couldn't create a media device. Error: %d\n", + ret); + return; + } } - dev->media_dev = mdev; #endif }
au0828 registers entity_notify hook to create media graph for the device. This handler runs whenvere a new entity gets added to the media device. It creates necessary links from video, vbi, and ALSA entities to decoder and links tuner and decoder entities. As this handler runs as entities get added, it has to maintain state on the links it already created. New fields are added to au0828_dev to keep this state information. entity_notify gets unregistered before media_device unregister.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/usb/au0828/au0828-core.c | 118 +++++++++++++++++++++------------ drivers/media/usb/au0828/au0828.h | 6 ++ 2 files changed, 81 insertions(+), 43 deletions(-)
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index 63e31e2..fcff2e2 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -134,6 +134,8 @@ static void au0828_unregister_media_device(struct au0828_dev *dev) #ifdef CONFIG_MEDIA_CONTROLLER if (dev->media_dev && media_devnode_is_registered(&dev->media_dev->devnode)) { + media_device_unregister_entity_notify(dev->media_dev, + &dev->entity_notify); media_device_unregister(dev->media_dev); dev->media_dev = NULL; } @@ -197,6 +199,75 @@ static void au0828_usb_disconnect(struct usb_interface *interface) au0828_usb_release(dev); }
+static void au0828_create_media_graph(struct media_entity *new, + void *notify_data) +{ +#ifdef CONFIG_MEDIA_CONTROLLER + struct au0828_dev *dev = (struct au0828_dev *) notify_data; + struct media_device *mdev = dev->media_dev; + struct media_entity *entity, *tuner = NULL, *decoder = NULL; + struct media_entity *alsa_capture = NULL; + int ret; + + if (!mdev) + return; + + if (dev->tuner_linked && dev->vdev_linked && dev->vbi_linked && + dev->alsa_capture_linked) + return; + + media_device_for_each_entity(entity, mdev) { + switch (entity->type) { + case MEDIA_ENT_T_V4L2_SUBDEV_TUNER: + tuner = entity; + break; + case MEDIA_ENT_T_V4L2_SUBDEV_DECODER: + decoder = entity; + break; + case MEDIA_ENT_T_DEVNODE_ALSA_CAPTURE: + alsa_capture = entity; + break; + } + } + + if (!decoder) + return; + + /* save tuner for using in enable_source disable_source handlers + * avoid graph walk in those handlers. Note that decoder is + * saved during v4l2_dev registration */ + + if (tuner && !dev->tuner_linked) { + dev->tuner = tuner; + ret = media_entity_create_link(tuner, 0, decoder, 0, + MEDIA_LNK_FL_ENABLED); + if (ret == 0) + dev->tuner_linked = 1; + } + if (dev->vdev.entity.parent && !dev->vdev_linked) { + ret = media_entity_create_link(decoder, AU8522_PAD_VID_OUT, + &dev->vdev.entity, 0, + MEDIA_LNK_FL_ENABLED); + if (ret == 0) + dev->vdev_linked = 1; + } + if (dev->vbi_dev.entity.parent && !dev->vbi_linked) { + ret = media_entity_create_link(decoder, AU8522_PAD_VBI_OUT, + &dev->vbi_dev.entity, 0, + MEDIA_LNK_FL_ENABLED); + if (ret == 0) + dev->vbi_linked = 1; + } + if (alsa_capture && !dev->alsa_capture_linked) { + ret = media_entity_create_link(decoder, AU8522_PAD_AUDIO_OUT, + alsa_capture, 0, + MEDIA_LNK_FL_ENABLED); + if (ret == 0) + dev->alsa_capture_linked = 1; + } +#endif +} + static void au0828_media_device_register(struct au0828_dev *dev, struct usb_device *udev) { @@ -227,52 +298,15 @@ static void au0828_media_device_register(struct au0828_dev *dev, ret); return; } + /* register entity_notify callback */ + dev->entity_notify.notify_data = (void *) dev; + dev->entity_notify.notify = au0828_create_media_graph; + media_device_register_entity_notify(mdev, &dev->entity_notify); } dev->media_dev = mdev; #endif }
- -static void au0828_create_media_graph(struct au0828_dev *dev) -{ -#ifdef CONFIG_MEDIA_CONTROLLER - struct media_device *mdev = dev->media_dev; - struct media_entity *entity; - struct media_entity *tuner = NULL, *decoder = NULL; - - if (!mdev) - return; - - media_device_for_each_entity(entity, mdev) { - switch (entity->type) { - case MEDIA_ENT_T_V4L2_SUBDEV_TUNER: - tuner = entity; - break; - case MEDIA_ENT_T_V4L2_SUBDEV_DECODER: - decoder = entity; - break; - } - } - - /* Analog setup, using tuner as a link */ - - if (!decoder) - return; - - if (tuner) - media_entity_create_link(tuner, 0, decoder, 0, - MEDIA_LNK_FL_ENABLED); - if (dev->vdev.entity.parent) - media_entity_create_link(decoder, AU8522_PAD_VID_OUT, - &dev->vdev.entity, 0, - MEDIA_LNK_FL_ENABLED); - if (dev->vbi_dev.entity.parent) - media_entity_create_link(decoder, AU8522_PAD_VBI_OUT, - &dev->vbi_dev.entity, 0, - MEDIA_LNK_FL_ENABLED); -#endif -} - static int au0828_usb_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -385,8 +419,6 @@ static int au0828_usb_probe(struct usb_interface *interface,
mutex_unlock(&dev->lock);
- au0828_create_media_graph(dev); - return retval; }
diff --git a/drivers/media/usb/au0828/au0828.h b/drivers/media/usb/au0828/au0828.h index d3644b3..08cc7b8 100644 --- a/drivers/media/usb/au0828/au0828.h +++ b/drivers/media/usb/au0828/au0828.h @@ -281,6 +281,12 @@ struct au0828_dev { struct media_device *media_dev; struct media_pad video_pad, vbi_pad; struct media_entity *decoder; + struct media_entity *tuner; + struct media_entity_notify entity_notify; + bool tuner_linked; + bool vdev_linked; + bool vbi_linked; + bool alsa_capture_linked; #endif };
Implements enable_source and disable_source handlers for other drivers (v4l2-core, dvb-core, and ALSA) to use to check for tuner connected to the decoder and activate the link if tuner is free, and deactivate and free the tuner when it is no longer needed.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/usb/au0828/au0828-core.c | 169 +++++++++++++++++++++++++++++++++ drivers/media/usb/au0828/au0828.h | 2 + 2 files changed, 171 insertions(+)
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index fcff2e2..ce9d5d4 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -268,6 +268,164 @@ static void au0828_create_media_graph(struct media_entity *new, #endif }
+static int au0828_enable_source(struct media_entity *entity, + struct media_pipeline *pipe) +{ +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_entity *source; + struct media_entity *sink; + struct media_link *link, *found_link = NULL; + int i, ret = 0; + struct media_device *mdev = entity->parent; + struct au0828_dev *dev; + + if (!mdev) + return -ENODEV; + + /* for ALSA and Video entities, source is the decoder */ + mutex_lock(&mdev->graph_mutex); + + dev = mdev->source_priv; + if (!dev->tuner || !dev->decoder) { + ret = -ENODEV; + goto end; + } + + /* + * For ALSA and V4L2 entity, find the link to which decoder + * is the sink. Look for an active link between decoder and + * tuner, if one exists, nothing to do. If not, look for any + * active links between tuner and any other entity. If one + * exists, tuner is busy. If tuner is free, setup link and + * start pipeline from source (tuner). + * For DVB FE entity, the source for the link is the tuner. + * Check if tuner is available and setup link and start + * pipeline. + */ + if (entity->type != MEDIA_ENT_T_DEVNODE_DVB_FE) + sink = dev->decoder; + else + sink = entity; + + /* Is an active link between sink and tuner */ + if (dev->active_link) { + if (dev->active_link->sink->entity == sink && + dev->active_link->source->entity == dev->tuner) { + ret = 0; + goto end; + } else { + ret = -EBUSY; + goto end; + } + } + + for (i = 0; i < sink->num_links; i++) { + link = &sink->links[i]; + /* Used to check just the sink, check source too - works?? */ + if (link->sink->entity == sink && + link->source->entity == dev->tuner) { + found_link = link; + break; + } + } + + if (!found_link) { + ret = -ENODEV; + goto end; + } + + source = found_link->source->entity; + for (i = 0; i < source->num_links; i++) { + struct media_entity *find_sink; + int flags = 0; + + link = &source->links[i]; + find_sink = link->sink->entity; + + if (find_sink == sink) + flags = MEDIA_LNK_FL_ENABLED; + + ret = __media_entity_setup_link(link, flags); + if (ret) { + pr_err( + "Change tuner link %s->%s to %s. Error %d\n", + source->name, find_sink->name, + flags ? "enabled" : "disabled", + ret); + goto end; + } + /* start pipeline */ + if (find_sink == sink) { + dev->active_link = link; + ret = __media_entity_pipeline_start(entity, pipe); + if (ret) { + pr_err("Start Pipeline: %s->%s Error %d\n", + source->name, entity->name, ret); + goto end; + } + /* save link owner to avoid audio deactivating + video owned link from disable_source and + vice versa */ + dev->active_link_owner = entity; + pr_info("Started Pipeline: %s->%s ret %d\n", + source->name, entity->name, ret); + } + } +end: + mutex_unlock(&mdev->graph_mutex); + return ret; +#endif + return 0; +} + +static void au0828_disable_source(struct media_entity *entity) +{ +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_entity *sink; + int ret = 0; + struct media_device *mdev = entity->parent; + struct au0828_dev *dev; + + if (!mdev) + return; + + mutex_lock(&mdev->graph_mutex); + + dev = mdev->source_priv; + if (!dev->tuner || !dev->decoder || !dev->active_link) { + ret = -ENODEV; + goto end; + } + + if (entity->type != MEDIA_ENT_T_DEVNODE_DVB_FE) + sink = dev->decoder; + else + sink = entity; + + /* link is active - stop pipeline from source (tuner) */ + if (dev->active_link && dev->active_link->sink->entity == sink && + dev->active_link->source->entity == dev->tuner) { + /* prevent video from deactivating link when audio + has active pipeline */ + if (dev->active_link_owner != entity) + goto end; + __media_entity_pipeline_stop(entity); + pr_info("Stopped Pipeline: %s->%s entity %s\n", + dev->active_link->source->entity->name, + dev->active_link->sink->entity->name, + entity->name); + ret = __media_entity_setup_link(dev->active_link, 0); + if (ret) + pr_err("Deactive link Error %d\n", ret); + dev->active_link = NULL; + dev->active_link_owner = NULL; + } + +end: + mutex_unlock(&mdev->graph_mutex); +#endif +} + static void au0828_media_device_register(struct au0828_dev *dev, struct usb_device *udev) { @@ -291,6 +449,10 @@ static void au0828_media_device_register(struct au0828_dev *dev, sizeof(mdev->serial)); strcpy(mdev->bus_info, udev->devpath); mdev->hw_revision = le16_to_cpu(udev->descriptor.bcdDevice); + /* Set enable_source and disable_source handlers and data */ + mdev->source_priv = (void *) dev; + mdev->enable_source = au0828_enable_source; + mdev->disable_source = au0828_disable_source; ret = media_device_register(mdev); if (ret) { dev_err(&udev->dev, @@ -303,6 +465,13 @@ static void au0828_media_device_register(struct au0828_dev *dev, dev->entity_notify.notify = au0828_create_media_graph; media_device_register_entity_notify(mdev, &dev->entity_notify); } + /* If ALSA registered the media device - set enable_source */ + if (!mdev->enable_source) { + /* Set enable_source and disable_source handlers and data */ + mdev->source_priv = (void *) dev; + mdev->enable_source = au0828_enable_source; + mdev->disable_source = au0828_disable_source; + } dev->media_dev = mdev; #endif } diff --git a/drivers/media/usb/au0828/au0828.h b/drivers/media/usb/au0828/au0828.h index 08cc7b8..f64dbbb 100644 --- a/drivers/media/usb/au0828/au0828.h +++ b/drivers/media/usb/au0828/au0828.h @@ -287,6 +287,8 @@ struct au0828_dev { bool vdev_linked; bool vbi_linked; bool alsa_capture_linked; + struct media_link *active_link; + struct media_entity *active_link_owner; #endif };
When enable_source finds the tuner busy, do dvb_generic_release(). In addition, when dvb_frontend_start() fails, call disable_source to release tuner.
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- drivers/media/dvb-core/dvb_frontend.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c index c06dd61..67e30ae 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -2503,13 +2503,13 @@ static int dvb_frontend_open(struct inode *inode, struct file *file) if (ret) { dev_err(fe->dvb->device, "Tuner is busy. Error %d\n", ret); - goto err1; + goto err2; } } #endif ret = dvb_frontend_start (fe); if (ret) - goto err2; + goto err3;
/* empty event queue */ fepriv->events.eventr = fepriv->events.eventw = 0; @@ -2519,6 +2519,11 @@ static int dvb_frontend_open(struct inode *inode, struct file *file) mutex_unlock (&adapter->mfe_lock); return ret;
+err3: +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + if (fe->dvb->mdev && fe->dvb->mdev->disable_source) + fe->dvb->mdev->disable_source(dvbdev->entity); +#endif err2: dvb_generic_release(inode, file); err1:
Change ALSA driver to use Managed ~media Managed Controller API to share tuner with DVB and V4L2 drivers that control AU0828 media device. Media device is created based on a newly added field value in the struct snd_usb_audio_quirk. Using this approach, the media controller API usage can be added for a specific device. In this patch, Media Controller API is enabled for AU0828 hw. snd_usb_create_quirk() will check this new field, if set will create a media device using media_device_get_devres() interface.
media_device_get_devres() will allocate a new media device devres or return an existing one, if it finds one.
During probe, media usb driver could have created the media device devres. It will then register the media device if it isn't already registered. Media device unregister is done from usb_audio_disconnect().
During probe, media usb driver could have created the media device devres. It will then register the media device if it isn't already registered. Media device unregister is done from usb_audio_disconnect().
New structure media_ctl is added to group the new fields to support media entity and links. This new structure is added to struct snd_usb_substream.
A new entity_notify hook and a new ALSA capture media entity are registered from snd_usb_pcm_open() after setting up hardware information for the PCM device.
When a new entity is registered, Media Controller API interface media_device_register_entity() invokes all registered entity_notify hooks for the media device. ALSA entity_notify hook parses all the entity list to find a link from decoder it ALSA entity. This indicates that the bridge driver created a link from decoder to ALSA capture entity.
ALSA will attempt to enable the tuner to link the tuner to the decoder calling enable_source handler if one is provided by the bridge driver prior to starting Media pipeline from snd_usb_hw_params(). If enable_source returns with tuner busy condition, then snd_usb_hw_params() will fail with -EBUSY. Media pipeline is stopped from snd_usb_hw_free().
Signed-off-by: Shuah Khan shuahkh@osg.samsung.com --- sound/usb/Makefile | 15 +++- sound/usb/card.c | 5 ++ sound/usb/card.h | 1 + sound/usb/media.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/media.h | 51 ++++++++++++++ sound/usb/pcm.c | 29 ++++++-- sound/usb/quirks-table.h | 1 + sound/usb/quirks.c | 9 ++- sound/usb/stream.c | 2 + sound/usb/usbaudio.h | 1 + 10 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 sound/usb/media.c create mode 100644 sound/usb/media.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2d2d122..665fdd9 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -2,6 +2,18 @@ # Makefile for ALSA #
+# Media Controller +ifeq ($(CONFIG_MEDIA_CONTROLLER),y) + ifeq ($(CONFIG_MEDIA_SUPPORT),y) + KBUILD_CFLAGS += -DUSE_MEDIA_CONTROLLER + endif + ifeq ($(CONFIG_MEDIA_SUPPORT_MODULE),y) + ifeq ($(MODULE),y) + KBUILD_CFLAGS += -DUSE_MEDIA_CONTROLLER + endif + endif +endif + snd-usb-audio-objs := card.o \ clock.o \ endpoint.o \ @@ -13,7 +25,8 @@ snd-usb-audio-objs := card.o \ pcm.o \ proc.o \ quirks.o \ - stream.o + stream.o \ + media.o
snd-usbmidi-lib-objs := midi.o
diff --git a/sound/usb/card.c b/sound/usb/card.c index 1fab977..469d2bf 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -66,6 +66,7 @@ #include "format.h" #include "power.h" #include "stream.h" +#include "media.h"
MODULE_AUTHOR("Takashi Iwai tiwai@suse.de"); MODULE_DESCRIPTION("USB Audio"); @@ -619,6 +620,10 @@ static void usb_audio_disconnect(struct usb_interface *intf) list_for_each_entry(mixer, &chip->mixer_list, list) { snd_usb_mixer_disconnect(mixer); } + /* Nice to check quirk && quirk->media_device + * need some special handlings. Doesn't look like + * we have access to quirk here */ + media_device_delete(intf); }
chip->num_interfaces--; diff --git a/sound/usb/card.h b/sound/usb/card.h index ef580b4..235a85f 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -155,6 +155,7 @@ struct snd_usb_substream { } dsd_dop;
bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */ + void *media_ctl; };
struct snd_usb_stream { diff --git a/sound/usb/media.c b/sound/usb/media.c new file mode 100644 index 0000000..97c3b2d --- /dev/null +++ b/sound/usb/media.c @@ -0,0 +1,178 @@ +/* + * media.c - Media Controller specific ALSA driver code + * + * Copyright (c) 2015 Shuah Khan shuahkh@osg.samsung.com + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * + * This file is released under the GPLv2. + */ + +/* + * This file adds Media Controller support to ALSA driver + * to use the Media Controller API to share tuner with DVB + * and V4L2 drivers that control media device. Media device + * is created based on existing quirks framework. Using this + * approach, the media controller API usage can be added for + * a specific device. +*/ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> +#include <linux/module.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> + +#include "usbaudio.h" +#include "card.h" +#include "midi.h" +#include "mixer.h" +#include "proc.h" +#include "quirks.h" +#include "endpoint.h" +#include "helper.h" +#include "debug.h" +#include "pcm.h" +#include "format.h" +#include "power.h" +#include "stream.h" +#include "media.h" + +#ifdef USE_MEDIA_CONTROLLER +int media_device_init(struct snd_usb_audio *chip, struct usb_interface *iface) +{ + struct media_device *mdev; + struct usb_device *usbdev = interface_to_usbdev(iface); + int ret; + + mdev = media_device_get_devres(&usbdev->dev); + if (!mdev) + return -ENOMEM; + if (!media_devnode_is_registered(&mdev->devnode)) { + /* register media device */ + mdev->dev = &usbdev->dev; + if (usbdev->product) + strlcpy(mdev->model, usbdev->product, + sizeof(mdev->model)); + if (usbdev->serial) + strlcpy(mdev->serial, usbdev->serial, + sizeof(mdev->serial)); + strcpy(mdev->bus_info, usbdev->devpath); + mdev->hw_revision = le16_to_cpu(usbdev->descriptor.bcdDevice); + ret = media_device_register(mdev); + if (ret) { + dev_err(&usbdev->dev, + "Couldn't create a media device. Error: %d\n", + ret); + return ret; + } + } + return 0; +} + +void media_device_delete(struct usb_interface *iface) +{ + struct media_device *mdev; + struct usb_device *usbdev = interface_to_usbdev(iface); + + mdev = media_device_find_devres(&usbdev->dev); + if (mdev && media_devnode_is_registered(&mdev->devnode)) + media_device_unregister(mdev); +} + +static int media_enable_source(struct media_ctl *mctl) +{ + if (mctl && mctl->media_dev->enable_source) + return mctl->media_dev->enable_source(&mctl->media_entity, + &mctl->media_pipe); + return 0; +} + +static void media_disable_source(struct media_ctl *mctl) +{ + if (mctl && mctl->media_dev->disable_source) + mctl->media_dev->disable_source(&mctl->media_entity); +} + +int media_stream_init(struct snd_usb_substream *subs, struct snd_pcm *pcm, + int stream) +{ + struct media_device *mdev; + struct media_ctl *mctl; + struct device *pcm_dev = &pcm->streams[stream].dev; + + mdev = media_device_find_devres(&subs->dev->dev); + if (!mdev) + return -ENODEV; + + if (subs->media_ctl) + return 0; + + /* allocate media_ctl */ + mctl = kzalloc(sizeof(struct media_ctl), GFP_KERNEL); + if (!mctl) + return -ENOMEM; + + subs->media_ctl = (void *) mctl; + mctl->media_dev = mdev; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + mctl->media_entity.type = MEDIA_ENT_T_DEVNODE_ALSA_PLAYBACK; + else + mctl->media_entity.type = MEDIA_ENT_T_DEVNODE_ALSA_CAPTURE; + mctl->media_entity.name = pcm->name; + mctl->media_entity.info.dev.major = MAJOR(pcm_dev->devt); + mctl->media_entity.info.dev.minor = MINOR(pcm_dev->devt); + mctl->media_pad.flags = MEDIA_PAD_FL_SINK; + media_entity_init(&mctl->media_entity, 1, &mctl->media_pad, 0); + return media_device_register_entity(mctl->media_dev, + &mctl->media_entity); +} + +void media_stream_delete(struct snd_usb_substream *subs) +{ + struct media_ctl *mctl = (struct media_ctl *) subs->media_ctl; + + if (mctl && mctl->media_dev) { + struct media_device *mdev; + + mdev = media_device_find_devres(&subs->dev->dev); + if (mdev) { + media_entity_remove_links(&mctl->media_entity); + media_device_unregister_entity(&mctl->media_entity); + media_entity_cleanup(&mctl->media_entity); + } + mctl->media_dev = NULL; + kfree(mctl); + subs->media_ctl = NULL; + } +} + +int media_start_pipeline(struct snd_usb_substream *subs) +{ + struct media_ctl *mctl = (struct media_ctl *) subs->media_ctl; + + if (mctl) + return media_enable_source(mctl); + return 0; +} + +void media_stop_pipeline(struct snd_usb_substream *subs) +{ + struct media_ctl *mctl = (struct media_ctl *) subs->media_ctl; + + if (mctl) + media_disable_source(mctl); +} +#endif diff --git a/sound/usb/media.h b/sound/usb/media.h new file mode 100644 index 0000000..90587aa --- /dev/null +++ b/sound/usb/media.h @@ -0,0 +1,51 @@ +/* + * media.h - Media Controller specific ALSA driver code + * + * Copyright (c) 2015 Shuah Khan shuahkh@osg.samsung.com + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * + * This file is released under the GPLv2. + */ + +/* + * This file adds Media Controller support to ALSA driver + * to use the Media Controller API to share tuner with DVB + * and V4L2 drivers that control media device. Media device + * is created based on existing quirks framework. Using this + * approach, the media controller API usage can be added for + * a specific device. +*/ +#ifndef __MEDIA_H + +#ifdef USE_MEDIA_CONTROLLER +#include <media/media-device.h> +#include <media/media-entity.h> + +struct media_ctl { + struct media_device *media_dev; + struct media_entity media_entity; + struct media_pad media_pad; + struct media_pipeline media_pipe; +}; + +int media_device_init(struct snd_usb_audio *chip, struct usb_interface *iface); +void media_device_delete(struct usb_interface *iface); +int media_stream_init(struct snd_usb_substream *subs, struct snd_pcm *pcm, + int stream); +void media_stream_delete(struct snd_usb_substream *subs); +int media_start_pipeline(struct snd_usb_substream *subs); +void media_stop_pipeline(struct snd_usb_substream *subs); +#else +static inline int media_device_init(struct snd_usb_audio *chip, + struct usb_interface *iface) + { return 0; } +static inline void media_device_delete(struct usb_interface *iface) { } +static inline int media_stream_init(struct snd_usb_substream *subs, + struct snd_pcm *pcm, int stream) + { return 0; } +static inline void media_stream_delete(struct snd_usb_substream *subs) { } +static inline int media_start_pipeline(struct snd_usb_substream *subs) + { return 0; } +static inline void media_stop_pipeline(struct snd_usb_substream *subs) { } +#endif +#endif /* __MEDIA_H */ diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index b4ef410..0cba02e 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -35,6 +35,7 @@ #include "pcm.h" #include "clock.h" #include "power.h" +#include "media.h"
#define SUBSTREAM_FLAG_DATA_EP_STARTED 0 #define SUBSTREAM_FLAG_SYNC_EP_STARTED 1 @@ -687,10 +688,14 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, struct audioformat *fmt; int ret;
+ ret = media_start_pipeline(subs); + if (ret) + return ret; + ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); if (ret < 0) - return ret; + goto err_ret;
subs->pcm_format = params_format(hw_params); subs->period_bytes = params_period_bytes(hw_params); @@ -704,23 +709,29 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, dev_dbg(&subs->dev->dev, "cannot set format: format = %#x, rate = %d, channels = %d\n", subs->pcm_format, subs->cur_rate, subs->channels); - return -EINVAL; + ret = -EINVAL; + goto err_ret; }
down_read(&subs->stream->chip->shutdown_rwsem); - if (subs->stream->chip->shutdown) + if (subs->stream->chip->shutdown) { ret = -ENODEV; - else + goto err_ret; + } else ret = set_format(subs, fmt); up_read(&subs->stream->chip->shutdown_rwsem); if (ret < 0) - return ret; + goto err_ret;
subs->interface = fmt->iface; subs->altset_idx = fmt->altset_idx; subs->need_setup_ep = true;
return 0; + +err_ret: + media_stop_pipeline(subs); + return ret; }
/* @@ -732,6 +743,7 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream) { struct snd_usb_substream *subs = substream->runtime->private_data;
+ media_stop_pipeline(subs); subs->cur_audiofmt = NULL; subs->cur_rate = 0; subs->period_bytes = 0; @@ -1195,6 +1207,7 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_usb_substream *subs = &as->substream[direction]; + int ret;
subs->interface = -1; subs->altset_idx = 0; @@ -1208,7 +1221,10 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) subs->dsd_dop.channel = 0; subs->dsd_dop.marker = 1;
- return setup_hw_info(runtime, subs); + ret = setup_hw_info(runtime, subs); + if (ret == 0) + ret = media_stream_init(subs, as->pcm, direction); + return ret; }
static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) @@ -1217,6 +1233,7 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) struct snd_usb_substream *subs = &as->substream[direction];
stop_endpoints(subs, true); + media_stop_pipeline(subs);
if (!as->chip->shutdown && subs->interface >= 0) { usb_set_interface(subs->dev, subs->interface, 0); diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 2f6d3e9..51c544f 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2798,6 +2798,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .product_name = pname, \ .ifnum = QUIRK_ANY_INTERFACE, \ .type = QUIRK_AUDIO_ALIGN_TRANSFER, \ + .media_device = 1, \ } \ }
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 7c5a701..8190069 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -36,6 +36,7 @@ #include "pcm.h" #include "clock.h" #include "stream.h" +#include "media.h"
/* * handle the quirks for the contained interfaces @@ -541,13 +542,19 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip, [QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk, [QUIRK_AUDIO_STANDARD_MIXER] = create_standard_mixer_quirk, }; + int ret;
+ if (quirk->media_device) { + /* don't want to fail when media_device_init() doesn't work */ + media_device_init(chip, iface); + } if (quirk->type < QUIRK_TYPE_COUNT) { - return quirk_funcs[quirk->type](chip, iface, driver, quirk); + ret = quirk_funcs[quirk->type](chip, iface, driver, quirk); } else { usb_audio_err(chip, "invalid quirk type %d\n", quirk->type); return -ENXIO; } + return ret; }
/* diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 310a382..6d01c47 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -36,6 +36,7 @@ #include "format.h" #include "clock.h" #include "stream.h" +#include "media.h"
/* * free a substream @@ -52,6 +53,7 @@ static void free_substream(struct snd_usb_substream *subs) kfree(fp); } kfree(subs->rate_list.list); + media_stream_delete(subs); }
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 91d0380..c2dbf1d 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -108,6 +108,7 @@ struct snd_usb_audio_quirk { const char *product_name; int16_t ifnum; uint16_t type; + bool media_device; const void *data; };
participants (1)
-
Shuah Khan