Commit 02de698c authored by Ruslan Bilovol's avatar Ruslan Bilovol Committed by Greg Kroah-Hartman
Browse files

usb: gadget: u_audio: add bi-directional volume and mute support



USB Audio Class 1/2 have ability to change device's
volume and mute by USB Host through class-specific control
requests. Device also can notify Host about volume/mute
change on its side through optional interrupt endpoint.

This patch adds Volume and Mute ALSA controls which can be
used by user to send and receive notifications to/from
the USB Host about Volume and Mute change.

These params come from f_uac* so volume and mute controls
will be created only if the function support and enable
each explicitly

Signed-off-by: default avatarRuslan Bilovol <ruslan.bilovol@gmail.com>
Signed-off-by: default avatarPavel Hofman <pavel.hofman@ivitera.com>
Link: https://lore.kernel.org/r/20210712125529.76070-3-pavel.hofman@ivitera.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent b48f8939
Loading
Loading
Loading
Loading
+358 −11
Original line number Diff line number Diff line
@@ -12,11 +12,14 @@
 *    Jaswinder Singh (jaswinder.singh@linaro.org)
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <linux/usb/audio.h>

#include "u_audio.h"

@@ -24,6 +27,12 @@
#define PRD_SIZE_MAX	PAGE_SIZE
#define MIN_PERIODS	4

enum {
	UAC_FBACK_CTRL,
	UAC_MUTE_CTRL,
	UAC_VOLUME_CTRL,
};

/* Runtime data params for one stream */
struct uac_rtd_params {
	struct snd_uac_chip *uac; /* parent chip */
@@ -43,6 +52,17 @@ struct uac_rtd_params {

	struct usb_request *req_fback; /* Feedback endpoint request */
	bool fb_ep_enabled; /* if the ep is enabled */

  /* Volume/Mute controls and their state */
  int fu_id; /* Feature Unit ID */
  struct snd_kcontrol *snd_kctl_volume;
  struct snd_kcontrol *snd_kctl_mute;
  s16 volume_min, volume_max, volume_res;
  s16 volume;
  int mute;

  spinlock_t lock; /* lock for control transfers */

};

struct snd_uac_chip {
@@ -597,6 +617,103 @@ void u_audio_stop_playback(struct g_audio *audio_dev)
}
EXPORT_SYMBOL_GPL(u_audio_stop_playback);

int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val)
{
	struct snd_uac_chip *uac = audio_dev->uac;
	struct uac_rtd_params *prm;
	unsigned long flags;

	if (playback)
		prm = &uac->p_prm;
	else
		prm = &uac->c_prm;

	spin_lock_irqsave(&prm->lock, flags);
	*val = prm->volume;
	spin_unlock_irqrestore(&prm->lock, flags);

	return 0;
}
EXPORT_SYMBOL_GPL(u_audio_get_volume);

int u_audio_set_volume(struct g_audio *audio_dev, int playback, s16 val)
{
	struct snd_uac_chip *uac = audio_dev->uac;
	struct uac_rtd_params *prm;
	unsigned long flags;
	int change = 0;

	if (playback)
		prm = &uac->p_prm;
	else
		prm = &uac->c_prm;

	spin_lock_irqsave(&prm->lock, flags);
	val = clamp(val, prm->volume_min, prm->volume_max);
	if (prm->volume != val) {
		prm->volume = val;
		change = 1;
	}
	spin_unlock_irqrestore(&prm->lock, flags);

	if (change)
		snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE,
				&prm->snd_kctl_volume->id);

	return 0;
}
EXPORT_SYMBOL_GPL(u_audio_set_volume);

int u_audio_get_mute(struct g_audio *audio_dev, int playback, int *val)
{
	struct snd_uac_chip *uac = audio_dev->uac;
	struct uac_rtd_params *prm;
	unsigned long flags;

	if (playback)
		prm = &uac->p_prm;
	else
		prm = &uac->c_prm;

	spin_lock_irqsave(&prm->lock, flags);
	*val = prm->mute;
	spin_unlock_irqrestore(&prm->lock, flags);

	return 0;
}
EXPORT_SYMBOL_GPL(u_audio_get_mute);

int u_audio_set_mute(struct g_audio *audio_dev, int playback, int val)
{
	struct snd_uac_chip *uac = audio_dev->uac;
	struct uac_rtd_params *prm;
	unsigned long flags;
	int change = 0;
	int mute;

	if (playback)
		prm = &uac->p_prm;
	else
		prm = &uac->c_prm;

	mute = val ? 1 : 0;

	spin_lock_irqsave(&prm->lock, flags);
	if (prm->mute != mute) {
		prm->mute = mute;
		change = 1;
	}
	spin_unlock_irqrestore(&prm->lock, flags);

	if (change)
		snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE,
			       &prm->snd_kctl_mute->id);

	return 0;
}
EXPORT_SYMBOL_GPL(u_audio_set_mute);


