|
@@ -0,0 +1,318 @@
|
|
|
+/*
|
|
|
+ * media.c - Media Controller specific ALSA driver code
|
|
|
+ *
|
|
|
+ * Copyright (c) 2016 Shuah Khan <shuahkh@osg.samsung.com>
|
|
|
+ * Copyright (c) 2016 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/mutex.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+#include <linux/usb.h>
|
|
|
+
|
|
|
+#include <sound/pcm.h>
|
|
|
+#include <sound/core.h>
|
|
|
+
|
|
|
+#include "usbaudio.h"
|
|
|
+#include "card.h"
|
|
|
+#include "mixer.h"
|
|
|
+#include "media.h"
|
|
|
+
|
|
|
+static int media_snd_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_snd_disable_source(struct media_ctl *mctl)
|
|
|
+{
|
|
|
+ if (mctl && mctl->media_dev->disable_source)
|
|
|
+ mctl->media_dev->disable_source(&mctl->media_entity);
|
|
|
+}
|
|
|
+
|
|
|
+int media_snd_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;
|
|
|
+ u32 intf_type;
|
|
|
+ int ret = 0;
|
|
|
+ u16 mixer_pad;
|
|
|
+ struct media_entity *entity;
|
|
|
+
|
|
|
+ mdev = subs->stream->chip->media_dev;
|
|
|
+ if (!mdev)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ if (subs->media_ctl)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /* allocate media_ctl */
|
|
|
+ mctl = kzalloc(sizeof(*mctl), GFP_KERNEL);
|
|
|
+ if (!mctl)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ mctl->media_dev = mdev;
|
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
+ intf_type = MEDIA_INTF_T_ALSA_PCM_PLAYBACK;
|
|
|
+ mctl->media_entity.function = MEDIA_ENT_F_AUDIO_PLAYBACK;
|
|
|
+ mctl->media_pad.flags = MEDIA_PAD_FL_SOURCE;
|
|
|
+ mixer_pad = 1;
|
|
|
+ } else {
|
|
|
+ intf_type = MEDIA_INTF_T_ALSA_PCM_CAPTURE;
|
|
|
+ mctl->media_entity.function = MEDIA_ENT_F_AUDIO_CAPTURE;
|
|
|
+ mctl->media_pad.flags = MEDIA_PAD_FL_SINK;
|
|
|
+ mixer_pad = 2;
|
|
|
+ }
|
|
|
+ mctl->media_entity.name = pcm->name;
|
|
|
+ media_entity_pads_init(&mctl->media_entity, 1, &mctl->media_pad);
|
|
|
+ ret = media_device_register_entity(mctl->media_dev,
|
|
|
+ &mctl->media_entity);
|
|
|
+ if (ret)
|
|
|
+ goto err1;
|
|
|
+
|
|
|
+ mctl->intf_devnode = media_devnode_create(mdev, intf_type, 0,
|
|
|
+ MAJOR(pcm_dev->devt),
|
|
|
+ MINOR(pcm_dev->devt));
|
|
|
+ if (!mctl->intf_devnode) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto err2;
|
|
|
+ }
|
|
|
+ mctl->intf_link = media_create_intf_link(&mctl->media_entity,
|
|
|
+ &mctl->intf_devnode->intf,
|
|
|
+ MEDIA_LNK_FL_ENABLED);
|
|
|
+ if (!mctl->intf_link) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto err3;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* create link between mixer and audio */
|
|
|
+ media_device_for_each_entity(entity, mdev) {
|
|
|
+ switch (entity->function) {
|
|
|
+ case MEDIA_ENT_F_AUDIO_MIXER:
|
|
|
+ ret = media_create_pad_link(entity, mixer_pad,
|
|
|
+ &mctl->media_entity, 0,
|
|
|
+ MEDIA_LNK_FL_ENABLED);
|
|
|
+ if (ret)
|
|
|
+ goto err4;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ subs->media_ctl = mctl;
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err4:
|
|
|
+ media_remove_intf_link(mctl->intf_link);
|
|
|
+err3:
|
|
|
+ media_devnode_remove(mctl->intf_devnode);
|
|
|
+err2:
|
|
|
+ media_device_unregister_entity(&mctl->media_entity);
|
|
|
+err1:
|
|
|
+ kfree(mctl);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+void media_snd_stream_delete(struct snd_usb_substream *subs)
|
|
|
+{
|
|
|
+ struct media_ctl *mctl = subs->media_ctl;
|
|
|
+
|
|
|
+ if (mctl && mctl->media_dev) {
|
|
|
+ struct media_device *mdev;
|
|
|
+
|
|
|
+ mdev = subs->stream->chip->media_dev;
|
|
|
+ if (mdev && media_devnode_is_registered(&mdev->devnode)) {
|
|
|
+ media_devnode_remove(mctl->intf_devnode);
|
|
|
+ media_device_unregister_entity(&mctl->media_entity);
|
|
|
+ media_entity_cleanup(&mctl->media_entity);
|
|
|
+ }
|
|
|
+ kfree(mctl);
|
|
|
+ subs->media_ctl = NULL;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int media_snd_start_pipeline(struct snd_usb_substream *subs)
|
|
|
+{
|
|
|
+ struct media_ctl *mctl = subs->media_ctl;
|
|
|
+
|
|
|
+ if (mctl)
|
|
|
+ return media_snd_enable_source(mctl);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void media_snd_stop_pipeline(struct snd_usb_substream *subs)
|
|
|
+{
|
|
|
+ struct media_ctl *mctl = subs->media_ctl;
|
|
|
+
|
|
|
+ if (mctl)
|
|
|
+ media_snd_disable_source(mctl);
|
|
|
+}
|
|
|
+
|
|
|
+int media_snd_mixer_init(struct snd_usb_audio *chip)
|
|
|
+{
|
|
|
+ struct device *ctl_dev = &chip->card->ctl_dev;
|
|
|
+ struct media_intf_devnode *ctl_intf;
|
|
|
+ struct usb_mixer_interface *mixer;
|
|
|
+ struct media_device *mdev = chip->media_dev;
|
|
|
+ struct media_mixer_ctl *mctl;
|
|
|
+ u32 intf_type = MEDIA_INTF_T_ALSA_CONTROL;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!mdev)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ ctl_intf = chip->ctl_intf_media_devnode;
|
|
|
+ if (!ctl_intf) {
|
|
|
+ ctl_intf = media_devnode_create(mdev, intf_type, 0,
|
|
|
+ MAJOR(ctl_dev->devt),
|
|
|
+ MINOR(ctl_dev->devt));
|
|
|
+ if (!ctl_intf)
|
|
|
+ return -ENOMEM;
|
|
|
+ chip->ctl_intf_media_devnode = ctl_intf;
|
|
|
+ }
|
|
|
+
|
|
|
+ list_for_each_entry(mixer, &chip->mixer_list, list) {
|
|
|
+
|
|
|
+ if (mixer->media_mixer_ctl)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* allocate media_mixer_ctl */
|
|
|
+ mctl = kzalloc(sizeof(*mctl), GFP_KERNEL);
|
|
|
+ if (!mctl)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ mctl->media_dev = mdev;
|
|
|
+ mctl->media_entity.function = MEDIA_ENT_F_AUDIO_MIXER;
|
|
|
+ mctl->media_entity.name = chip->card->mixername;
|
|
|
+ mctl->media_pad[0].flags = MEDIA_PAD_FL_SINK;
|
|
|
+ mctl->media_pad[1].flags = MEDIA_PAD_FL_SOURCE;
|
|
|
+ mctl->media_pad[2].flags = MEDIA_PAD_FL_SOURCE;
|
|
|
+ media_entity_pads_init(&mctl->media_entity, MEDIA_MIXER_PAD_MAX,
|
|
|
+ mctl->media_pad);
|
|
|
+ ret = media_device_register_entity(mctl->media_dev,
|
|
|
+ &mctl->media_entity);
|
|
|
+ if (ret) {
|
|
|
+ kfree(mctl);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ mctl->intf_link = media_create_intf_link(&mctl->media_entity,
|
|
|
+ &ctl_intf->intf,
|
|
|
+ MEDIA_LNK_FL_ENABLED);
|
|
|
+ if (!mctl->intf_link) {
|
|
|
+ media_device_unregister_entity(&mctl->media_entity);
|
|
|
+ media_entity_cleanup(&mctl->media_entity);
|
|
|
+ kfree(mctl);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+ mctl->intf_devnode = ctl_intf;
|
|
|
+ mixer->media_mixer_ctl = mctl;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void media_snd_mixer_delete(struct snd_usb_audio *chip)
|
|
|
+{
|
|
|
+ struct usb_mixer_interface *mixer;
|
|
|
+ struct media_device *mdev = chip->media_dev;
|
|
|
+
|
|
|
+ if (!mdev)
|
|
|
+ return;
|
|
|
+
|
|
|
+ list_for_each_entry(mixer, &chip->mixer_list, list) {
|
|
|
+ struct media_mixer_ctl *mctl;
|
|
|
+
|
|
|
+ mctl = mixer->media_mixer_ctl;
|
|
|
+ if (!mixer->media_mixer_ctl)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (media_devnode_is_registered(&mdev->devnode)) {
|
|
|
+ media_device_unregister_entity(&mctl->media_entity);
|
|
|
+ media_entity_cleanup(&mctl->media_entity);
|
|
|
+ }
|
|
|
+ kfree(mctl);
|
|
|
+ mixer->media_mixer_ctl = NULL;
|
|
|
+ }
|
|
|
+ if (media_devnode_is_registered(&mdev->devnode))
|
|
|
+ media_devnode_remove(chip->ctl_intf_media_devnode);
|
|
|
+ chip->ctl_intf_media_devnode = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+int media_snd_device_create(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 (!mdev->dev) {
|
|
|
+ /* 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);
|
|
|
+ media_device_init(mdev);
|
|
|
+ }
|
|
|
+ if (!media_devnode_is_registered(&mdev->devnode)) {
|
|
|
+ ret = media_device_register(mdev);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(&usbdev->dev,
|
|
|
+ "Couldn't register media device. Error: %d\n",
|
|
|
+ ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* save media device - avoid lookups */
|
|
|
+ chip->media_dev = mdev;
|
|
|
+
|
|
|
+ /* Create media entities for mixer and control dev */
|
|
|
+ ret = media_snd_mixer_init(chip);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(&usbdev->dev,
|
|
|
+ "Couldn't create media mixer entities. Error: %d\n",
|
|
|
+ ret);
|
|
|
+
|
|
|
+ /* clear saved media_dev */
|
|
|
+ chip->media_dev = NULL;
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void media_snd_device_delete(struct snd_usb_audio *chip)
|
|
|
+{
|
|
|
+ struct media_device *mdev = chip->media_dev;
|
|
|
+
|
|
|
+ media_snd_mixer_delete(chip);
|
|
|
+
|
|
|
+ if (mdev) {
|
|
|
+ if (media_devnode_is_registered(&mdev->devnode))
|
|
|
+ media_device_unregister(mdev);
|
|
|
+ chip->media_dev = NULL;
|
|
|
+ }
|
|
|
+}
|