blob: 93a50d01490c03e4e0ea81e330f3c807f328f246 [file] [log] [blame]
/*
* 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 free_mctl;
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 unregister_entity;
}
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 devnode_remove;
}
/* 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 remove_intf_link;
break;
}
}
subs->media_ctl = mctl;
return 0;
remove_intf_link:
media_remove_intf_link(mctl->intf_link);
devnode_remove:
media_devnode_remove(mctl->intf_devnode);
unregister_entity:
media_device_unregister_entity(&mctl->media_entity);
free_mctl:
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;
}
}