[alsa-devel] [PATCH v4 1/1] ALSA: Tascam US-16x08 DSP mixer quirk

OnkelDead onkel at paraair.de
Thu Feb 9 18:30:22 CET 2017


Ok, here my result

>> +#include <linux/slab.h>
>> +#include <linux/usb.h>
>> +#include <linux/usb/audio-v2.h>
>> +#include "linux/kthread.h"
>
> Why kthread?  Also why double-quote, not bracket?
>
>> +
>> +#include <sound/core.h>
>> +#include <sound/control.h>
>> +#include <asm-generic/errno-base.h>
>
> This looks very wrong.  Just include <linux/errno.h> if really needed
> (I guess you won't).

Indeed! Includes removed

>> +/* wrapper function to send prepared URB buffer to usb device. Return -1
>> + * if something went wrong
>
> It's always better to return a negative error code instead of -1.

Now returns correct errno

>> +    if (err == 0) {
>> +        store->value[index] = val;
>> +        elem->cached &= 1 << index;
>
> Do you mean "|="?  Otherwise the cache value is set exclusively.
> (Ditto for all other places.)

Absolutely right, fixed.

>> +    switch (elem->head.id) {
>> +    case SND_US16X08_ID_COMP_THRESHOLD:
>> +        val = store->valThreshold[index];
>> +        break;
>> +    case SND_US16X08_ID_COMP_RATIO:
>> +        val = store->valRatio[index];
>> +        break;
>> +    case SND_US16X08_ID_COMP_ATTACK:
>> +        val = store->valAttack[index];
>> +        break;
>> +    case SND_US16X08_ID_COMP_RELEASE:
>> +        val = store->valRelease[index];
>> +        break;
>> +    case SND_US16X08_ID_COMP_GAIN:
>> +        val = store->valGain[index];
>> +        break;
>> +    case SND_US16X08_ID_COMP_SWITCH:
>> +        val = store->valSwitch[index];
>> +        break;
>
> Wouldn't it be easier to have something like:
>
>    val_idx = elem_id_to_idx(elem->head.id);
>    val = store->values[val_id][index];
>
> A series of switch/case doesn't look good.

Good advise, I've changed the store structs accordingly.
More about those shadow stores later.

>> +    buf[20] = store->low_store->valSwitch[index];
>> +    buf[17] = store->low_store->valWidth[index];
>> +    buf[14] = store->low_store->valFreq[index];
>> +    buf[11] = store->low_store->valdB[index];
>> +    buf[8] = 0x01;
>> +    err = snd_us16x08_send_urb(chip, buf, sizeof(eqs_msq));
>
> No error check?

Fixed.

>
>> +    /* give time to the device to handle the request */
>> +    mdelay(15);
>
> Does it have to be mdelay(), not msleep() or its variant?
> mdelay() with such a long time is usually something wrong.
>
> > +/* setup EQ store and assign default values */
>> +static struct snd_us16x08_eq_store *snd_us16x08_create_eq_store(int band_index)
>> +{
>> +    int i = 0;
>
> Superfluous initialization.

Fixed.

>> +/* suspend/resume */
>> +
>> +static void snd_us16x08_resume_route(struct usb_mixer_elem_info *elem)
>> +{
>> +    int i;
>> +    struct snd_us16x08_common *store =
>> +        (struct snd_us16x08_common *) elem->private_data;
>> +
>> +    for (i = 0; i < elem->channels; i++)
>> +        if (elem->cached & (1 << i))
>> +            store->value[i] = elem->cache_val[i];
>
> Does it make sense?  The resume function resumes the device, and it's
> not about the memory we manage by ourselves.  Usually the resume
> function writes back the cached value to the device.
>
> If store->value[] and elem->cache_val[] is always identical, we can
> drop even store->value[].

A first impression must lead in your concern.
But using those shadow caches is important, cause the device
expects multiple control values in a single URB message.
If e.g. the threshold of a compressor got changed, the driver has to send
new threshold AND current ratio, attack, release etc. in a single command.
I didn't found a solution for cross-mixer-element-cache-access.
Any hints ?

I've removed all shadow caches where not needed.

I've remove the resume callback, the device is self powered and has its own
suspend/resume mechanism.

>> +    { /* EQ high gain */
>> +        .kcontrol_new = &snd_us16x08_eq_gain_ctl,
>> +        .control_id = SND_US16X08_ID_EQHIGHLEVEL,
>> +        .type = USB_MIXER_U8,
>> +        .num_channels = 16,
>> +        .name = "High gain",
>> +        .freeer = snd_usb_mixer_elem_free
>> +    },
>> +    { /* EQ low freq */
>> +        .kcontrol_new = &snd_us16x08_eq_high_freq_ctl,
>> +        .control_id = SND_US16X08_ID_EQHIGHFREQ,
>> +        .type = USB_MIXER_U8,
>> +        .num_channels = 16,
>> +        .name = "High freq",
>
> These mixer control names look somehow too ambiguous...

I've tried to keep them short, they are somehow equal to those in
the MS control panel I'd lean on.
Still unchanged.

>> +    struct snd_us16x08_eq_store *eq_high_store =
>> +        snd_us16x08_create_eq_store(0x04);
>> +    struct snd_us16x08_eq_all_store *eq_all_store =
>> +        kmalloc(sizeof(struct snd_us16x08_eq_all_store), GFP_KERNEL);
>> +
>> +    /* check for allocation error */
>> +    if (eq_low_store == NULL || eq_midlow_store == NULL ||
>> +        eq_midhigh_store == NULL || eq_high_store == NULL ||
>> +        eq_all_store == NULL)
>> +        return -ENOMEM;
>
> This leaks the memory.  You forgot to release non-NULL ones.

Fixed.

>> +        /* create bus store */
>> +        bus_store = snd_us16x08_create_bus_store(0);
>> +        if (bus_store == NULL)
>> +            return -ENOMEM;
>> +
>> +        /* create channel store */
>> +        channel_store = snd_us16x08_create_channel_store();
>> +        if (channel_store == NULL)
>> +            return -ENOMEM;
>
> Ditto.

Fixed.

>> +        err = add_new_ctl(mixer, &snd_us16x08_bus_ctl,
>> +            SND_US16X08_ID_MUTE, USB_MIXER_U8, 1, "Master mute",
>> +            (void *) bus_store, NULL);
>> +        if (err < 0)
>> +            return err;
>> +
>> +        err = add_new_ctl(mixer, &snd_us16x08_ch_boolean_ctl,
>> +            SND_US16X08_ID_PHASE, USB_MIXER_U8, 16, "Phase",
>> +            (void *) channel_store,
>> +            snd_usb_mixer_elem_free);
>> +        if (err < 0)
>> +            return err;
>> +
>> +        err = add_new_ctl(mixer, &snd_us16x08_ch_int_ctl,
>> +            SND_US16X08_ID_FADER, USB_MIXER_S16, 16, "Fader",
>> +            (void *) channel_store, NULL);
>> +        if (err < 0)
>> +            return err;
>> +
>> +        err = add_new_ctl(mixer, &snd_us16x08_ch_boolean_ctl,
>> +            SND_US16X08_ID_MUTE, USB_MIXER_BOOLEAN, 16, "Mute",
>> +            (void *) channel_store, NULL);
>> +
>> +        if (err < 0)
>> +            return err;
>> +
>> +        err = add_new_ctl(mixer, &snd_us16x08_ch_int_ctl,
>> +            SND_US16X08_ID_PAN, USB_MIXER_U16, 16, "Pan",
>> +            (void *) channel_store, NULL);
>
> Can we use a table for registration, too?
> This sequence of calls looks... well, a lot of room to optimize

Fixed, tables are in place.

>> +struct snd_us16x08_eq_store {
>> +    u8 valdB[SND_US16X08_MAX_CHANNELS];
>> +    u8 valFreq[SND_US16X08_MAX_CHANNELS];
>> +    u8 valWidth[SND_US16X08_MAX_CHANNELS];
>> +    u8 valSwitch[SND_US16X08_MAX_CHANNELS];
>
> Please avoid thisSomething, but use this_something (dB might be an
> exception, though).
> In this case, val prefix can be dropped, too.  It's just superfluous.
>
>
>> +struct snd_us16x08_comp_store {
>> +    u8 valThreshold[SND_US16X08_MAX_CHANNELS];
>> +    u8 valAttack[SND_US16X08_MAX_CHANNELS];
>> +    u8 valRelease[SND_US16X08_MAX_CHANNELS];
>> +    u8 valRatio[SND_US16X08_MAX_CHANNELS];
>> +    u8 valGain[SND_US16X08_MAX_CHANNELS];
>> +    u8 valSwitch[SND_US16X08_MAX_CHANNELS];
>> +};
>
> Ditto.

Fixed.


Add mixer quirk for Tascam US-16x08 usb interface.
Even that this an usb compliant device,
the input channels and DSP functions (EQ/Compressor)
arn't accessible by default.

Signed-off-by: Detlef Urban <onkel at paraair.de>
---
 sound/usb/Makefile        |    1 +
 sound/usb/mixer_quirks.c  |    5 +
 sound/usb/mixer_us16x08.c | 1425 +++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/mixer_us16x08.h |  122 ++++
 4 files changed, 1553 insertions(+)
 create mode 100644 sound/usb/mixer_us16x08.c
 create mode 100644 sound/usb/mixer_us16x08.h

diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 2d2d122..42cb33b 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -10,6 +10,7 @@ snd-usb-audio-objs := 	card.o \
 			mixer.o \
 			mixer_quirks.o \
 			mixer_scarlett.o \
+			mixer_us16x08.o \
 			pcm.o \
 			proc.o \
 			quirks.o \
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 04991b0..4fa0053 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -43,6 +43,7 @@
 #include "mixer.h"
 #include "mixer_quirks.h"
 #include "mixer_scarlett.h"
+#include "mixer_us16x08.h"
 #include "helper.h"
 
 extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl;
@@ -1729,6 +1730,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
 		return err;
 
 	switch (mixer->chip->usb_id) {
+	/* Tascam US-16x08 */
+	case USB_ID(0x0644, 0x8047):
+		err = snd_us16x08_controls_create(mixer);
+		break;
 	case USB_ID(0x041e, 0x3020):
 	case USB_ID(0x041e, 0x3040):
 	case USB_ID(0x041e, 0x3042):
diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c
new file mode 100644
index 0000000..0ee3f40
--- /dev/null
+++ b/sound/usb/mixer_us16x08.c
@@ -0,0 +1,1425 @@
+/*
+ *   Tascam US-16x08 ALSA driver
+ *
+ *   Copyright (c) 2016 by Detlef Urban (onkel at paraair.de)
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+
+#include "mixer_us16x08.h"
+
+/* USB control message templates */
+static char route_msg[] = {
+	0x61,
+	0x02,
+	0x03, /* input from master (0x02) or input from computer bus (0x03) */
+	0x62,
+	0x02,
+	0x01, /* input index (0x01/0x02 eq. left/right) or bus (0x01-0x08) */
+	0x41,
+	0x01,
+	0x61,
+	0x02,
+	0x01,
+	0x62,
+	0x02,
+	0x01, /* output index (0x01-0x08) */
+	0x42,
+	0x01,
+	0x43,
+	0x01,
+	0x00,
+	0x00
+};
+
+static char mix_init_msg1[] = {
+	0x71, 0x01, 0x00, 0x00
+};
+
+static char mix_init_msg2[] = {
+	0x62, 0x02, 0x00, 0x61, 0x02, 0x04, 0xb1, 0x01, 0x00, 0x00
+};
+
+static char mix_msg_in[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x04, 0x62, 0x02, 0x01,
+	0x81, /* 0x06: Controller ID */
+	0x02, /* 0x07:  */
+	0x00, /* 0x08: Value of common mixer */
+	0x00,
+	0x00
+};
+
+static char mix_msg_out[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x02, 0x62, 0x02, 0x01,
+	0x81, /* 0x06: Controller ID */
+	0x02, /*                    0x07:  */
+	0x00, /*                    0x08: Value of common mixer */
+	0x00,
+	0x00
+};
+
+static char bypass_msg_out[] = {
+	0x45,
+	0x02,
+	0x01, /* on/off flag */
+	0x00,
+	0x00
+};
+
+static char bus_msg_out[] = {
+	0x44,
+	0x02,
+	0x01, /* on/off flag */
+	0x00,
+	0x00
+};
+
+static char comp_msg[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x04, 0x62, 0x02, 0x01,
+	0x91,
+	0x02,
+	0xf0, /* 0x08: Threshold db (8) (e0 ... 00) (+-0dB -- -32dB) x-32 */
+	0x92,
+	0x02,
+	0x0a, /* 0x0b: Ratio (0a,0b,0d,0f,11,14,19,1e,23,28,32,3c,50,a0,ff)  */
+	0x93,
+	0x02,
+	0x02, /* 0x0e: Attack (0x02 ... 0xc0) (2ms ... 200ms) */
+	0x94,
+	0x02,
+	0x01, /* 0x11: Release (0x01 ... 0x64) (10ms ... 1000ms) x*10  */
+	0x95,
+	0x02,
+	0x03, /* 0x14: gain (0 ... 20) (0dB .. 20dB) */
+	0x96,
+	0x02,
+	0x01,
+	0x97,
+	0x02,
+	0x01, /* 0x1a: main Comp switch (0 ... 1) (off ... on)) */
+	0x00,
+	0x00
+};
+
+static char eqs_msq[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x04, 0x62, 0x02, 0x01,
+	0x51, /*                0x06: Controller ID  */
+	0x02,
+	0x04, /* 0x08: EQ set num (0x01..0x04) (LOW, LOWMID, HIGHMID, HIGH)) */
+	0x52,
+	0x02,
+	0x0c, /* 0x0b: value dB (0 ... 12) (-12db .. +12db)  x-6 */
+	0x53,
+	0x02,
+	0x0f, /* 0x0e: value freq (32-47) (1.7kHz..18kHz) */
+	0x54,
+	0x02,
+	0x02, /* 0x11: band width (0-6) (Q16-Q0.25)  2^x/4 (EQ xxMID only) */
+	0x55,
+	0x02,
+	0x01, /* 0x14: main EQ switch (0 ... 1) (off ... on)) */
+	0x00,
+	0x00
+};
+
+/* compressor ratio map */
+static char ratio_map[] = {
+	0x0a, 0x0b, 0x0d, 0x0f, 0x11, 0x14, 0x19, 0x1e,
+	0x23, 0x28, 0x32, 0x3c, 0x50, 0xa0, 0xff
+};
+
+static int snd_us16x08_recv_urb(struct snd_usb_audio *chip,
+	unsigned char *buf, int size)
+{
+
+	mutex_lock(&chip->mutex);
+	snd_usb_ctl_msg(chip->dev,
+		usb_rcvctrlpipe(chip->dev, 0),
+		SND_US16X08_URB_METER_REQUEST,
+		SND_US16X08_URB_METER_REQUESTTYPE, 0, 0, buf, size);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+/* wrapper function to send prepared URB buffer to usb device. Return an error
+ * code if something went wrong
+ */
+static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size)
+{
+	int err = 0;
+
+	if (chip) {
+		err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0),
+			SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE,
+			0, 0, buf, size);
+	}
+
+	return err;
+}
+
+static int snd_us16x08_route_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->count = 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->value.integer.max = SND_US16X08_KCMAX(kcontrol);
+	uinfo->value.integer.min = SND_US16X08_KCMIN(kcontrol);
+	uinfo->value.integer.step = SND_US16X08_KCSTEP(kcontrol);
+	return 0;
+}
+
+static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	int index = ucontrol->id.index;
+
+	/* route has no bias */
+	ucontrol->value.integer.value[0] = elem->cache_val[index];
+
+	return 0;
+}
+
+static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+
+	int index = ucontrol->id.index;
+	char buf[sizeof(route_msg)];
+	int val, val_org, err = 0;
+
+	/* prepare the message buffer from template */
+	memcpy(buf, route_msg, sizeof(route_msg));
+
+	/*  get the new value (no bias for routes) */
+	val = ucontrol->value.integer.value[0];
+	if (val < 2) {
+		/* input comes from a master channel */
+		val_org = val;
+		buf[2] = 0x02;
+	} else {
+		/* input comes from a computer channel */
+		buf[2] = 0x03;
+		val_org = val - 2;
+	}
+
+	/* place new route selection in URB message */
+	buf[5] = (unsigned char) (val_org & 0x0f) + 1;
+	/* place route selector in URB message */
+	buf[13] = index + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(route_msg));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_err(chip, "Failed to set routing, err:%d\n", err);
+	}
+
+	return err == 0 ? 1 : 0;
+}
+
+static int snd_us16x08_master_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->count = 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.max = SND_US16X08_KCMAX(kcontrol);
+	uinfo->value.integer.min = SND_US16X08_KCMIN(kcontrol);
+	uinfo->value.integer.step = SND_US16X08_KCSTEP(kcontrol);
+	return 0;
+}
+
+static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+
+	int index = ucontrol->id.index;
+
+	ucontrol->value.integer.value[0] = elem->cache_val[index];
+
+	return 0;
+}
+
+static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	char buf[sizeof(mix_msg_out)];
+	int val, err = 0;
+	int index = ucontrol->id.index;
+
+	/* prepare the message buffer from template */
+	memcpy(buf, mix_msg_out, sizeof(mix_msg_out));
+
+	/* new control value incl. bias*/
+	val = ucontrol->value.integer.value[0];
+
+	buf[8] = val - SND_US16X08_KCBIAS(kcontrol);
+	buf[6] = elem->head.id;
+
+	/* place channel selector in URB message */
+	buf[5] = index + 1;
+	err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_out));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_err(chip, "Failed to set master, err:%d\n", err);
+	}
+
+	return err == 0 ? 1 : 0;
+}
+
+static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+
+	char buf[sizeof(mix_msg_out)];
+	int val, err = 0;
+
+	val = ucontrol->value.integer.value[0];
+
+	/* prepare the message buffer from template */
+	switch (elem->head.id) {
+	case SND_US16X08_ID_BYPASS:
+		memcpy(buf, bypass_msg_out, sizeof(bypass_msg_out));
+		buf[2] = val;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(bypass_msg_out));
+		break;
+	case SND_US16X08_ID_BUSS_OUT:
+		memcpy(buf, bus_msg_out, sizeof(bus_msg_out));
+		buf[2] = val;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(bus_msg_out));
+		break;
+	case SND_US16X08_ID_MUTE:
+		memcpy(buf, mix_msg_out, sizeof(mix_msg_out));
+		buf[8] = val;
+		buf[6] = elem->head.id;
+		buf[5] = 1;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_out));
+		break;
+	}
+
+	if (err > 0) {
+		elem->cached |= 1;
+		elem->cache_val[0] = val;
+	} else {
+		usb_audio_err(chip, "Failed to set buss param, err:%d\n", err);
+	}
+
+	return err == 0 ? 1 : 0;
+}
+
+static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+
+	switch (elem->head.id) {
+	case SND_US16X08_ID_BUSS_OUT:
+		ucontrol->value.integer.value[0] = elem->cache_val[0];
+		break;
+	case SND_US16X08_ID_BYPASS:
+		ucontrol->value.integer.value[0] = elem->cache_val[0];
+		break;
+	case SND_US16X08_ID_MUTE:
+		ucontrol->value.integer.value[0] = elem->cache_val[0];
+		break;
+	}
+
+	return 0;
+}
+
+/* gets a current mixer value from common store */
+static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	int index = ucontrol->id.index;
+
+	ucontrol->value.integer.value[0] = elem->cache_val[index];
+
+	return 0;
+}
+
+static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	char buf[sizeof(mix_msg_in)];
+	int val, err;
+	int index = ucontrol->id.index;
+
+	/* prepare URB message from template */
+	memcpy(buf, mix_msg_in, sizeof(mix_msg_in));
+
+	val = ucontrol->value.integer.value[0];
+
+	/* add the bias to the new value */
+	buf[8] = val - SND_US16X08_KCBIAS(kcontrol);
+	buf[6] = elem->head.id;
+	buf[5] = index + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_in));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_err(chip, "Failed to set channel, err:%d\n", err);
+	}
+
+	return err == 0 ? 1 : 0;
+}
+
+static int snd_us16x08_mix_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->count = 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.max = SND_US16X08_KCMAX(kcontrol);
+	uinfo->value.integer.min = SND_US16X08_KCMIN(kcontrol);
+	uinfo->value.integer.step = SND_US16X08_KCSTEP(kcontrol);
+	return 0;
+}
+
+static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_comp_store *store =
+		((struct snd_us16x08_comp_store *) elem->private_data);
+	int index = ucontrol->id.index;
+	int val_idx = COMP_STORE_IDX(elem->head.id);
+
+	ucontrol->value.integer.value[0] = store->val[val_idx][index];
+
+	return 0;
+}
+
+static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_comp_store *store =
+		((struct snd_us16x08_comp_store *) elem->private_data);
+	int index = ucontrol->id.index;
+
+	char buf[sizeof(comp_msg)];
+	int val_idx, val;
+	int err = 0;
+
+	/* prepare compressor URB message from template  */
+	memcpy(buf, comp_msg, sizeof(comp_msg));
+
+	/* new control value incl. bias*/
+	val_idx = elem->head.id - SND_US16X08_ID_COMP_BASE;
+
+	val = ucontrol->value.integer.value[0];
+	store->val[val_idx][index] = ucontrol->value.integer.value[0];
+
+	/* place comp values in message buffer watch bias! */
+	buf[8] = store->val[
+		COMP_STORE_IDX(SND_US16X08_ID_COMP_THRESHOLD)][index]
+		- SND_US16X08_COMP_THRESHOLD_BIAS;
+	buf[11] = ratio_map[store->val[
+		COMP_STORE_IDX(SND_US16X08_ID_COMP_RATIO)][index]];
+	buf[14] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_ATTACK)][index]
+		+ SND_US16X08_COMP_ATTACK_BIAS;
+	buf[17] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RELEASE)][index]
+		+ SND_US16X08_COMP_RELEASE_BIAS;
+	buf[20] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_GAIN)][index];
+	buf[26] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)][index];
+
+	/* place channel selector in message buffer */
+	buf[5] = index + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(comp_msg));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_err(chip, "Failed to set compressor, err:%d\n", err);
+	}
+
+	return 1;
+}
+
+static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int val = 0;
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_eq_store *store =
+		((struct snd_us16x08_eq_store *) elem->private_data);
+	int index = ucontrol->id.index;
+
+	/* get low switch from cache is enough, cause all bands are together */
+	val = store->val[EQ_STORE_BAND_IDX(elem->head.id)]
+		[EQ_STORE_PARAM_IDX(elem->head.id)][index];
+	ucontrol->value.integer.value[0] = val;
+
+	return 0;
+}
+
+static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_eq_store *store =
+		((struct snd_us16x08_eq_store *) elem->private_data);
+	int index = ucontrol->id.index;
+
+	char buf[sizeof(eqs_msq)];
+	int val, err = 0;
+	int b_idx;
+
+	/* new control value incl. bias*/
+	val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol);
+
+	/* prepare URB message from EQ template */
+	memcpy(buf, eqs_msq, sizeof(eqs_msq));
+
+	/* place channel index in URB message */
+	buf[5] = index + 1;
+	for (b_idx = 0; b_idx < SND_US16X08_ID_EQ_BAND_COUNT; b_idx++) {
+		/* all four EQ bands have to be enabled/disabled in once */
+		buf[20] = val;
+		buf[17] = store->val[b_idx][2][index];
+		buf[14] = store->val[b_idx][1][index];
+		buf[11] = store->val[b_idx][0][index];
+		buf[8] = b_idx + 1;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(eqs_msq));
+		if (err < 0)
+			break;
+		store->val[b_idx][3][index] = val;
+		msleep(15);
+	}
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_err(chip, "Failed to set eq switch, err:%d\n", err);
+	}
+
+	return 1;
+}
+
+static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int val = 0;
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_eq_store *store =
+		((struct snd_us16x08_eq_store *) elem->private_data);
+	int index = ucontrol->id.index;
+	int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1;
+	int p_idx = EQ_STORE_PARAM_IDX(elem->head.id);
+
+	val = store->val[b_idx][p_idx][index];
+
+	ucontrol->value.integer.value[0] = val;
+
+	return 0;
+}
+
+static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_eq_store *store =
+		((struct snd_us16x08_eq_store *) elem->private_data);
+	int index = ucontrol->id.index;
+
+	char buf[sizeof(eqs_msq)];
+	int val, err = 0;
+	int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1;
+	int p_idx = EQ_STORE_PARAM_IDX(elem->head.id);
+
+	/* copy URB buffer from EQ template */
+	memcpy(buf, eqs_msq, sizeof(eqs_msq));
+
+	val = ucontrol->value.integer.value[0];
+
+	store->val[b_idx][p_idx][index] = val;
+	buf[20] = store->val[b_idx][3][index];
+	buf[17] = store->val[b_idx][2][index];
+	buf[14] = store->val[b_idx][1][index];
+	buf[11] = store->val[b_idx][0][index];
+
+	/* place channel index in URB buffer */
+	buf[5] = index + 1;
+
+	/* place EQ band in URB buffer */
+	buf[8] = b_idx + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(eqs_msq));
+
+	if (err > 0) {
+		/* store new value in EQ band cache */
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_err(chip, "Failed to set eq param, err:%d\n", err);
+	}
+
+	return 1;
+}
+
+static int snd_us16x08_meter_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->count = 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.max = 0x7FFF;
+	uinfo->value.integer.min = 0;
+
+	return 0;
+}
+
+/* calculate compressor index for reduction level request */
+static int snd_get_meter_comp_index(struct snd_us16x08_meter_store *store)
+{
+	int ret;
+
+	/* any channel active */
+	if (store->comp_active_index) {
+		/* check for stereo link */
+		if (store->comp_active_index & 0x20) {
+			/* reset comp_index to left channel*/
+			if (store->comp_index -
+				store->comp_active_index > 1)
+				store->comp_index =
+				store->comp_active_index;
+
+			ret = store->comp_index++ & 0x1F;
+		} else {
+			/* no stereo link */
+			ret = store->comp_active_index;
+		}
+	} else {
+		/* skip channels with no compressor active */
+		while (!store->comp_store->val[
+			COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)]
+			[store->comp_index - 1]
+			&& store->comp_index <= SND_US16X08_MAX_CHANNELS) {
+			store->comp_index++;
+		}
+		ret = store->comp_index++;
+		if (store->comp_index > SND_US16X08_MAX_CHANNELS)
+			store->comp_index = 1;
+	}
+	return ret;
+}
+
+/* retrieve the meter level values from URB message */
+static void get_meter_levels_from_urb(int s,
+	struct snd_us16x08_meter_store *store,
+	u8 *meter_urb)
+{
+	int val = MUC2(meter_urb, s) + (MUC3(meter_urb, s) << 8);
+
+	if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 &&
+		MUA2(meter_urb, s) == 0x04 && MUB0(meter_urb, s) == 0x62) {
+		if (MUC0(meter_urb, s) == 0x72)
+			store->meter_level[MUB2(meter_urb, s) - 1] = val;
+		if (MUC0(meter_urb, s) == 0xb2)
+			store->comp_level[MUB2(meter_urb, s) - 1] = val;
+	}
+	if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 &&
+		MUA2(meter_urb, s) == 0x02 && MUB0(meter_urb, s) == 0x62)
+		store->master_level[MUB2(meter_urb, s) - 1] = val;
+}
+
+/* Function to retrieve current meter values from the device.
+ *
+ * The device needs to be polled for meter values with an initial
+ * requests. It will return with a sequence of different meter value
+ * packages. The first request (case 0:) initiate this meter response sequence.
+ * After the third response, an additional request can be placed,
+ * to retrieve compressor reduction level value for given channel. This round
+ * trip channel selector will skip all inactive compressors.
+ * A mixer can interrupt this round-trip by selecting one ore two (stereo-link)
+ * specific channels.
+ */
+static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int i, set;
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_meter_store *store = elem->private_data;
+	u8 meter_urb[64];
+
+
+	if (elem) {
+		store = (struct snd_us16x08_meter_store *) elem->private_data;
+		chip = elem->head.mixer->chip;
+	} else
+		return 0;
+
+	switch (kcontrol->private_value) {
+	case 0:
+		snd_us16x08_send_urb(chip, mix_init_msg1, 4);
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value++;
+		break;
+	case 1:
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value++;
+		break;
+	case 2:
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value++;
+		break;
+	case 3:
+		mix_init_msg2[2] = snd_get_meter_comp_index(store);
+		snd_us16x08_send_urb(chip, mix_init_msg2, 10);
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value = 0;
+		break;
+	}
+
+	for (set = 0; set < 6; set++)
+		get_meter_levels_from_urb(set, store, meter_urb);
+
+	for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) {
+		ucontrol->value.integer.value[i] =
+			store ? store->meter_level[i] : 0;
+	}
+
+	ucontrol->value.integer.value[i++] = store ? store->master_level[0] : 0;
+	ucontrol->value.integer.value[i++] = store ? store->master_level[1] : 0;
+
+	for (i = 2; i < SND_US16X08_MAX_CHANNELS + 2; i++)
+		ucontrol->value.integer.value[i + SND_US16X08_MAX_CHANNELS] =
+		store ? store->comp_level[i - 2] : 0;
+
+	return 1;
+}
+
+static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_meter_store *store = elem->private_data;
+	int val;
+
+	val = ucontrol->value.integer.value[0];
+	store->comp_active_index = val;
+	store->comp_index = val;
+
+	return 1;
+}
+
+static struct snd_kcontrol_new snd_us16x08_ch_boolean_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_channel_get,
+	.put = snd_us16x08_channel_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_ch_int_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_channel_get,
+	.put = snd_us16x08_channel_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133)
+};
+
+static struct snd_kcontrol_new snd_us16x08_master_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 1,
+	.info = snd_us16x08_master_info,
+	.get = snd_us16x08_master_get,
+	.put = snd_us16x08_master_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133)
+};
+
+static struct snd_kcontrol_new snd_us16x08_route_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 8,
+	.info = snd_us16x08_route_info,
+	.get = snd_us16x08_route_get,
+	.put = snd_us16x08_route_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 9)
+};
+
+static struct snd_kcontrol_new snd_us16x08_bus_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 1,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_bus_get,
+	.put = snd_us16x08_bus_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_compswitch_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_threshold_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_COMP_THRESHOLD_BIAS, 1,
+	0, 0x20)
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_ratio_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0,
+	sizeof(ratio_map) - 1), /*max*/
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_gain_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x14)
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_attack_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value =
+	SND_US16X08_KCSET(SND_US16X08_COMP_ATTACK_BIAS, 1, 0, 0xc6),
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_release_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value =
+	SND_US16X08_KCSET(SND_US16X08_COMP_RELEASE_BIAS, 1, 0, 0x63),
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_gain_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 24),
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_low_freq_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x1F),
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_mid_freq_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x3F)
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_mid_width_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x06)
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_high_freq_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value =
+	SND_US16X08_KCSET(SND_US16X08_EQ_HIGHFREQ_BIAS, 1, 0, 0x1F)
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_switch_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_eqswitch_get,
+	.put = snd_us16x08_eqswitch_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_meter_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 1,
+	.info = snd_us16x08_meter_info,
+	.get = snd_us16x08_meter_get,
+	.put = snd_us16x08_meter_put
+};
+
+/* control store preparation */
+
+/* setup compressor store and assign default value */
+static struct snd_us16x08_comp_store *snd_us16x08_create_comp_store(void)
+{
+	int i = 0;
+	struct snd_us16x08_comp_store *tmp =
+		kmalloc(sizeof(struct snd_us16x08_comp_store), GFP_KERNEL);
+
+	if (tmp == NULL)
+		return NULL;
+
+	for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) {
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_THRESHOLD)][i]
+			= 0x20;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RATIO)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_GAIN)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_ATTACK)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RELEASE)][i] = 0x00;
+	}
+	return tmp;
+}
+
+/* setup EQ store and assign default values */
+static struct snd_us16x08_eq_store *snd_us16x08_create_eq_store(void)
+{
+	int i, b_idx;
+	struct snd_us16x08_eq_store *tmp =
+		kmalloc(sizeof(struct snd_us16x08_eq_store), GFP_KERNEL);
+
+	if (tmp == NULL)
+		return NULL;
+
+	for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) {
+		for (b_idx = 0; b_idx < SND_US16X08_ID_EQ_BAND_COUNT; b_idx++) {
+			tmp->val[b_idx][0][i] = 0x0c;
+			tmp->val[b_idx][3][i] = 0x00;
+			switch (b_idx) {
+			case 0: /* EQ Low */
+				tmp->val[b_idx][1][i] = 0x05;
+				tmp->val[b_idx][2][i] = 0xff;
+				break;
+			case 1: /* EQ Mid low */
+				tmp->val[b_idx][1][i] = 0x0e;
+				tmp->val[b_idx][2][i] = 0x02;
+				break;
+			case 2: /* EQ Mid High */
+				tmp->val[b_idx][1][i] = 0x1b;
+				tmp->val[b_idx][2][i] = 0x02;
+				break;
+			case 3: /* EQ High */
+				tmp->val[b_idx][1][i] = 0x2f
+					- SND_US16X08_EQ_HIGHFREQ_BIAS;
+				tmp->val[b_idx][2][i] = 0xff;
+				break;
+			}
+		}
+	}
+	return tmp;
+}
+
+struct snd_us16x08_meter_store *snd_us16x08_create_meter_store(void)
+{
+	struct snd_us16x08_meter_store *tmp =
+		kzalloc(sizeof(struct snd_us16x08_meter_store), GFP_KERNEL);
+
+	if (!tmp)
+		return NULL;
+	tmp->comp_index = 1;
+	tmp->comp_active_index = 0;
+	return tmp;
+
+}
+
+static int add_new_ctl(struct usb_mixer_interface *mixer,
+	const struct snd_kcontrol_new *ncontrol,
+	int index, int val_type, int channels,
+	const char *name, const void *opt,
+	void (*freeer)(struct snd_kcontrol *kctl),
+	struct usb_mixer_elem_info **elem_ret)
+{
+	struct snd_kcontrol *kctl;
+	struct usb_mixer_elem_info *elem;
+	int err;
+
+	usb_audio_dbg(mixer->chip, "us16x08 add mixer %s\n", name);
+
+	elem = kzalloc(sizeof(*elem), GFP_KERNEL);
+	if (!elem)
+		return -ENOMEM;
+
+	elem->head.mixer = mixer;
+	elem->head.resume = NULL;
+	elem->control = 0;
+	elem->idx_off = 0;
+	elem->head.id = index;
+	elem->val_type = val_type;
+	elem->channels = channels;
+	elem->private_data = (void *) opt;
+
+	kctl = snd_ctl_new1(ncontrol, elem);
+	if (!kctl) {
+		kfree(elem);
+		return -ENOMEM;
+	}
+
+	kctl->private_free = freeer;
+
+	strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+
+	err = snd_usb_mixer_add_control(&elem->head, kctl);
+	if (err < 0)
+		return err;
+
+	if (elem_ret)
+		*elem_ret = elem;
+
+	return 0;
+}
+
+static struct snd_us16x08_control_params control_params;
+
+/* table of EQ controls */
+static struct snd_us16x08_control_params eq_controls[] = {
+	{ /* EQ switch */
+		.kcontrol_new = &snd_us16x08_eq_switch_ctl,
+		.control_id = SND_US16X08_ID_EQENABLE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "EQ enable",
+		.freeer = snd_usb_mixer_elem_free
+	},
+	{ /* EQ low gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQLOWLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Low gain",
+		.freeer = snd_usb_mixer_elem_free
+	},
+	{ /* EQ low freq */
+		.kcontrol_new = &snd_us16x08_eq_low_freq_ctl,
+		.control_id = SND_US16X08_ID_EQLOWFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Low freq",
+		.freeer = NULL
+	},
+	{ /* EQ mid low gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQLOWMIDLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Mid low gain",
+		.freeer = snd_usb_mixer_elem_free
+	},
+	{ /* EQ mid low freq */
+		.kcontrol_new = &snd_us16x08_eq_mid_freq_ctl,
+		.control_id = SND_US16X08_ID_EQLOWMIDFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Mid low freq",
+		.freeer = NULL
+	},
+	{ /* EQ mid low Q */
+		.kcontrol_new = &snd_us16x08_eq_mid_width_ctl,
+		.control_id = SND_US16X08_ID_EQLOWMIDWIDTH,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Mid low Q",
+		.freeer = NULL
+	},
+	{ /* EQ mid high gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHMIDLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Mid high gain",
+		.freeer = snd_usb_mixer_elem_free
+	},
+	{ /* EQ mid high freq */
+		.kcontrol_new = &snd_us16x08_eq_mid_freq_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHMIDFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Mid high freq",
+		.freeer = NULL
+	},
+	{ /* EQ mid high Q */
+		.kcontrol_new = &snd_us16x08_eq_mid_width_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHMIDWIDTH,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Mid high Q",
+		.freeer = NULL
+	},
+	{ /* EQ high gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "High gain",
+		.freeer = snd_usb_mixer_elem_free
+	},
+	{ /* EQ low freq */
+		.kcontrol_new = &snd_us16x08_eq_high_freq_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "High freq",
+		.freeer = NULL
+	},
+};
+
+/* table of compressor controls */
+static struct snd_us16x08_control_params comp_controls[] = {
+	{ /* Comp enable */
+		.kcontrol_new = &snd_us16x08_compswitch_ctl,
+		.control_id = SND_US16X08_ID_COMP_SWITCH,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Comp enable",
+		.freeer = snd_usb_mixer_elem_free
+	},
+	{ /* Comp threshold */
+		.kcontrol_new = &snd_us16x08_comp_threshold_ctl,
+		.control_id = SND_US16X08_ID_COMP_THRESHOLD,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Threshold",
+		.freeer = NULL
+	},
+	{ /* Comp ratio */
+		.kcontrol_new = &snd_us16x08_comp_ratio_ctl,
+		.control_id = SND_US16X08_ID_COMP_RATIO,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Ratio",
+		.freeer = NULL
+	},
+	{ /* Comp attack */
+		.kcontrol_new = &snd_us16x08_comp_attack_ctl,
+		.control_id = SND_US16X08_ID_COMP_ATTACK,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Attack",
+		.freeer = NULL
+	},
+	{ /* Comp release */
+		.kcontrol_new = &snd_us16x08_comp_release_ctl,
+		.control_id = SND_US16X08_ID_COMP_RELEASE,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Release",
+		.freeer = NULL
+	},
+	{ /* Comp gain */
+		.kcontrol_new = &snd_us16x08_comp_gain_ctl,
+		.control_id = SND_US16X08_ID_COMP_GAIN,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Gain",
+		.freeer = NULL
+	},
+};
+
+/* table of channel controls */
+static struct snd_us16x08_control_params channel_controls[] = {
+	{ /* Phase */
+		.kcontrol_new = &snd_us16x08_ch_boolean_ctl,
+		.control_id = SND_US16X08_ID_PHASE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Phase",
+		.freeer = snd_usb_mixer_elem_free,
+		.default_val = 0
+	},
+	{ /* Fader */
+		.kcontrol_new = &snd_us16x08_ch_int_ctl,
+		.control_id = SND_US16X08_ID_FADER,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Fader",
+		.freeer = NULL,
+		.default_val = 127
+	},
+	{ /* Mute */
+		.kcontrol_new = &snd_us16x08_ch_boolean_ctl,
+		.control_id = SND_US16X08_ID_MUTE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Mute",
+		.freeer = NULL,
+		.default_val = 0
+	},
+	{ /* Pan */
+		.kcontrol_new = &snd_us16x08_ch_int_ctl,
+		.control_id = SND_US16X08_ID_PAN,
+		.type = USB_MIXER_U16,
+		.num_channels = 16,
+		.name = "Pan",
+		.freeer = NULL,
+		.default_val = 127
+	},
+};
+
+/* table of master controls */
+static struct snd_us16x08_control_params master_controls[] = {
+	{ /* Master */
+		.kcontrol_new = &snd_us16x08_master_ctl,
+		.control_id = SND_US16X08_ID_FADER,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Master",
+		.freeer = NULL,
+		.default_val = 127
+	},
+	{ /* Bypass */
+		.kcontrol_new = &snd_us16x08_bus_ctl,
+		.control_id = SND_US16X08_ID_BYPASS,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Bypass",
+		.freeer = NULL,
+		.default_val = 0
+	},
+	{ /* Buss out */
+		.kcontrol_new = &snd_us16x08_bus_ctl,
+		.control_id = SND_US16X08_ID_BUSS_OUT,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Buss out",
+		.freeer = NULL,
+		.default_val = 0
+	},
+	{ /* Master mute */
+		.kcontrol_new = &snd_us16x08_bus_ctl,
+		.control_id = SND_US16X08_ID_MUTE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Master mute",
+		.freeer = NULL,
+		.default_val = 0
+	},
+
+};
+
+int snd_us16x08_controls_create(struct usb_mixer_interface *mixer)
+{
+	int i, j;
+	int err;
+	struct usb_mixer_elem_info *elem;
+	struct snd_us16x08_comp_store *comp_store;
+	struct snd_us16x08_meter_store *meter_store;
+	struct snd_us16x08_eq_store *eq_store;
+
+	/* just check for non-MIDI interface */
+	if (mixer->hostif->desc.bInterfaceNumber == 3) {
+
+		/* create compressor mixer elements */
+		comp_store = snd_us16x08_create_comp_store();
+		if (comp_store == NULL)
+			return -ENOMEM;
+
+		/* create eq store */
+		eq_store = snd_us16x08_create_eq_store();
+		if (eq_store == NULL) {
+			kfree(comp_store);
+			return -ENOMEM;
+		}
+
+		/* create meters store */
+		meter_store = snd_us16x08_create_meter_store();
+		if (meter_store == NULL) {
+			kfree(comp_store);
+			kfree(eq_store);
+			return -ENOMEM;
+		}
+
+		/* add routing control */
+		err = add_new_ctl(mixer, &snd_us16x08_route_ctl,
+			SND_US16X08_ID_ROUTE, USB_MIXER_U8, 8, "Route",
+			NULL, NULL, &elem);
+		if (err < 0) {
+			usb_audio_err(mixer->chip,
+				"Failed to create route control, err:%d\n",
+				err);
+			return err;
+		}
+		for (i = 0; i < 8; i++)
+			elem->cache_val[i] = i < 2 ? i : i + 2;
+		elem->cached = 0xff;
+
+		/* add master controls */
+		for (i = 0;
+			i < sizeof(master_controls)
+			/ sizeof(control_params);
+			i++) {
+
+			err = add_new_ctl(mixer,
+				master_controls[i].kcontrol_new,
+				master_controls[i].control_id,
+				master_controls[i].type,
+				master_controls[i].num_channels,
+				master_controls[i].name,
+				comp_store,
+				master_controls[i].freeer, &elem);
+			if (err < 0)
+				return err;
+			elem->cache_val[0] = master_controls[i].default_val;
+			elem->cached = 1;
+		}
+
+		/* add channel controls */
+		for (i = 0;
+			i < sizeof(channel_controls)
+			/ sizeof(control_params);
+			i++) {
+
+			err = add_new_ctl(mixer,
+				channel_controls[i].kcontrol_new,
+				channel_controls[i].control_id,
+				channel_controls[i].type,
+				channel_controls[i].num_channels,
+				channel_controls[i].name,
+				comp_store,
+				channel_controls[i].freeer, &elem);
+			if (err < 0)
+				return err;
+			for (j = 0; j < SND_US16X08_MAX_CHANNELS; j++) {
+				elem->cache_val[j] =
+					channel_controls[i].default_val;
+			}
+			elem->cached = 0xffff;
+		}
+
+		/* add EQ controls */
+		for (i = 0; i < sizeof(eq_controls) /
+			sizeof(control_params); i++) {
+
+			err = add_new_ctl(mixer,
+				eq_controls[i].kcontrol_new,
+				eq_controls[i].control_id,
+				eq_controls[i].type,
+				eq_controls[i].num_channels,
+				eq_controls[i].name,
+				eq_store,
+				eq_controls[i].freeer, NULL);
+			if (err < 0)
+				return err;
+		}
+
+		/* add compressor controls */
+		for (i = 0;
+			i < sizeof(comp_controls)
+			/ sizeof(control_params);
+			i++) {
+
+			err = add_new_ctl(mixer,
+				comp_controls[i].kcontrol_new,
+				comp_controls[i].control_id,
+				comp_controls[i].type,
+				comp_controls[i].num_channels,
+				comp_controls[i].name,
+				comp_store,
+				comp_controls[i].freeer, NULL);
+			if (err < 0)
+				return err;
+		}
+
+		/* meter function 'get' must access to compressor store
+		 * so place a reference here
+		 */
+		meter_store->comp_store = comp_store;
+		err = add_new_ctl(mixer, &snd_us16x08_meter_ctl,
+			SND_US16X08_ID_METER, USB_MIXER_U16, 0, "Meter",
+			(void *) meter_store, snd_usb_mixer_elem_free, NULL);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h
new file mode 100644
index 0000000..64f89b5
--- /dev/null
+++ b/sound/usb/mixer_us16x08.h
@@ -0,0 +1,122 @@
+#ifndef __USB_MIXER_US16X08_H
+#define __USB_MIXER_US16X08_H
+
+#define SND_US16X08_MAX_CHANNELS 16
+
+/* define some bias, cause some alsa-mixers wont work with
+ * negative ranges or if mixer-min != 0
+ */
+#define SND_US16X08_NO_BIAS 0
+#define SND_US16X08_FADER_BIAS 127
+#define SND_US16X08_EQ_HIGHFREQ_BIAS 0x20
+#define SND_US16X08_COMP_THRESHOLD_BIAS 0x20
+#define SND_US16X08_COMP_ATTACK_BIAS 2
+#define SND_US16X08_COMP_RELEASE_BIAS 1
+
+/* get macro for components of kcontrol private_value */
+#define SND_US16X08_KCBIAS(x) (((x)->private_value >> 24) & 0xff)
+#define SND_US16X08_KCSTEP(x) (((x)->private_value >> 16) & 0xff)
+#define SND_US16X08_KCMIN(x) (((x)->private_value >> 8) & 0xff)
+#define SND_US16X08_KCMAX(x) (((x)->private_value >> 0) & 0xff)
+/* set macro for kcontrol private_value */
+#define SND_US16X08_KCSET(bias, step, min, max)  \
+	(((bias) << 24) | ((step) << 16) | ((min) << 8) | (max))
+
+/* the URB request/type to control Tascam mixers */
+#define SND_US16X08_URB_REQUEST 0x1D
+#define SND_US16X08_URB_REQUESTTYPE 0x40
+
+/* the URB params to retrieve meter ranges */
+#define SND_US16X08_URB_METER_REQUEST       0x1e
+#define SND_US16X08_URB_METER_REQUESTTYPE   0xc0
+
+#define MUA0(x, y) ((x)[(y) * 10 + 4])
+#define MUA1(x, y) ((x)[(y) * 10 + 5])
+#define MUA2(x, y) ((x)[(y) * 10 + 6])
+#define MUB0(x, y) ((x)[(y) * 10 + 7])
+#define MUB1(x, y) ((x)[(y) * 10 + 8])
+#define MUB2(x, y) ((x)[(y) * 10 + 9])
+#define MUC0(x, y) ((x)[(y) * 10 + 10])
+#define MUC1(x, y) ((x)[(y) * 10 + 11])
+#define MUC2(x, y) ((x)[(y) * 10 + 12])
+#define MUC3(x, y) ((x)[(y) * 10 + 13])
+
+/* Common Channel control IDs */
+#define SND_US16X08_ID_BYPASS 0x45
+#define SND_US16X08_ID_BUSS_OUT 0x44
+#define SND_US16X08_ID_PHASE 0x85
+#define SND_US16X08_ID_MUTE 0x83
+#define SND_US16X08_ID_FADER 0x81
+#define SND_US16X08_ID_PAN 0x82
+#define SND_US16X08_ID_METER 0xB1
+
+#define SND_US16X08_ID_EQ_BAND_COUNT 4
+#define SND_US16X08_ID_EQ_PARAM_COUNT 4
+
+/* EQ level IDs */
+#define SND_US16X08_ID_EQLOWLEVEL 0x01
+#define SND_US16X08_ID_EQLOWMIDLEVEL 0x02
+#define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03
+#define SND_US16X08_ID_EQHIGHLEVEL 0x04
+
+/* EQ frequence IDs */
+#define SND_US16X08_ID_EQLOWFREQ 0x11
+#define SND_US16X08_ID_EQLOWMIDFREQ 0x12
+#define SND_US16X08_ID_EQHIGHMIDFREQ 0x13
+#define SND_US16X08_ID_EQHIGHFREQ 0x14
+
+/* EQ width IDs */
+#define SND_US16X08_ID_EQLOWMIDWIDTH 0x22
+#define SND_US16X08_ID_EQHIGHMIDWIDTH 0x23
+
+#define SND_US16X08_ID_EQENABLE 0x30
+
+#define EQ_STORE_BAND_IDX(x) ((x) & 0xf)
+#define EQ_STORE_PARAM_IDX(x) (((x) & 0xf0) >> 4)
+
+#define SND_US16X08_ID_ROUTE 0x00
+
+/* Compressor Ids */
+#define SND_US16X08_ID_COMP_BASE	0x32
+#define SND_US16X08_ID_COMP_THRESHOLD	SND_US16X08_ID_COMP_BASE
+#define SND_US16X08_ID_COMP_RATIO	(SND_US16X08_ID_COMP_BASE + 1)
+#define SND_US16X08_ID_COMP_ATTACK	(SND_US16X08_ID_COMP_BASE + 2)
+#define SND_US16X08_ID_COMP_RELEASE	(SND_US16X08_ID_COMP_BASE + 3)
+#define SND_US16X08_ID_COMP_GAIN	(SND_US16X08_ID_COMP_BASE + 4)
+#define SND_US16X08_ID_COMP_SWITCH	(SND_US16X08_ID_COMP_BASE + 5)
+#define SND_US16X08_ID_COMP_COUNT	6
+
+#define COMP_STORE_IDX(x) ((x) - SND_US16X08_ID_COMP_BASE)
+
+struct snd_us16x08_eq_store {
+	u8 val[SND_US16X08_ID_EQ_BAND_COUNT][SND_US16X08_ID_EQ_PARAM_COUNT]
+		[SND_US16X08_MAX_CHANNELS];
+};
+
+struct snd_us16x08_comp_store {
+	u8 val[SND_US16X08_ID_COMP_COUNT][SND_US16X08_MAX_CHANNELS];
+};
+
+struct snd_us16x08_meter_store {
+	int meter_level[SND_US16X08_MAX_CHANNELS];
+	int master_level[2]; /* level of meter for master output */
+	int comp_index; /* round trip channel selector */
+	int comp_active_index; /* channel select from user space mixer */
+	int comp_level[16]; /* compressor reduction level */
+	struct snd_us16x08_comp_store *comp_store;
+};
+
+struct snd_us16x08_control_params {
+	struct snd_kcontrol_new *kcontrol_new;
+	int control_id;
+	int type;
+	int num_channels;
+	const char *name;
+	void (*freeer)(struct snd_kcontrol *kctl);
+	int default_val;
+};
+
+#define snd_us16x08_switch_info snd_ctl_boolean_mono_info
+
+int snd_us16x08_controls_create(struct usb_mixer_interface *mixer);
+#endif /* __USB_MIXER_US16X08_H */
-- 
2.7.4



More information about the Alsa-devel mailing list