static int u_audio_pitch_info(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_info *uinfo)
{
@@ -656,14 +773,158 @@ static int u_audio_pitch_put(struct snd_kcontrol *kcontrol,
	return change;
}

static const struct snd_kcontrol_new u_audio_controls[]  = {
static int u_audio_mute_info(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	uinfo->value.integer.step = 1;

	return 0;
}

static int u_audio_mute_get(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
	unsigned long flags;

	spin_lock_irqsave(&prm->lock, flags);
	ucontrol->value.integer.value[0] = !prm->mute;
	spin_unlock_irqrestore(&prm->lock, flags);

	return 0;
}

static int u_audio_mute_put(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
	struct snd_uac_chip *uac = prm->uac;
	struct g_audio *audio_dev = uac->audio_dev;
	unsigned int val;
	unsigned long flags;
	int change = 0;

	val = !ucontrol->value.integer.value[0];

	spin_lock_irqsave(&prm->lock, flags);
	if (val != prm->mute) {
		prm->mute = val;
		change = 1;
	}
	spin_unlock_irqrestore(&prm->lock, flags);

	if (change && audio_dev->notify)
		audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_MUTE);

	return change;
}

/*
 * TLV callback for mixer volume controls
 */
static int u_audio_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag,
			 unsigned int size, unsigned int __user *_tlv)
{
	struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
	DECLARE_TLV_DB_MINMAX(scale, 0, 0);

	if (size < sizeof(scale))
		return -ENOMEM;

	/* UAC volume resolution is 1/256 dB, TLV is 1/100 dB */
	scale[2] = (prm->volume_min * 100) / 256;
	scale[3] = (prm->volume_max * 100) / 256;
	if (copy_to_user(_tlv, scale, sizeof(scale)))
		return -EFAULT;

	return 0;
}

static int u_audio_volume_info(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_info *uinfo)
{
	struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max =
		(prm->volume_max - prm->volume_min + prm->volume_res - 1)
		/ prm->volume_res;
	uinfo->value.integer.step = 1;

	return 0;
}

static int u_audio_volume_get(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
	unsigned long flags;

	spin_lock_irqsave(&prm->lock, flags);
	ucontrol->value.integer.value[0] =
			(prm->volume - prm->volume_min) / prm->volume_res;
	spin_unlock_irqrestore(&prm->lock, flags);

	return 0;
}

static int u_audio_volume_put(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
{
	struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
	struct snd_uac_chip *uac = prm->uac;
	struct g_audio *audio_dev = uac->audio_dev;
	unsigned int val;
	s16 volume;
	unsigned long flags;
	int change = 0;

	val = ucontrol->value.integer.value[0];

	spin_lock_irqsave(&prm->lock, flags);
	volume = (val * prm->volume_res) + prm->volume_min;
	volume = clamp(volume, prm->volume_min, prm->volume_max);
	if (volume != prm->volume) {
		prm->volume = volume;
		change = 1;
	}
	spin_unlock_irqrestore(&prm->lock, flags);

	if (change && audio_dev->notify)
		audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_VOLUME);

	return change;
}


static struct snd_kcontrol_new u_audio_controls[]  = {
  [UAC_FBACK_CTRL] {
    .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
    .name =         "Capture Pitch 1000000",
    .info =         u_audio_pitch_info,
    .get =          u_audio_pitch_get,
    .put =          u_audio_pitch_put,
  },
  [UAC_MUTE_CTRL] {
		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
		.name =		"", /* will be filled later */
		.info =		u_audio_mute_info,
		.get =		u_audio_mute_get,
		.put =		u_audio_mute_put,
	},
	[UAC_VOLUME_CTRL] {
		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
		.name =		"", /* will be filled later */
		.info =		u_audio_volume_info,
		.get =		u_audio_volume_get,
		.put =		u_audio_volume_put,
	},
};

int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
@@ -675,7 +936,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
	struct snd_kcontrol *kctl;
	struct uac_params *params;
	int p_chmask, c_chmask;
	int err;
	int i, err;

	if (!g_audio)
		return -EINVAL;
