forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
8.1 KiB
327 lines
8.1 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* media.c - Media Controller specific ALSA driver code |
|
* |
|
* Copyright (c) 2019 Shuah Khan <[email protected]> |
|
* |
|
*/ |
|
|
|
/* |
|
* This file adds Media Controller support to the ALSA driver |
|
* to use the Media Controller API to share the tuner with DVB |
|
* and V4L2 drivers that control the media device. |
|
* |
|
* The media device is created based on the 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" |
|
|
|
int snd_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; |
|
u32 intf_type; |
|
int ret = 0; |
|
u16 mixer_pad; |
|
struct media_entity *entity; |
|
|
|
mdev = subs->stream->chip->media_dev; |
|
if (!mdev) |
|
return 0; |
|
|
|
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 snd_media_stream_delete(struct snd_usb_substream *subs) |
|
{ |
|
struct media_ctl *mctl = subs->media_ctl; |
|
|
|
if (mctl) { |
|
struct media_device *mdev; |
|
|
|
mdev = mctl->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 snd_media_start_pipeline(struct snd_usb_substream *subs) |
|
{ |
|
struct media_ctl *mctl = subs->media_ctl; |
|
int ret = 0; |
|
|
|
if (!mctl) |
|
return 0; |
|
|
|
mutex_lock(&mctl->media_dev->graph_mutex); |
|
if (mctl->media_dev->enable_source) |
|
ret = mctl->media_dev->enable_source(&mctl->media_entity, |
|
&mctl->media_pipe); |
|
mutex_unlock(&mctl->media_dev->graph_mutex); |
|
return ret; |
|
} |
|
|
|
void snd_media_stop_pipeline(struct snd_usb_substream *subs) |
|
{ |
|
struct media_ctl *mctl = subs->media_ctl; |
|
|
|
if (!mctl) |
|
return; |
|
|
|
mutex_lock(&mctl->media_dev->graph_mutex); |
|
if (mctl->media_dev->disable_source) |
|
mctl->media_dev->disable_source(&mctl->media_entity); |
|
mutex_unlock(&mctl->media_dev->graph_mutex); |
|
} |
|
|
|
static int snd_media_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 snd_media_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 snd_media_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 = 0; |
|
|
|
/* usb-audio driver is probed for each usb interface, and |
|
* there are multiple interfaces per device. Avoid calling |
|
* media_device_usb_allocate() each time usb_audio_probe() |
|
* is called. Do it only once. |
|
*/ |
|
if (chip->media_dev) { |
|
mdev = chip->media_dev; |
|
goto snd_mixer_init; |
|
} |
|
|
|
mdev = media_device_usb_allocate(usbdev, KBUILD_MODNAME, THIS_MODULE); |
|
if (IS_ERR(mdev)) |
|
return -ENOMEM; |
|
|
|
/* save media device - avoid lookups */ |
|
chip->media_dev = mdev; |
|
|
|
snd_mixer_init: |
|
/* Create media entities for mixer and control dev */ |
|
ret = snd_media_mixer_init(chip); |
|
/* media_device might be registered, print error and continue */ |
|
if (ret) |
|
dev_err(&usbdev->dev, |
|
"Couldn't create media mixer entities. Error: %d\n", |
|
ret); |
|
|
|
if (!media_devnode_is_registered(mdev->devnode)) { |
|
/* dont'register if snd_media_mixer_init() failed */ |
|
if (ret) |
|
goto create_fail; |
|
|
|
/* register media_device */ |
|
ret = media_device_register(mdev); |
|
create_fail: |
|
if (ret) { |
|
snd_media_mixer_delete(chip); |
|
media_device_delete(mdev, KBUILD_MODNAME, THIS_MODULE); |
|
/* clear saved media_dev */ |
|
chip->media_dev = NULL; |
|
dev_err(&usbdev->dev, |
|
"Couldn't register media device. Error: %d\n", |
|
ret); |
|
return ret; |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
void snd_media_device_delete(struct snd_usb_audio *chip) |
|
{ |
|
struct media_device *mdev = chip->media_dev; |
|
struct snd_usb_stream *stream; |
|
|
|
/* release resources */ |
|
list_for_each_entry(stream, &chip->pcm_list, list) { |
|
snd_media_stream_delete(&stream->substream[0]); |
|
snd_media_stream_delete(&stream->substream[1]); |
|
} |
|
|
|
snd_media_mixer_delete(chip); |
|
|
|
if (mdev) { |
|
media_device_delete(mdev, KBUILD_MODNAME, THIS_MODULE); |
|
chip->media_dev = NULL; |
|
} |
|
}
|
|
|