diff mbox

[4/4,v4] ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20

Message ID 1415055496-11151-5-git-send-email-chris.j.arges@canonical.com (mailing list archive)
State Superseded
Delegated to: Takashi Iwai
Headers show

Commit Message

Chris J Arges Nov. 3, 2014, 10:58 p.m. UTC
This code contains the Scarlett mixer interface code that was originally
written by Tobias Hoffman and Robin Gareus. Because the device doesn't
properly implement UAC2 this code adds a mixer quirk for the device.

Changes from the original code include removing the metering code along with
dead code and comments. Compiler warnings were fixed. The code to initialize
the sampling rate was causing a crash this was fixed as discussed on the
mailing list. Error, and info messages were convered to dev_err and dev_info
interfaces. The custom scarlett_mixer_elem_info struct was replaced with the
more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.

This patch also makes additional modifications based on upstream comments.
Individual control creation functions are removed and a generic
function is no used. Macros for function calls are removed to improve
readability. Hardcoded control initialization is removed.

Signed-off-by: Chris J Arges <chris.j.arges@canonical.com>
---
 sound/usb/Makefile         |    1 +
 sound/usb/mixer_quirks.c   |    9 +
 sound/usb/mixer_scarlett.c | 1070 ++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/mixer_scarlett.h |    6 +
 4 files changed, 1086 insertions(+)
 create mode 100644 sound/usb/mixer_scarlett.c
 create mode 100644 sound/usb/mixer_scarlett.h

Comments

Takashi Iwai Nov. 4, 2014, 10:18 a.m. UTC | #1
At Mon,  3 Nov 2014 16:58:16 -0600,
Chris J Arges wrote:
> 
> This code contains the Scarlett mixer interface code that was originally
> written by Tobias Hoffman and Robin Gareus. Because the device doesn't
> properly implement UAC2 this code adds a mixer quirk for the device.
> 
> Changes from the original code include removing the metering code along with
> dead code and comments. Compiler warnings were fixed. The code to initialize
> the sampling rate was causing a crash this was fixed as discussed on the
> mailing list. Error, and info messages were convered to dev_err and dev_info
> interfaces. The custom scarlett_mixer_elem_info struct was replaced with the
> more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
> 
> This patch also makes additional modifications based on upstream comments.
> Individual control creation functions are removed and a generic
> function is no used. Macros for function calls are removed to improve
> readability. Hardcoded control initialization is removed.
> 
> Signed-off-by: Chris J Arges <chris.j.arges@canonical.com>

The new patch looks almost good.  A remaining concern is:

> +static const struct scarlett_mixer_elem_enum_info opt_save = {
> +	.start = 0,
> +	.len = 2,
> +	.names = (const char * const[]){
> +		"---", "Save"

This isn't quite intuitive.  And I think this is an abuse of ctl
enum (see below).

Also...

> +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
> +				 struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct usb_mixer_elem_info *elem = kctl->private_data;
> +	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
> +	int err, val;
> +
> +	err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
> +	if (err < 0)
> +		return err;
> +
> +	if ((opt->start == -1) && (val > opt->len)) /* >= 0x20 */
> +		val = 0;
> +	else
> +		val = clamp(val - opt->start, 0, opt->len-1);

Is the if condition above really correct?  It's not obvious to me what
this really checks.

Now back to "save to hw" ctl:

> +static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
> +				 struct snd_ctl_elem_value *ucontrol)
> +{
> +	ucontrol->value.enumerated.item[0] = 0;
> +	return 0;
> +}

So, here is the problem.  You want to use this ctl to trigger
something.  This is no correct behavior for an enum ctl.
If the put callback succeeds, the value should be stored.
(Of course, then it won't work as you expected.)

What actually this control does?  Why it can't be done always
(transparently)?

> +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
> +				 struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct usb_mixer_elem_info *elem = kctl->private_data;
> +	struct snd_usb_audio *chip = elem->mixer->chip;
> +	char buf[] = { 0x00, 0xa5 };
> +	int err;
> +
> +	if (ucontrol->value.enumerated.item[0] > 0) {
> +		err = snd_usb_ctl_msg(chip->dev,
> +			usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
> +			USB_RECIP_INTERFACE | USB_TYPE_CLASS |
> +			USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
> +			(0x3c << 8), buf, 2);
> +		if (err < 0)
> +			return err;
> +
> +		usb_audio_info(elem->mixer->chip,
> +			 "scarlett: saved settings to hardware.\n");

Using *_info() here is rather annoying, it may spew too many random
messages.

> +static int add_new_ctl(struct usb_mixer_interface *mixer,
> +		       const struct snd_kcontrol_new *ncontrol,
> +		       int index, int offset, int num,
> +		       int val_type, int channels, const char *name,
> +		       const struct scarlett_mixer_elem_enum_info *opt,
> +		       struct usb_mixer_elem_info **elem_ret
> +)
> +{
> +	struct snd_kcontrol *kctl;
> +	struct usb_mixer_elem_info *elem;
> +	int err;
> +
> +	elem = kzalloc(sizeof(*elem), GFP_KERNEL);
> +	if (!elem)
> +		return -ENOMEM;
> +
> +	elem->mixer = mixer;
> +	elem->control = offset;
> +	elem->idx_off = num;
> +	elem->id = index;
> +	elem->val_type = val_type;
> +
> +	elem->channels = channels;
> +
> +	/* add scarlett_mixer_elem_enum_info struct */
> +	elem->private_data = (void *)opt;
> +
> +	kctl = snd_ctl_new1(ncontrol, elem);
> +	if (!kctl) {
> +		usb_audio_err(mixer->chip, "cannot malloc kcontrol\n");

This is superfluous.  kmalloc() already gives warning.

> +static int add_output_ctls(struct usb_mixer_interface *mixer,
> +			   int index, const char *name,
> +			   const struct scarlett_device_info *info)
> +{
> +	int err;
> +	char mx[48];

Use SNDRV_CTL_ELEM_ID_NAME_MAXLEN (which is 44).

> +static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer,
> +	const struct scarlett_device_info *info)
> +{
> +	int i = 0;
> +	int err = 0;
> +	char mx[32];

Ditto.
Also, use snprintf() consistently.

> +/*
> + * Create and initialize a mixer for the Focusrite(R) Scarlett
> + */
> +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer)
> +{
> +	int err, i, o;
> +	char mx[32];

Ditto.


thanks,

Takashi
Tobias Hoffmann Nov. 4, 2014, 1:16 p.m. UTC | #2
> +#define TXT_OFF	"Off"
> +#define TXT_PCM(num) "PCM " #num
> +#define TXT_ANALOG(num) "Analog " #num
> +#define TXT_SPDIF(num) "SPDIF " #num
> +#define TXT_ADAT(num) "ADAT " #num
> +#define TXT_MIX(letter) "MIX " #letter

My reasoning for using:
   static const char txtOff[] = "Off",
           txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
           txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",  [...]
instead of repeating the strings for each device (or using macros that 
expand to the same strings) was that the final .o / .ko would contain 
the string data only once.

But maybe (as Clemens suggested) the strings *for .opt_master and 
.opt_matrix* should just be created dynamically in 
scarlett_ctl_enum_info, instead of using snd_ctl_enum_info.
I want to point out that scarlett_device_info already contains the 
necessary information to do this:

	.matrix_out = 6,
...
	.pcm_start = 0,  // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12)
	.analog_start = 12, // analog(1) .. analog(4)
	.spdif_start = 16, // spdif(1) .. spdif(2)
	.adat_start = 18,  // adat(1) .. adat(0)  [i.e. no adat]
	.mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)



and Off(-1). Only elem->private_data has to be of type 
scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), 
but of type scarlett_device_info for the master and mixer enums...

Some more comments inline:

On 04/11/14 11:18, Takashi Iwai wrote:
> The new patch looks almost good. A remaining concern is:
>> +static const struct scarlett_mixer_elem_enum_info opt_save = {
>> +	.start = 0,
>> +	.len = 2,
>> +	.names = (const char * const[]){
>> +		"---", "Save"
> This isn't quite intuitive.  And I think this is an abuse of ctl
> enum (see below).
It's a hack to expose the "Save to hardware" functionality without 
requiring a special mixer application. Robin's original patch already 
contained this (ab)use.


> Also...
>
>> +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
>> +				 struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct usb_mixer_elem_info *elem = kctl->private_data;
>> +	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
>> +	int err, val;
>> +
>> +	err = snd_usb_get_cur_mix_value(elem, 0, 0,&val);
>> +	if (err<  0)
>> +		return err;
>> +
>> +	if ((opt->start == -1)&&  (val>  opt->len)) /*>= 0x20 */
>> +		val = 0;
>> +	else
>> +		val = clamp(val - opt->start, 0, opt->len-1);
> Is the if condition above really correct?  It's not obvious to me what
> this really checks.

(opt->start == -1) is used in enums that control the routing, to 
represent the "Off"-value.
But the device uses number_of_master_enum_values+1 (even for the matrix 
enum) as "Off".
That would make "Off" the last enum value in Mixer applications, which 
is undesired (and, for the matrix enums, this still results in a gap 
between last_matrix_enum_index and Off=last_mixer_enum_index).

scarlett_ctl_enum_put did contain the inverse logic (before cleanup):

> #if 0 // TODO?
>         if (val == -1) {
>                 val = elem->enum->len + 1; /* only true for master, 
> not for mixer [also master must be used] */
>                 // ... or? > 0x20,  18i8: 0x22
>         } else
> #endif
>         val = val + elem->opt->start;

It is commented out, because the device also recognized val = -1 + -1; 
as "Off", without the need to somehow expose the device-specific 
enum->len of .opt_master to all the .opt_matrix controls.
It's a bit dirty, but it works...


> Now back to "save to hw" ctl:
>
>> +static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
>> +				 struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	ucontrol->value.enumerated.item[0] = 0;
>> +	return 0;
>> +}
> So, here is the problem.  You want to use this ctl to trigger
> something.  This is no correct behavior for an enum ctl.
> If the put callback succeeds, the value should be stored.
> (Of course, then it won't work as you expected.)
>
> What actually this control does?  Why it can't be done always
> (transparently)?

What it does is it store the current settings in non-volatile memory for 
standalone operation (i.e. USB not connected). It's the device 
configuration after power-on.

Loading this driver then reinitalizes the device to basically zero 
values (userspace "acontrol restore" will finally set the device to what 
the user expects).
The zeroing is unfortunately necessary, because the device does not 
support reading back certain mixer values after power-on (only garbage 
is returned). After initialization, only cval->cache_val is ever 
returned by snd_usb_get_cur_mix_value.


>> +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
>> +				 struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct usb_mixer_elem_info *elem = kctl->private_data;
>> +	struct snd_usb_audio *chip = elem->mixer->chip;
>> +	char buf[] = { 0x00, 0xa5 };
>> +	int err;
>> +
>> +	if (ucontrol->value.enumerated.item[0]>  0) {
>> +		err = snd_usb_ctl_msg(chip->dev,
>> +			usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
>> +			USB_RECIP_INTERFACE | USB_TYPE_CLASS |
>> +			USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
>> +			(0x3c<<  8), buf, 2);
>> +		if (err<  0)
>> +			return err;
>> +
>> +		usb_audio_info(elem->mixer->chip,
>> +			 "scarlett: saved settings to hardware.\n");
> Using *_info() here is rather annoying, it may spew too many random
> messages.
OTOH, saving the settings to hardware should be a rare operation (the 
non-volatile memory might also be  designed to withstand only a limited 
amount of read-write cycles).

@Chris:
The original get_ctl_value (after cleanup: snd_usb_get_cur_mix_value) 
contained this:
>         // quirk: write 2bytes, but read 1byte
>         if ( (elem->index == 0x01)||  //  input impedance and input 
> pad switch
>              ((elem->index == 0x0a)&&(elem->wValue < 0x0200))|| // bus 
> mutes
>              (elem->index == 0x32)||(elem->index == 0x33) ) { // mux
>                 val_len = 1;
>         }
Is it removed(AFAICT) intentionally in your patches?
The windows mixer software did read-back some values (but only after 
they had been written once, IIRC), and had this asymmetry that certain 
registers used 2-byte writes but 1-byte reads...
I'm quite unsure about just reading 2 bytes, i.e. whether the device 
would actually return the correct values.
OTOH, we should only hit the cache because of the zero-initialization(?).

   Tobias
Tobias Hoffmann Nov. 4, 2014, 1:29 p.m. UTC | #3
On 04/11/14 14:16, Tobias Hoffmann wrote:
> But maybe (as Clemens suggested) the strings *for .opt_master and 
> .opt_matrix* should just be created dynamically in 
> scarlett_ctl_enum_info, instead of using snd_ctl_enum_info.
> I want to point out that scarlett_device_info already contains the 
> necessary information to do this:
>
>     .matrix_out = 6,
> ...
>     .pcm_start = 0,  // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12)
>     .analog_start = 12, // analog(1) .. analog(4)
>     .spdif_start = 16, // spdif(1) .. spdif(2)
>     .adat_start = 18,  // adat(1) .. adat(0)  [i.e. no adat]
>     .mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
>
>
>
> and Off(-1). Only elem->private_data has to be of type 
> scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, 
> ...), but of type scarlett_device_info for the master and mixer enums...
>

Hmm, as the same options are required for a lot of master and mixer 
enums, the strings should probably be generated only once (and a dynamic 
scarlett_mixer_elem_enum_info would require no modifications of 
scarlett_ctl_enum_info).

   Tobias
Takashi Iwai Nov. 4, 2014, 2 p.m. UTC | #4
At Tue, 04 Nov 2014 14:16:13 +0100,
Tobias Hoffmann wrote:
> 
> > +#define TXT_OFF	"Off"
> > +#define TXT_PCM(num) "PCM " #num
> > +#define TXT_ANALOG(num) "Analog " #num
> > +#define TXT_SPDIF(num) "SPDIF " #num
> > +#define TXT_ADAT(num) "ADAT " #num
> > +#define TXT_MIX(letter) "MIX " #letter
> 
> My reasoning for using:
>    static const char txtOff[] = "Off",
>            txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
>            txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",  [...]
> instead of repeating the strings for each device (or using macros that 
> expand to the same strings) was that the final .o / .ko would contain 
> the string data only once.

The compiler should be clever enough.

> But maybe (as Clemens suggested) the strings *for .opt_master and 
> .opt_matrix* should just be created dynamically in 
> scarlett_ctl_enum_info, instead of using snd_ctl_enum_info.
> I want to point out that scarlett_device_info already contains the 
> necessary information to do this:
> 
> 	.matrix_out = 6,
> ...
> 	.pcm_start = 0,  // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12)
> 	.analog_start = 12, // analog(1) .. analog(4)
> 	.spdif_start = 16, // spdif(1) .. spdif(2)
> 	.adat_start = 18,  // adat(1) .. adat(0)  [i.e. no adat]
> 	.mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
> 
> 
> 
> and Off(-1). Only elem->private_data has to be of type 
> scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), 
> but of type scarlett_device_info for the master and mixer enums...

Yeah, that would be also cleaner.


> Some more comments inline:
> 
> On 04/11/14 11:18, Takashi Iwai wrote:
> > The new patch looks almost good. A remaining concern is:
> >> +static const struct scarlett_mixer_elem_enum_info opt_save = {
> >> +	.start = 0,
> >> +	.len = 2,
> >> +	.names = (const char * const[]){
> >> +		"---", "Save"
> > This isn't quite intuitive.  And I think this is an abuse of ctl
> > enum (see below).
> It's a hack to expose the "Save to hardware" functionality without 
> requiring a special mixer application. Robin's original patch already 
> contained this (ab)use.

Heh, the history can't be an excuse to continue the abuse :)

> > Also...
> >
> >> +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
> >> +				 struct snd_ctl_elem_value *ucontrol)
> >> +{
> >> +	struct usb_mixer_elem_info *elem = kctl->private_data;
> >> +	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
> >> +	int err, val;
> >> +
> >> +	err = snd_usb_get_cur_mix_value(elem, 0, 0,&val);
> >> +	if (err<  0)
> >> +		return err;
> >> +
> >> +	if ((opt->start == -1)&&  (val>  opt->len)) /*>= 0x20 */
> >> +		val = 0;
> >> +	else
> >> +		val = clamp(val - opt->start, 0, opt->len-1);
> > Is the if condition above really correct?  It's not obvious to me what
> > this really checks.
> 
> (opt->start == -1) is used in enums that control the routing, to 
> represent the "Off"-value.
> But the device uses number_of_master_enum_values+1 (even for the matrix 
> enum) as "Off".
> 
> That would make "Off" the last enum value in Mixer applications, which 
> is undesired (and, for the matrix enums, this still results in a gap 
> between last_matrix_enum_index and Off=last_mixer_enum_index).
> 
> scarlett_ctl_enum_put did contain the inverse logic (before cleanup):
> 
> > #if 0 // TODO?
> >         if (val == -1) {
> >                 val = elem->enum->len + 1; /* only true for master, 
> > not for mixer [also master must be used] */
> >                 // ... or? > 0x20,  18i8: 0x22
> >         } else
> > #endif
> >         val = val + elem->opt->start;
> 
> It is commented out, because the device also recognized val = -1 + -1; 
> as "Off", without the need to somehow expose the device-specific 
> enum->len of .opt_master to all the .opt_matrix controls.
> It's a bit dirty, but it works...

Oh, no this is too tricky.  If you want to keep this coding, you have
to put this whole comment in the code.  Otherwise no reader
understands the code correctly.

Please, don't play with too much tricks.  This is no hot path. The
code size and speed don't matter so much.  The readability has to be
cared at most, instead.


> > Now back to "save to hw" ctl:
> >
> >> +static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
> >> +				 struct snd_ctl_elem_value *ucontrol)
> >> +{
> >> +	ucontrol->value.enumerated.item[0] = 0;
> >> +	return 0;
> >> +}
> > So, here is the problem.  You want to use this ctl to trigger
> > something.  This is no correct behavior for an enum ctl.
> > If the put callback succeeds, the value should be stored.
> > (Of course, then it won't work as you expected.)
> >
> > What actually this control does?  Why it can't be done always
> > (transparently)?
> 
> What it does is it store the current settings in non-volatile memory for 
> standalone operation (i.e. USB not connected). It's the device 
> configuration after power-on.
> 
> Loading this driver then reinitalizes the device to basically zero 
> values (userspace "acontrol restore" will finally set the device to what 
> the user expects).
> The zeroing is unfortunately necessary, because the device does not 
> support reading back certain mixer values after power-on (only garbage 
> is returned). After initialization, only cval->cache_val is ever 
> returned by snd_usb_get_cur_mix_value.
> 
> 
> >> +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
> >> +				 struct snd_ctl_elem_value *ucontrol)
> >> +{
> >> +	struct usb_mixer_elem_info *elem = kctl->private_data;
> >> +	struct snd_usb_audio *chip = elem->mixer->chip;
> >> +	char buf[] = { 0x00, 0xa5 };
> >> +	int err;
> >> +
> >> +	if (ucontrol->value.enumerated.item[0]>  0) {
> >> +		err = snd_usb_ctl_msg(chip->dev,
> >> +			usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
> >> +			USB_RECIP_INTERFACE | USB_TYPE_CLASS |
> >> +			USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
> >> +			(0x3c<<  8), buf, 2);
> >> +		if (err<  0)
> >> +			return err;
> >> +
> >> +		usb_audio_info(elem->mixer->chip,
> >> +			 "scarlett: saved settings to hardware.\n");
> > Using *_info() here is rather annoying, it may spew too many random
> > messages.
> OTOH, saving the settings to hardware should be a rare operation (the 
> non-volatile memory might also be  designed to withstand only a limited 
> amount of read-write cycles).

If so, implementing it as a mixer ctl is a wrong design.  I'd suggest
rather to drop whole this stuff at first.  If really inevitably
needed, you can implement it in a different way.


thanks,

Takashi
Chris J Arges Nov. 4, 2014, 7:45 p.m. UTC | #5
On Tue, Nov 04, 2014 at 02:29:02PM +0100, Tobias Hoffmann wrote:
> On 04/11/14 14:16, Tobias Hoffmann wrote:
> >But maybe (as Clemens suggested) the strings *for .opt_master and
> >.opt_matrix* should just be created dynamically in
> >scarlett_ctl_enum_info, instead of using snd_ctl_enum_info.
> >I want to point out that scarlett_device_info already contains the
> >necessary information to do this:
> >
> >    .matrix_out = 6,
> >...
> >    .pcm_start = 0,  // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12)
> >    .analog_start = 12, // analog(1) .. analog(4)
> >    .spdif_start = 16, // spdif(1) .. spdif(2)
> >    .adat_start = 18,  // adat(1) .. adat(0)  [i.e. no adat]
> >    .mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
> >
> >
> >
> >and Off(-1). Only elem->private_data has to be of type
> >scarlett_mixer_elem_enum_info for the usual enums (impedance, pad,
> >...), but of type scarlett_device_info for the master and mixer
> >enums...
> >
> 
> Hmm, as the same options are required for a lot of master and mixer
> enums, the strings should probably be generated only once (and a
> dynamic scarlett_mixer_elem_enum_info would require no modifications
> of scarlett_ctl_enum_info).
> 
>   Tobias

Yes this would be the plan dynamially generate them once and reuse them. I'll work on implementing this.
--chris
Chris J Arges Nov. 4, 2014, 7:51 p.m. UTC | #6
On Tue, Nov 04, 2014 at 02:16:13PM +0100, Tobias Hoffmann wrote:
<snip>
> @Chris:
> The original get_ctl_value (after cleanup:
> snd_usb_get_cur_mix_value) contained this:
> >        // quirk: write 2bytes, but read 1byte
> >        if ( (elem->index == 0x01)||  //  input impedance and
> >input pad switch
> >             ((elem->index == 0x0a)&&(elem->wValue < 0x0200))|| //
> >bus mutes
> >             (elem->index == 0x32)||(elem->index == 0x33) ) { // mux
> >                val_len = 1;
> >        }
> Is it removed(AFAICT) intentionally in your patches?
> The windows mixer software did read-back some values (but only after
> they had been written once, IIRC), and had this asymmetry that
> certain registers used 2-byte writes but 1-byte reads...
> I'm quite unsure about just reading 2 bytes, i.e. whether the device
> would actually return the correct values.
> OTOH, we should only hit the cache because of the zero-initialization(?).
> 
>   Tobias
>

Tobias,
I've notcied that the controls work just fine without this hack (at least on my 18i8);
however I can look more closely into this to see what the extra byte that's being read actually is. 
--chris
Chris J Arges Nov. 4, 2014, 7:56 p.m. UTC | #7
On Tue, Nov 04, 2014 at 03:00:46PM +0100, Takashi Iwai wrote:
> At Tue, 04 Nov 2014 14:16:13 +0100,
> Tobias Hoffmann wrote:
> > 
> > > +#define TXT_OFF	"Off"
> > > +#define TXT_PCM(num) "PCM " #num
> > > +#define TXT_ANALOG(num) "Analog " #num
> > > +#define TXT_SPDIF(num) "SPDIF " #num
> > > +#define TXT_ADAT(num) "ADAT " #num
> > > +#define TXT_MIX(letter) "MIX " #letter
> > 
> > My reasoning for using:
> >    static const char txtOff[] = "Off",
> >            txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
> >            txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",  [...]
> > instead of repeating the strings for each device (or using macros that 
> > expand to the same strings) was that the final .o / .ko would contain 
> > the string data only once.
> 
> The compiler should be clever enough.
> 
> > But maybe (as Clemens suggested) the strings *for .opt_master and 
> > .opt_matrix* should just be created dynamically in 
> > scarlett_ctl_enum_info, instead of using snd_ctl_enum_info.
> > I want to point out that scarlett_device_info already contains the 
> > necessary information to do this:
> > 
> > 	.matrix_out = 6,
> > ...
> > 	.pcm_start = 0,  // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12)
> > 	.analog_start = 12, // analog(1) .. analog(4)
> > 	.spdif_start = 16, // spdif(1) .. spdif(2)
> > 	.adat_start = 18,  // adat(1) .. adat(0)  [i.e. no adat]
> > 	.mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
> > 
> > 
> > 
> > and Off(-1). Only elem->private_data has to be of type 
> > scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), 
> > but of type scarlett_device_info for the master and mixer enums...
> 
> Yeah, that would be also cleaner.
> 
> 
> > Some more comments inline:
> > 
> > On 04/11/14 11:18, Takashi Iwai wrote:
> > > The new patch looks almost good. A remaining concern is:
> > >> +static const struct scarlett_mixer_elem_enum_info opt_save = {
> > >> +	.start = 0,
> > >> +	.len = 2,
> > >> +	.names = (const char * const[]){
> > >> +		"---", "Save"
> > > This isn't quite intuitive.  And I think this is an abuse of ctl
> > > enum (see below).
> > It's a hack to expose the "Save to hardware" functionality without 
> > requiring a special mixer application. Robin's original patch already 
> > contained this (ab)use.
> 
> Heh, the history can't be an excuse to continue the abuse :)
> 
> > > Also...
> > >
> > >> +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
> > >> +				 struct snd_ctl_elem_value *ucontrol)
> > >> +{
> > >> +	struct usb_mixer_elem_info *elem = kctl->private_data;
> > >> +	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
> > >> +	int err, val;
> > >> +
> > >> +	err = snd_usb_get_cur_mix_value(elem, 0, 0,&val);
> > >> +	if (err<  0)
> > >> +		return err;
> > >> +
> > >> +	if ((opt->start == -1)&&  (val>  opt->len)) /*>= 0x20 */
> > >> +		val = 0;
> > >> +	else
> > >> +		val = clamp(val - opt->start, 0, opt->len-1);
> > > Is the if condition above really correct?  It's not obvious to me what
> > > this really checks.
> > 
> > (opt->start == -1) is used in enums that control the routing, to 
> > represent the "Off"-value.
> > But the device uses number_of_master_enum_values+1 (even for the matrix 
> > enum) as "Off".
> > 
> > That would make "Off" the last enum value in Mixer applications, which 
> > is undesired (and, for the matrix enums, this still results in a gap 
> > between last_matrix_enum_index and Off=last_mixer_enum_index).
> > 
> > scarlett_ctl_enum_put did contain the inverse logic (before cleanup):
> > 
> > > #if 0 // TODO?
> > >         if (val == -1) {
> > >                 val = elem->enum->len + 1; /* only true for master, 
> > > not for mixer [also master must be used] */
> > >                 // ... or? > 0x20,  18i8: 0x22
> > >         } else
> > > #endif
> > >         val = val + elem->opt->start;
> > 
> > It is commented out, because the device also recognized val = -1 + -1; 
> > as "Off", without the need to somehow expose the device-specific 
> > enum->len of .opt_master to all the .opt_matrix controls.
> > It's a bit dirty, but it works...
> 
> Oh, no this is too tricky.  If you want to keep this coding, you have
> to put this whole comment in the code.  Otherwise no reader
> understands the code correctly.
> 
> Please, don't play with too much tricks.  This is no hot path. The
> code size and speed don't matter so much.  The readability has to be
> cared at most, instead.
> 
> 
> > > Now back to "save to hw" ctl:
> > >
> > >> +static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
> > >> +				 struct snd_ctl_elem_value *ucontrol)
> > >> +{
> > >> +	ucontrol->value.enumerated.item[0] = 0;
> > >> +	return 0;
> > >> +}
> > > So, here is the problem.  You want to use this ctl to trigger
> > > something.  This is no correct behavior for an enum ctl.
> > > If the put callback succeeds, the value should be stored.
> > > (Of course, then it won't work as you expected.)
> > >
> > > What actually this control does?  Why it can't be done always
> > > (transparently)?
> > 
> > What it does is it store the current settings in non-volatile memory for 
> > standalone operation (i.e. USB not connected). It's the device 
> > configuration after power-on.
> > 
> > Loading this driver then reinitalizes the device to basically zero 
> > values (userspace "acontrol restore" will finally set the device to what 
> > the user expects).
> > The zeroing is unfortunately necessary, because the device does not 
> > support reading back certain mixer values after power-on (only garbage 
> > is returned). After initialization, only cval->cache_val is ever 
> > returned by snd_usb_get_cur_mix_value.
> > 
> > 
> > >> +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
> > >> +				 struct snd_ctl_elem_value *ucontrol)
> > >> +{
> > >> +	struct usb_mixer_elem_info *elem = kctl->private_data;
> > >> +	struct snd_usb_audio *chip = elem->mixer->chip;
> > >> +	char buf[] = { 0x00, 0xa5 };
> > >> +	int err;
> > >> +
> > >> +	if (ucontrol->value.enumerated.item[0]>  0) {
> > >> +		err = snd_usb_ctl_msg(chip->dev,
> > >> +			usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
> > >> +			USB_RECIP_INTERFACE | USB_TYPE_CLASS |
> > >> +			USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
> > >> +			(0x3c<<  8), buf, 2);
> > >> +		if (err<  0)
> > >> +			return err;
> > >> +
> > >> +		usb_audio_info(elem->mixer->chip,
> > >> +			 "scarlett: saved settings to hardware.\n");
> > > Using *_info() here is rather annoying, it may spew too many random
> > > messages.
> > OTOH, saving the settings to hardware should be a rare operation (the 
> > non-volatile memory might also be  designed to withstand only a limited 
> > amount of read-write cycles).
> 
> If so, implementing it as a mixer ctl is a wrong design.  I'd suggest
> rather to drop whole this stuff at first.  If really inevitably
> needed, you can implement it in a different way.
> 
> 
> thanks,
> 
> Takashi

Takashi,
I'll drop it for the initial patchset. Clemens mentioned implementing it as a
mixer control that allows no access but a TLV_COMMAND. Perhaps that could be a
start and then writing support in any userspace applications to expose that 
'button' control correctly.

--chris
David Henningsson Nov. 4, 2014, 8:11 p.m. UTC | #8
On 2014-11-03 23:58, Chris J Arges wrote:
> This code contains the Scarlett mixer interface code that was originally
> written by Tobias Hoffman and Robin Gareus. Because the device doesn't
> properly implement UAC2 this code adds a mixer quirk for the device.

Thanks a lot for taking over - it surely has been done with better speed 
than I would have.

> + *   Code cleanup:
> + *   David Henningsson <david.henningsson at canonical.com>

Thanks for the credit, not sure I deserve it though, all I did was to 
squash the patches and fix the checkpatch errors :-)

> +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
> +				 struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct usb_mixer_elem_info *elem = kctl->private_data;
> +	struct snd_usb_audio *chip = elem->mixer->chip;
> +	char buf[] = { 0x00, 0xa5 };
> +	int err;
> +
> +	if (ucontrol->value.enumerated.item[0] > 0) {
> +		err = snd_usb_ctl_msg(chip->dev,
> +			usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
> +			USB_RECIP_INTERFACE | USB_TYPE_CLASS |
> +			USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
> +			(0x3c << 8), buf, 2);
> +		if (err < 0)
> +			return err;
> +
> +		usb_audio_info(elem->mixer->chip,
> +			 "scarlett: saved settings to hardware.\n");
> +	}
> +	return 0;
> +}

To elaborate on Takashi's concern about this control, imagine e g what 
alsactl restore/store being run on startup/shutdown. On shutdown, 
alsactl store would read "0", and on startup, alsactl restore would 
write "0", with the result that the hw would not store the values set.

Would it be possible to have an autosave instead? E g, whenever a value 
is changed, it saves that value to the hardware. That's how other 
hardware works and what userspace expects.
If you want to avoid a lot of autosaves, e g if the autosave takes a lot 
of time before the hw responds, maybe you could do the autosave in a 10 
ms delayed_work, or similar.
Chris J Arges Nov. 4, 2014, 8:18 p.m. UTC | #9
On 11/04/2014 02:11 PM, David Henningsson wrote:
> 
> 
> On 2014-11-03 23:58, Chris J Arges wrote:
>> This code contains the Scarlett mixer interface code that was originally
>> written by Tobias Hoffman and Robin Gareus. Because the device doesn't
>> properly implement UAC2 this code adds a mixer quirk for the device.
> 
> Thanks a lot for taking over - it surely has been done with better speed
> than I would have.
> 
>> + *   Code cleanup:
>> + *   David Henningsson <david.henningsson at canonical.com>
> 
> Thanks for the credit, not sure I deserve it though, all I did was to
> squash the patches and fix the checkpatch errors :-)
> 
: )

>> +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
>> +                 struct snd_ctl_elem_value *ucontrol)
>> +{
>> +    struct usb_mixer_elem_info *elem = kctl->private_data;
>> +    struct snd_usb_audio *chip = elem->mixer->chip;
>> +    char buf[] = { 0x00, 0xa5 };
>> +    int err;
>> +
>> +    if (ucontrol->value.enumerated.item[0] > 0) {
>> +        err = snd_usb_ctl_msg(chip->dev,
>> +            usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
>> +            USB_RECIP_INTERFACE | USB_TYPE_CLASS |
>> +            USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
>> +            (0x3c << 8), buf, 2);
>> +        if (err < 0)
>> +            return err;
>> +
>> +        usb_audio_info(elem->mixer->chip,
>> +             "scarlett: saved settings to hardware.\n");
>> +    }
>> +    return 0;
>> +}
> 
> To elaborate on Takashi's concern about this control, imagine e g what
> alsactl restore/store being run on startup/shutdown. On shutdown,
> alsactl store would read "0", and on startup, alsactl restore would
> write "0", with the result that the hw would not store the values set.
> 
> Would it be possible to have an autosave instead? E g, whenever a value
> is changed, it saves that value to the hardware. That's how other
> hardware works and what userspace expects.
> If you want to avoid a lot of autosaves, e g if the autosave takes a lot
> of time before the hw responds, maybe you could do the autosave in a 10
> ms delayed_work, or similar.
> 

David,
Hey, as Tobias mentioned this is a HW saving (to the mixer's NVRAM)
function used for using the mixer disconnected from a computer. We
wouldn't want to continually write the NVRAM on every control update as
I'm unsure of how many write-cycles the device is capable of.

So without it you can still plug the mixer into the computer and alsa
will restore any saved settings automatically. I'm planning on dropping
the HW Save in v5 until we can figure out a proper control mechanism for
this.

--chris
David Henningsson Nov. 5, 2014, 9:55 a.m. UTC | #10
On 2014-11-04 21:18, Chris J Arges wrote:
>
>
> On 11/04/2014 02:11 PM, David Henningsson wrote:
>>
>>
>> On 2014-11-03 23:58, Chris J Arges wrote:
>>> This code contains the Scarlett mixer interface code that was originally
>>> written by Tobias Hoffman and Robin Gareus. Because the device doesn't
>>> properly implement UAC2 this code adds a mixer quirk for the device.
>>
>> Thanks a lot for taking over - it surely has been done with better speed
>> than I would have.
>>
>>> + *   Code cleanup:
>>> + *   David Henningsson <david.henningsson at canonical.com>
>>
>> Thanks for the credit, not sure I deserve it though, all I did was to
>> squash the patches and fix the checkpatch errors :-)
>>
> : )
>
>>> +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
>>> +                 struct snd_ctl_elem_value *ucontrol)
>>> +{
>>> +    struct usb_mixer_elem_info *elem = kctl->private_data;
>>> +    struct snd_usb_audio *chip = elem->mixer->chip;
>>> +    char buf[] = { 0x00, 0xa5 };
>>> +    int err;
>>> +
>>> +    if (ucontrol->value.enumerated.item[0] > 0) {
>>> +        err = snd_usb_ctl_msg(chip->dev,
>>> +            usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
>>> +            USB_RECIP_INTERFACE | USB_TYPE_CLASS |
>>> +            USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
>>> +            (0x3c << 8), buf, 2);
>>> +        if (err < 0)
>>> +            return err;
>>> +
>>> +        usb_audio_info(elem->mixer->chip,
>>> +             "scarlett: saved settings to hardware.\n");
>>> +    }
>>> +    return 0;
>>> +}
>>
>> To elaborate on Takashi's concern about this control, imagine e g what
>> alsactl restore/store being run on startup/shutdown. On shutdown,
>> alsactl store would read "0", and on startup, alsactl restore would
>> write "0", with the result that the hw would not store the values set.
>>
>> Would it be possible to have an autosave instead? E g, whenever a value
>> is changed, it saves that value to the hardware. That's how other
>> hardware works and what userspace expects.
>> If you want to avoid a lot of autosaves, e g if the autosave takes a lot
>> of time before the hw responds, maybe you could do the autosave in a 10
>> ms delayed_work, or similar.
>>
>
> David,
> Hey, as Tobias mentioned this is a HW saving (to the mixer's NVRAM)
> function used for using the mixer disconnected from a computer. We
> wouldn't want to continually write the NVRAM on every control update as
> I'm unsure of how many write-cycles the device is capable of.
>
> So without it you can still plug the mixer into the computer and alsa
> will restore any saved settings automatically. I'm planning on dropping
> the HW Save in v5 until we can figure out a proper control mechanism for
> this.

Aha, sorry for not grasping that. Maybe "save to HW" could be labelled 
"save for offline use" or so. (Cool feature, btw. I didn't know that was 
possible.)

I think a sysfs node would be simplest for this case. Just like we do 
"echo 1 > /sys/class/sound/hwC0D0/reconfig" to tell the HDA driver to 
reconfigure itself, there could be a "save_mixer_for_offline_use" node 
for this feature.
Chris J Arges Nov. 6, 2014, 2:33 p.m. UTC | #11
This is v5 of the patchset to merge what Tobias Hoffman and Robin Gareus have
done to enable the Focusrite Scarlett mixers for use with ALSA.

[v3]

I have split the commits into hopefully a logical series. First the original
quirk is reverted for one model of a Scarlett device. Next an additional
structure is added to be able to more easily reuse usb_mixer_elem_info.
After this mixer functions that were useful to this code were made public.
Finally the last patch adds the necessary functions to make this mixer work.

[v4]

This version removes the per-mixer control creation functions and uses a
generic function based on structure data. Macros used for control addition
are removed and the plain function is used instead. Hardcoded text block is
removed and macros to define strings are used instead. Hardcoded control
initialization has been removed.

[v5]

In this version, HW saving functionality has been removed in this initial
patchset. Macros for function calls are removed for readability. Strings for
enums are created dynamically using the info structures. String lengths for
controls are now all SNDRV_CTL_ELEM_ID_NAME_MAXLEN in length.

Note: Resending the entire patchset because of mail filter issues.

Chris J Arges (4):
  Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett
  ALSA: usb-audio: Add private_data pointer to usb_mixer_elem_info
  ALSA: usb-audio: make set_*_mix_values functions public
  ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and
    18i20

 sound/usb/Makefile         |    1 +
 sound/usb/mixer.c          |   34 +-
 sound/usb/mixer.h          |    9 +
 sound/usb/mixer_quirks.c   |   18 +-
 sound/usb/mixer_scarlett.c | 1009 ++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/mixer_scarlett.h |    6 +
 sound/usb/quirks-table.h   |   51 ---
 7 files changed, 1052 insertions(+), 76 deletions(-)
 create mode 100644 sound/usb/mixer_scarlett.c
 create mode 100644 sound/usb/mixer_scarlett.h
diff mbox

Patch

diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 2b92f0d..bcee406 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -9,6 +9,7 @@  snd-usb-audio-objs := 	card.o \
 			helper.o \
 			mixer.o \
 			mixer_quirks.o \
+			mixer_scarlett.o \
 			pcm.o \
 			proc.o \
 			quirks.o \
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index c9665bf..d06d27a 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -41,6 +41,7 @@ 
 #include "usbaudio.h"
 #include "mixer.h"
 #include "mixer_quirks.h"
+#include "mixer_scarlett.h"
 #include "helper.h"
 
 extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl;
@@ -1664,6 +1665,14 @@  int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
 		/* detection is disabled in mixer_maps.c */
 		err = snd_create_std_mono_table(mixer, ebox44_table);
 		break;
+
+	case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */
+	case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */
+	case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */
+	case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */
+	case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */
+		err = snd_scarlett_controls_create(mixer);
+		break;
 	}
 
 	return err;
diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c
new file mode 100644
index 0000000..d62f8d4
--- /dev/null
+++ b/sound/usb/mixer_scarlett.c
@@ -0,0 +1,1070 @@ 
+/*
+ *   Scarlett Driver for ALSA
+ *
+ *   Copyright (c) 2013 by Tobias Hoffmann
+ *   Copyright (c) 2013 by Robin Gareus <robin at gareus.org>
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de>
+ *   Copyright (c) 2014 by Chris J Arges <chris.j.arges at canonical.com>
+ *
+ *   Many codes borrowed from audio.c by
+ *	    Alan Cox (alan at lxorguk.ukuu.org.uk)
+ *	    Thomas Sailer (sailer at ife.ee.ethz.ch)
+ *
+ *   Code cleanup:
+ *   David Henningsson <david.henningsson at canonical.com>
+ *
+ *   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.
+ *
+ */
+
+/*
+ * Rewritten and extended to support more models, e.g. Scarlett 18i8.
+ *
+ * Many features of Scarlett 18i6 do not lend themselves to be implemented
+ * as simple mixer-quirk -- or at least I don't see a way how to do that, yet.
+ * Hence the top parts of this file is a 1:1 copy of select static functions
+ * from mixer.c to implement the interface.
+ * Suggestions how to avoid this code duplication are very welcome.
+ *
+ * eventually this should either be integrated as quirk into mixer_quirks.c
+ * or become a standalone module.
+ *
+ * This source hardcodes the URBs for the Scarlett,
+ * Auto-detection via UAC2 is not feasible to properly discover the vast
+ * majority of features. It's related to both Linux/ALSA's UAC2 as well as
+ * Focusrite's implementation of it. Eventually quirks may be sufficient but
+ * right now it's a major headache to work arount these things.
+ *
+ * NB. Neither the OSX nor the win driver provided by Focusrite performs
+ * discovery, they seem to operate the same as this driver.
+ */
+
+/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface.
+ *
+ * The protocol was reverse engineered by looking at communication between
+ * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6
+ * (firmware v305) using wireshark and usbmon in January 2013.
+ * Extended in July 2013.
+ *
+ * this mixer gives complete access to all features of the device:
+ *  - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z)
+ *  - select clock source
+ *  - dynamic input to mixer-matrix assignment
+ *  - 18 x 6 mixer-matrix gain stages
+ *  - bus routing & volume control
+ *  - save setting to hardware
+ *  - automatic re-initialization on connect if device was power-cycled
+ *  - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route channels)
+ *  (changing the samplerate and buffersize is supported by the PCM interface)
+ *
+ *
+ * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR)
+ * wIndex
+ * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 +
+ *      channel, data=Line/Inst (2bytes)
+ *      pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes)
+ *      ?? wValue=0x0803/04, ?? (2bytes)
+ * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes)
+ *      Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes)
+ * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte)
+ * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes)
+ * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes)
+ * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes)
+ * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes)
+ * 0x3c Matrix Mixer gains, wValue=mixer-node  data=gain(2bytes)
+ *      ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff)
+ *
+ * USB reads: (i.e. actually issued by original software)
+ * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!)
+ * 0x29 wValue=0x0100 sample-rate(4bytes)
+ *      wValue=0x0200 ?? 1byte (only once)
+ * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ??
+ *
+ * USB reads with bRequest = 0x03 = UAC2_CS_MEM
+ * 0x3c wValue=0x0002 1byte: sync status (locked=1)
+ *      wValue=0x0000 18*2byte: peak meter (inputs)
+ *      wValue=0x0001 8(?)*2byte: peak meter (mix)
+ *      wValue=0x0003 6*2byte: peak meter (pcm/daw)
+ *
+ * USB write with bRequest = 0x03
+ * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5
+ *
+ *
+ * <ditaa>
+ *  /--------------\    18chn            6chn    /--------------\
+ *  | Hardware  in +--+-------\        /------+--+ ALSA PCM out |
+ *  \--------------/  |       |        |      |  \--------------/
+ *                    |       |        |      |
+ *                    |       v        v      |
+ *                    |   +---------------+   |
+ *                    |    \ Matrix  Mux /    |
+ *                    |     +-----+-----+     |
+ *                    |           |           |
+ *                    |           | 18chn     |
+ *                    |           v           |
+ *                    |     +-----------+     |
+ *                    |     | Mixer     |     |
+ *                    |     |    Matrix |     |
+ *                    |     |           |     |
+ *                    |     | 18x6 Gain |     |
+ *                    |     |   stages  |     |
+ *                    |     +-----+-----+     |
+ *                    |           |           |
+ *                    |           |           |
+ *                    | 18chn     | 6chn      | 6chn
+ *                    v           v           v
+ *                    =========================
+ *             +---------------+     +--—------------+
+ *              \ Output  Mux /       \ Capture Mux /
+ *               +-----+-----+         +-----+-----+
+ *                     |                     |
+ *                     | 6chn                |
+ *                     v                     |
+ *              +-------------+              |
+ *              | Master Gain |              |
+ *              +------+------+              |
+ *                     |                     |
+ *                     | 6chn                | 18chn
+ *                     | (3 stereo pairs)    |
+ *  /--------------\   |                     |   /--------------\
+ *  | Hardware out |<--/                     \-->| ALSA PCM  in |
+ *  \--------------/                             \--------------/
+ * </ditaa>
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+#include "power.h"
+
+#include "mixer_scarlett.h"
+
+/* some gui mixers can't handle negative ctl values */
+#define SND_SCARLETT_LEVEL_BIAS 128
+#define SND_SCARLETT_MATRIX_IN_MAX 18
+#define SND_SCARLETT_CONTROLS_MAX 10
+
+enum {
+	SCARLETT_OUTPUTS,
+	SCARLETT_SWITCH_IMPEDANCE,
+	SCARLETT_SWITCH_PAD,
+};
+
+struct scarlett_mixer_elem_enum_info {
+	int start;
+	int len;
+	const char * const *names;
+};
+
+struct scarlett_mixer_control {
+	unsigned char num;
+	unsigned char type;
+	const char *name;
+};
+
+struct scarlett_device_info {
+	int matrix_in;
+	int matrix_out;
+	int input_len;
+	int output_len;
+
+	int pcm_start;
+	int analog_start;
+	int spdif_start;
+	int adat_start;
+	int mix_start;
+
+	struct scarlett_mixer_elem_enum_info opt_master;
+	struct scarlett_mixer_elem_enum_info opt_matrix;
+
+	int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX];
+
+	int num_controls;	/* number of items in controls */
+	const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX];
+};
+
+/********************** Enum Strings *************************/
+
+#define TXT_OFF	"Off"
+#define TXT_PCM(num) "PCM " #num
+#define TXT_ANALOG(num) "Analog " #num
+#define TXT_SPDIF(num) "SPDIF " #num
+#define TXT_ADAT(num) "ADAT " #num
+#define TXT_MIX(letter) "MIX " #letter
+
+static const struct scarlett_mixer_elem_enum_info opt_pad = {
+	.start = 0,
+	.len = 2,
+	.names = (const char * const[]){
+		"0dB", "-10dB"
+	}
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_impedance = {
+	.start = 0,
+	.len = 2,
+	.names = (const char * const[]){
+		"Line", "Hi-Z"
+	}
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_clock = {
+	.start = 1,
+	.len = 3,
+	.names = (const char * const[]){
+		"Internal", "SPDIF", "ADAT"
+	}
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_sync = {
+	.start = 0,
+	.len = 2,
+	.names = (const char * const[]){
+		"No Lock", "Locked"
+	}
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_save = {
+	.start = 0,
+	.len = 2,
+	.names = (const char * const[]){
+		"---", "Save"
+	}
+};
+
+static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = elem->channels;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, err, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &val);
+		if (err < 0)
+			return err;
+
+		val = !val; /* invert mute logic for mixer */
+		ucontrol->value.integer.value[i] = val;
+	}
+
+	return 0;
+}
+
+static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, changed = 0;
+	int err, oval, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
+		if (err < 0)
+			return err;
+
+		val = ucontrol->value.integer.value[i];
+		val = !val;
+		if (oval != val) {
+			err = snd_usb_set_cur_mix_value(elem, i, i, val);
+			if (err < 0)
+				return err;
+
+			changed = 1;
+		}
+	}
+
+	return changed;
+}
+
+static int scarlett_ctl_info(struct snd_kcontrol *kctl,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = elem->channels;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = (int)kctl->private_value +
+		SND_SCARLETT_LEVEL_BIAS;
+	uinfo->value.integer.step = 1;
+	return 0;
+}
+
+static int scarlett_ctl_get(struct snd_kcontrol *kctl,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, err, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &val);
+		if (err < 0)
+			return err;
+
+		val = clamp(val / 256, -128, (int)kctl->private_value) +
+				    SND_SCARLETT_LEVEL_BIAS;
+		ucontrol->value.integer.value[i] = val;
+	}
+
+	return 0;
+}
+
+static int scarlett_ctl_put(struct snd_kcontrol *kctl,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, changed = 0;
+	int err, oval, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
+		if (err < 0)
+			return err;
+
+		val = ucontrol->value.integer.value[i] -
+			SND_SCARLETT_LEVEL_BIAS;
+		val = val * 256;
+		if (oval != val) {
+			err = snd_usb_set_cur_mix_value(elem, i, i, val);
+			if (err < 0)
+				return err;
+
+			changed = 1;
+		}
+	}
+
+	return changed;
+}
+
+static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+
+	return snd_ctl_enum_info(uinfo, elem->channels, opt->len, opt->names);
+}
+
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+	int err, val;
+
+	err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
+	if (err < 0)
+		return err;
+
+	if ((opt->start == -1) && (val > opt->len)) /* >= 0x20 */
+		val = 0;
+	else
+		val = clamp(val - opt->start, 0, opt->len-1);
+
+	ucontrol->value.enumerated.item[0] = val;
+
+	return 0;
+}
+
+static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+	int changed = 0;
+	int err, oval, val;
+
+	err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval);
+	if (err < 0)
+		return err;
+
+	val = ucontrol->value.integer.value[0];
+	val = val + opt->start;
+	if (oval != val) {
+		err = snd_usb_set_cur_mix_value(elem, 0, 0, val);
+		if (err < 0)
+			return err;
+
+		changed = 1;
+	}
+
+	return changed;
+}
+
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] = 0;
+	return 0;
+}
+
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct snd_usb_audio *chip = elem->mixer->chip;
+	char buf[] = { 0x00, 0xa5 };
+	int err;
+
+	if (ucontrol->value.enumerated.item[0] > 0) {
+		err = snd_usb_ctl_msg(chip->dev,
+			usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
+			USB_RECIP_INTERFACE | USB_TYPE_CLASS |
+			USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
+			(0x3c << 8), buf, 2);
+		if (err < 0)
+			return err;
+
+		usb_audio_info(elem->mixer->chip,
+			 "scarlett: saved settings to hardware.\n");
+	}
+	return 0;
+}
+
+static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct snd_usb_audio *chip = elem->mixer->chip;
+	unsigned char buf[2 * MAX_CHANNELS] = {0, };
+	int wValue = (elem->control << 8) | elem->idx_off;
+	int idx = snd_usb_ctrl_intf(chip) | (elem->id << 8);
+	int err;
+
+	err = snd_usb_ctl_msg(chip->dev,
+				usb_rcvctrlpipe(chip->dev, 0),
+				UAC2_CS_MEM,
+				USB_RECIP_INTERFACE | USB_TYPE_CLASS |
+				USB_DIR_IN, wValue, idx, buf, elem->channels);
+	if (err < 0)
+		return err;
+
+	ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1);
+	return 0;
+}
+
+static struct snd_kcontrol_new usb_scarlett_ctl_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "",
+	.info = scarlett_ctl_switch_info,
+	.get =  scarlett_ctl_switch_get,
+	.put =  scarlett_ctl_switch_put,
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0);
+
+static struct snd_kcontrol_new usb_scarlett_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.name = "",
+	.info = scarlett_ctl_info,
+	.get =  scarlett_ctl_get,
+	.put =  scarlett_ctl_put,
+	.private_value = 6,  /* max value */
+	.tlv = { .p = db_scale_scarlett_gain }
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_master = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.name = "",
+	.info = scarlett_ctl_info,
+	.get =  scarlett_ctl_get,
+	.put =  scarlett_ctl_put,
+	.private_value = 6,  /* max value */
+	.tlv = { .p = db_scale_scarlett_gain }
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_enum = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "",
+	.info = scarlett_ctl_enum_info,
+	.get =  scarlett_ctl_enum_get,
+	.put =  scarlett_ctl_enum_put,
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_sync = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.name = "",
+	.info = scarlett_ctl_enum_info,
+	.get =  scarlett_ctl_meter_get,
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_save = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "",
+	.info = scarlett_ctl_enum_info,
+	.get =  scarlett_ctl_save_get,
+	.put =  scarlett_ctl_save_put,
+};
+
+static int add_new_ctl(struct usb_mixer_interface *mixer,
+		       const struct snd_kcontrol_new *ncontrol,
+		       int index, int offset, int num,
+		       int val_type, int channels, const char *name,
+		       const struct scarlett_mixer_elem_enum_info *opt,
+		       struct usb_mixer_elem_info **elem_ret
+)
+{
+	struct snd_kcontrol *kctl;
+	struct usb_mixer_elem_info *elem;
+	int err;
+
+	elem = kzalloc(sizeof(*elem), GFP_KERNEL);
+	if (!elem)
+		return -ENOMEM;
+
+	elem->mixer = mixer;
+	elem->control = offset;
+	elem->idx_off = num;
+	elem->id = index;
+	elem->val_type = val_type;
+
+	elem->channels = channels;
+
+	/* add scarlett_mixer_elem_enum_info struct */
+	elem->private_data = (void *)opt;
+
+	kctl = snd_ctl_new1(ncontrol, elem);
+	if (!kctl) {
+		usb_audio_err(mixer->chip, "cannot malloc kcontrol\n");
+		kfree(elem);
+		return -ENOMEM;
+	}
+	kctl->private_free = snd_usb_mixer_elem_free;
+
+	snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name);
+
+	err = snd_ctl_add(mixer->chip->card, kctl);
+	if (err < 0)
+		return err;
+
+	if (elem_ret)
+		*elem_ret = elem;
+
+	return 0;
+}
+
+static int add_output_ctls(struct usb_mixer_interface *mixer,
+			   int index, const char *name,
+			   const struct scarlett_device_info *info)
+{
+	int err;
+	char mx[48];
+	struct usb_mixer_elem_info *elem;
+
+	/* Add mute switch */
+	snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01,
+			  2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem);
+	if (err < 0)
+		return err;
+
+	/* Add volume control and initialize to 0 */
+	snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02,
+			  2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem);
+	if (err < 0)
+		return err;
+
+	/* Add L channel source playback enumeration */
+	snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index,
+			  USB_MIXER_S16, 1, mx, &info->opt_master, &elem);
+	if (err < 0)
+		return err;
+
+	/* Add R channel source playback enumeration */
+	snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index+1,
+			  USB_MIXER_S16, 1, mx, &info->opt_master, &elem);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/********************** device-specific config *************************/
+
+static const char * const s6i6_names[] = {
+	TXT_OFF,
+	TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4),
+	TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8),
+	TXT_PCM(9), TXT_PCM(10), TXT_PCM(11), TXT_PCM(12),
+	TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4),
+	TXT_SPDIF(1), TXT_SPDIF(2),
+	TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D),
+	TXT_MIX(E), TXT_MIX(F), TXT_MIX(G), TXT_MIX(H),
+};
+
+/*  untested...  */
+static const struct scarlett_device_info s6i6_info = {
+	.matrix_in = 18,
+	.matrix_out = 8,
+	.input_len = 6,
+	.output_len = 6,
+
+	.pcm_start = 0,
+	.analog_start = 12,
+	.spdif_start = 16,
+	.adat_start = 18,
+	.mix_start = 18,
+
+	.opt_master = {
+		.start = -1,
+		.len = 27,
+		.names = s6i6_names
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 19,
+		.names = s6i6_names
+	},
+
+	.num_controls = 0,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		12, 13, 14, 15,                 /* Analog -> 1..4 */
+		16, 17,                          /* SPDIF -> 5,6 */
+		0, 1, 2, 3, 4, 5, 6, 7,     /* PCM[1..12] -> 7..18 */
+		8, 9, 10, 11
+	}
+};
+
+/* and 2 loop channels: Mix1, Mix2 */
+static const char * const s8i6_names[] = {
+	TXT_OFF,
+	TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4),
+	TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8),
+	TXT_PCM(9), TXT_PCM(10), TXT_PCM(11), TXT_PCM(12),
+	TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4),
+	TXT_SPDIF(1), TXT_SPDIF(2),
+	TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D),
+	TXT_MIX(E), TXT_MIX(F),
+};
+
+/*  untested...  */
+static const struct scarlett_device_info s8i6_info = {
+	.matrix_in = 18,
+	.matrix_out = 6,
+	.input_len = 8,
+	.output_len = 6,
+
+	.pcm_start = 0,
+	.analog_start = 12,
+	.spdif_start = 16,
+	.adat_start = 18,
+	.mix_start = 18,
+
+	.opt_master = {
+		.start = -1,
+		.len = 25,
+		.names = s8i6_names
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 19,
+		.names = s8i6_names
+	},
+
+	.num_controls = 7,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		12, 13, 14, 15,                 /* Analog -> 1..4 */
+		16, 17,                          /* SPDIF -> 5,6 */
+		0, 1, 2, 3, 4, 5, 6, 7,     /* PCM[1..12] -> 7..18 */
+		8, 9, 10, 11
+	}
+};
+
+static const char * const s18i6_names[] = {
+	TXT_OFF,
+	TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4),
+	TXT_PCM(5), TXT_PCM(6),
+	TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4),
+	TXT_ANALOG(5), TXT_ANALOG(6), TXT_ANALOG(7), TXT_ANALOG(8),
+	TXT_SPDIF(1), TXT_SPDIF(2),
+	TXT_ADAT(1), TXT_ADAT(2), TXT_ADAT(3), TXT_ADAT(4),
+	TXT_ADAT(5), TXT_ADAT(6), TXT_ADAT(7), TXT_ADAT(8),
+	TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D),
+	TXT_MIX(E), TXT_MIX(F),
+};
+
+static const struct scarlett_device_info s18i6_info = {
+	.matrix_in = 18,
+	.matrix_out = 6,
+	.input_len = 18,
+	.output_len = 6,
+
+	.pcm_start = 0,
+	.analog_start = 6,
+	.spdif_start = 14,
+	.adat_start = 16,
+	.mix_start = 24,
+
+	.opt_master = {
+		.start = -1,
+		.len = 31,
+		.names = s18i6_names
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 25,
+		.names = s18i6_names
+	},
+
+	.num_controls = 5,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		 6,  7,  8,  9, 10, 11, 12, 13, /* Analog -> 1..8 */
+		16, 17, 18, 19, 20, 21,     /* ADAT[1..6] -> 9..14 */
+		14, 15,                          /* SPDIF -> 15,16 */
+		0, 1                          /* PCM[1,2] -> 17,18 */
+	}
+};
+
+static const char * const s18i8_names[] = {
+	TXT_OFF,
+	TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4),
+	TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8),
+	TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4),
+	TXT_ANALOG(5), TXT_ANALOG(6), TXT_ANALOG(7), TXT_ANALOG(8),
+	TXT_SPDIF(1), TXT_SPDIF(2),
+	TXT_ADAT(1), TXT_ADAT(2), TXT_ADAT(3), TXT_ADAT(4),
+	TXT_ADAT(5), TXT_ADAT(6), TXT_ADAT(7), TXT_ADAT(8),
+	TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D),
+	TXT_MIX(E), TXT_MIX(F), TXT_MIX(G), TXT_MIX(H),
+};
+
+static const struct scarlett_device_info s18i8_info = {
+	.matrix_in = 18,
+	.matrix_out = 8,
+	.input_len = 18,
+	.output_len = 8,
+
+	.pcm_start = 0,
+	.analog_start = 8,
+	.spdif_start = 16,
+	.adat_start = 18,
+	.mix_start = 26,
+
+	.opt_master = {
+		.start = -1,
+		.len = 35,
+		.names = s18i8_names
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 27,
+		.names = s18i8_names
+	},
+
+	.num_controls = 10,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" },
+		{ .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		 8,  9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */
+		18, 19, 20, 21, 22, 23,     /* ADAT[1..6] -> 9..14 */
+		16, 17,                          /* SPDIF -> 15,16 */
+		0, 1                          /* PCM[1,2] -> 17,18 */
+	}
+};
+
+static const char * const s18i20_names[] = {
+	TXT_OFF,
+	TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4),
+	TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8),
+	TXT_PCM(9), TXT_PCM(10), TXT_PCM(11), TXT_PCM(12),
+	TXT_PCM(13), TXT_PCM(14), TXT_PCM(15), TXT_PCM(16),
+	TXT_PCM(17), TXT_PCM(18), TXT_PCM(19), TXT_PCM(20),
+	TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4),
+	TXT_ANALOG(5), TXT_ANALOG(6), TXT_ANALOG(7), TXT_ANALOG(8),
+	TXT_SPDIF(1), TXT_SPDIF(2),
+	TXT_ADAT(1), TXT_ADAT(2), TXT_ADAT(3), TXT_ADAT(4),
+	TXT_ADAT(5), TXT_ADAT(6), TXT_ADAT(7), TXT_ADAT(8),
+	TXT_MIX('A'), TXT_MIX('B'), TXT_MIX('C'), TXT_MIX('D'),
+	TXT_MIX('E'), TXT_MIX('F'), TXT_MIX('G'), TXT_MIX('H'),
+};
+
+static const struct scarlett_device_info s18i20_info = {
+	.matrix_in = 18,
+	.matrix_out = 8,
+	.input_len = 18,
+	.output_len = 20,
+
+	.pcm_start = 0,
+	.analog_start = 20,
+	.spdif_start = 28,
+	.adat_start = 30,
+	.mix_start = 38,
+
+	.opt_master = {
+		.start = -1,
+		.len = 47,
+		.names = s18i20_names
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 39,
+		.names = s18i20_names
+	},
+
+	.num_controls = 10,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" },
+		{ .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" },
+		{ .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" },
+		{ .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" },
+		{ .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" },
+		{ .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" },
+		{ .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" },
+		/*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/
+	},
+
+	.matrix_mux_init = {
+		20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */
+		30, 31, 32, 33, 34, 35,     /* ADAT[1..6] -> 9..14 */
+		28, 29,                          /* SPDIF -> 15,16 */
+		0, 1                          /* PCM[1,2] -> 17,18 */
+	}
+};
+
+
+static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer,
+	const struct scarlett_device_info *info)
+{
+	int i = 0;
+	int err = 0;
+	char mx[32];
+	const struct scarlett_mixer_control *ctl;
+	struct usb_mixer_elem_info *elem;
+
+	/* create master switch and playback volume */
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, 0,
+			  USB_MIXER_S16, 1, "Master Playback Switch", NULL,
+			  &elem);
+	if (err < 0)
+		return err;
+
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, 0,
+			  USB_MIXER_S16, 1, "Master Playback Volume", NULL,
+			  &elem);
+	if (err < 0)
+		return err;
+
+	/* iterate through controls in info struct and create each one */
+	for (i = 0; i < info->num_controls; i++) {
+		ctl = &info->controls[i];
+
+		switch (ctl->type) {
+		case SCARLETT_OUTPUTS:
+			err = add_output_ctls(mixer, ctl->num, ctl->name, info);
+			if (err < 0)
+				return err;
+			break;
+		case SCARLETT_SWITCH_IMPEDANCE:
+			sprintf(mx, "Input %d Impedance Switch",
+				ctl->num);
+			err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01,
+					  0x09, ctl->num, USB_MIXER_S16, 1, mx,
+					  &opt_impedance, &elem);
+			if (err < 0)
+				return err;
+			break;
+		case SCARLETT_SWITCH_PAD:
+			sprintf(mx, "Input %d Pad Switch", ctl->num);
+			err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01,
+					  0x0b, ctl->num, USB_MIXER_S16, 1, mx,
+					  &opt_pad, &elem);
+			if (err < 0)
+				return err;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Create and initialize a mixer for the Focusrite(R) Scarlett
+ */
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer)
+{
+	int err, i, o;
+	char mx[32];
+	const struct scarlett_device_info *info;
+	struct usb_mixer_elem_info *elem;
+	static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
+
+	switch (mixer->chip->usb_id) {
+	case USB_ID(0x1235, 0x8012):
+		info = &s6i6_info;
+		break;
+	case USB_ID(0x1235, 0x8002):
+		info = &s8i6_info;
+		break;
+	case USB_ID(0x1235, 0x8004):
+		info = &s18i6_info;
+		break;
+	case USB_ID(0x1235, 0x8014):
+		info = &s18i8_info;
+		break;
+	case USB_ID(0x1235, 0x800c):
+		info = &s18i20_info;
+		break;
+	default: /* device not (yet) supported */
+		return -EINVAL;
+	}
+
+	/* generic function to create controls */
+	err = scarlett_controls_create_generic(mixer, info);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < info->matrix_in; i++) {
+		snprintf(mx, 32, "Matrix %02d Input Playback Route", i+1);
+		err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x32, 0x06, i,
+				  USB_MIXER_S16, 1, mx, &info->opt_matrix,
+				  &elem);
+		if (err < 0)
+			return err;
+
+		for (o = 0; o < info->matrix_out; o++) {
+			sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1,
+				o+'A');
+			err = add_new_ctl(mixer, &usb_scarlett_ctl, 0x3c, 0x00,
+					  (i << 3) + (o & 0x07), USB_MIXER_S16,
+					  1, mx, NULL, &elem);
+			if (err < 0)
+				return err;
+
+		}
+	}
+
+	for (i = 0; i < info->input_len; i++) {
+		snprintf(mx, 32, "Input Source %02d Capture Route", i+1);
+		err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x34, 0x00, i,
+				  USB_MIXER_S16, 1, mx, &info->opt_master,
+				  &elem);
+		if (err < 0)
+			return err;
+	}
+
+	/* val_len == 1 needed here */
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0,
+			  USB_MIXER_U8, 1, "Sample Clock Source",
+			  &opt_clock, &elem);
+	if (err < 0)
+		return err;
+
+	/* val_len == 1 and UAC2_CS_MEM */
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2,
+			  USB_MIXER_U8, 1, "Sample Clock Sync Status",
+			  &opt_sync, &elem);
+	if (err < 0)
+		return err;
+
+	/* val_len == 1 and UAC2_CS_MEM */
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a,
+			  USB_MIXER_U8, 1, "Save To HW", &opt_save, &elem);
+	if (err < 0)
+		return err;
+
+	/* initialize sampling rate to 48000 */
+	err = snd_usb_ctl_msg(mixer->chip->dev,
+		usb_sndctrlpipe(mixer->chip->dev, 0), UAC2_CS_CUR,
+		USB_RECIP_INTERFACE | USB_TYPE_CLASS |
+		USB_DIR_OUT, 0x0100, snd_usb_ctrl_intf(mixer->chip) |
+		(0x29 << 8), sample_rate_buffer, 4);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h
new file mode 100644
index 0000000..19c592a
--- /dev/null
+++ b/sound/usb/mixer_scarlett.h
@@ -0,0 +1,6 @@ 
+#ifndef __USB_MIXER_SCARLETT_H
+#define __USB_MIXER_SCARLETT_H
+
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer);
+
+#endif /* __USB_MIXER_SCARLETT_H */