@@ -693,6 +954,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
	if (c_chmask) {
		struct uac_rtd_params *prm = &uac->c_prm;

    spin_lock_init(&prm->lock);
    uac->c_prm.uac = uac;
		prm->max_psize = g_audio->out_ep_maxpsize;

@@ -716,6 +978,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
	if (p_chmask) {
		struct uac_rtd_params *prm = &uac->p_prm;

		spin_lock_init(&prm->lock);
		uac->p_prm.uac = uac;
		prm->max_psize = g_audio->in_ep_maxpsize;

@@ -760,10 +1023,18 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops);

	if (c_chmask && g_audio->in_ep_fback) {
	/*
	 * Create mixer and controls
	 * Create only if it's required on USB side
	 */
	if ((c_chmask && g_audio->in_ep_fback)
			|| (p_chmask && params->p_fu.id)
			|| (c_chmask && params->c_fu.id))
		strscpy(card->mixername, card_name, sizeof(card->driver));

		kctl = snd_ctl_new1(&u_audio_controls[0], &uac->c_prm);
	if (c_chmask && g_audio->in_ep_fback) {
		kctl = snd_ctl_new1(&u_audio_controls[UAC_FBACK_CTRL],
				    &uac->c_prm);
		if (!kctl) {
			err = -ENOMEM;
			goto snd_fail;
@@ -777,6 +1048,82 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
			goto snd_fail;
	}

	for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) {
		struct uac_rtd_params *prm;
		struct uac_fu_params *fu;
		char ctrl_name[24];
		char *direction;

		if (!pcm->streams[i].substream_count)
			continue;

		if (i == SNDRV_PCM_STREAM_PLAYBACK) {
			prm = &uac->p_prm;
			fu = &params->p_fu;
			direction = "Playback";
		} else {
			prm = &uac->c_prm;
			fu = &params->c_fu;
			direction = "Capture";
		}

		prm->fu_id = fu->id;

		if (fu->mute_present) {
			snprintf(ctrl_name, sizeof(ctrl_name),
					"PCM %s Switch", direction);

			u_audio_controls[UAC_MUTE_CTRL].name = ctrl_name;

			kctl = snd_ctl_new1(&u_audio_controls[UAC_MUTE_CTRL],
					    prm);
			if (!kctl) {
				err = -ENOMEM;
				goto snd_fail;
			}

			kctl->id.device = pcm->device;
			kctl->id.subdevice = i;

			err = snd_ctl_add(card, kctl);
			if (err < 0)
				goto snd_fail;
			prm->snd_kctl_mute = kctl;
			prm->mute = 0;
		}

		if (fu->volume_present) {
			snprintf(ctrl_name, sizeof(ctrl_name),
					"PCM %s Volume", direction);

			u_audio_controls[UAC_VOLUME_CTRL].name = ctrl_name;

			kctl = snd_ctl_new1(&u_audio_controls[UAC_VOLUME_CTRL],
					    prm);
			if (!kctl) {
				err = -ENOMEM;
				goto snd_fail;
			}

			kctl->id.device = pcm->device;
			kctl->id.subdevice = i;


			kctl->tlv.c = u_audio_volume_tlv;
			kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ |
					SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;

			err = snd_ctl_add(card, kctl);
			if (err < 0)
				goto snd_fail;
			prm->snd_kctl_volume = kctl;
			prm->volume = fu->volume_max;
			prm->volume_max = fu->volume_max;
			prm->volume_min = fu->volume_min;
			prm->volume_res = fu->volume_res;
		}
	}

	strscpy(card->driver, card_name, sizeof(card->driver));
	strscpy(card->shortname, card_name, sizeof(card->shortname));
	sprintf(card->longname, "%s %i", card_name, card->dev->id);
+22 −0
Original line number Diff line number Diff line
@@ -19,16 +19,30 @@
 */
#define FBACK_SLOW_MAX	250

/* Feature Unit parameters */
struct uac_fu_params {
	int id;			/* Feature Unit ID */

	bool mute_present;	/* mute control enable */

	bool volume_present;	/* volume control enable */
	s16 volume_min;		/* min volume in 1/256 dB */
	s16 volume_max;		/* max volume in 1/256 dB */
	s16 volume_res;		/* volume resolution in 1/256 dB */
};

struct uac_params {
	/* playback */
	int p_chmask;	/* channel mask */
	int p_srate;	/* rate in Hz */
	int p_ssize;	/* sample size */
	struct uac_fu_params p_fu;	/* Feature Unit parameters */

	/* capture */
	int c_chmask;	/* channel mask */
	int c_srate;	/* rate in Hz */
	int c_ssize;	/* sample size */
	struct uac_fu_params c_fu;	/* Feature Unit parameters */

	int req_number; /* number of preallocated requests */
	int fb_max;	/* upper frequency drift feedback limit per-mil */
@@ -49,6 +63,9 @@ struct g_audio {
	/* Max packet size for all out_ep possible speeds */
	unsigned int out_ep_maxpsize;

	/* Notify UAC driver about control change */
	int (*notify)(struct g_audio *g_audio, int unit_id, int cs);

	/* The ALSA Sound Card it represents on the USB-Client side */
	struct snd_uac_chip *uac;

@@ -94,4 +111,9 @@ void u_audio_stop_capture(struct g_audio *g_audio);
int u_audio_start_playback(struct g_audio *g_audio);
void u_audio_stop_playback(struct g_audio *g_audio);

int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val);
int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val);
int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val);
int u_audio_set_mute(struct g_audio *g_audio, int playback, int val);

#endif /* __U_AUDIO_H */