mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	Fix some spelling mistakes as follows: altenate ==> alternate compatbile ==> compatible perfoms ==> performs dont'register ==> don't register periodicaly ==> periodically arount ==> around Signed-off-by: gushengxian <gushengxian@yulong.com> Link: https://lore.kernel.org/r/20210705120052.665212-1-gushengxian507419@gmail.com Signed-off-by: Takashi Iwai <tiwai@suse.de>
		
			
				
	
	
		
			327 lines
		
	
	
	
		
			8.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
	
		
			8.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
/*
 | 
						|
 * media.c - Media Controller specific ALSA driver code
 | 
						|
 *
 | 
						|
 * Copyright (c) 2019 Shuah Khan <shuah@kernel.org>
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * 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)) {
 | 
						|
		/* don't 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;
 | 
						|
	}
 | 
						|
}
 |