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

Chris J Arges chris.j.arges at canonical.com
Mon Nov 3 18:11:45 CET 2014



On 10/30/2014 02:43 AM, Takashi Iwai wrote:
> At Wed, 29 Oct 2014 15:56:03 -0500,
> Chris J Arges wrote:
>>
>> +/********************** Enum Strings *************************/
>> +static const char txtOff[] = "Off",
>> +	     txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
>> +	     txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",
>> +	     txtPcm5[] = "PCM 5", txtPcm6[] = "PCM 6",
>> +	     txtPcm7[] = "PCM 7", txtPcm8[] = "PCM 8",
>> +	     txtPcm9[] = "PCM 9", txtPcm10[] = "PCM 10",
>> +	     txtPcm11[] = "PCM 11", txtPcm12[] = "PCM 12",
>> +	     txtPcm13[] = "PCM 13", txtPcm14[] = "PCM 14",
>> +	     txtPcm15[] = "PCM 15", txtPcm16[] = "PCM 16",
>> +	     txtPcm17[] = "PCM 17", txtPcm18[] = "PCM 18",
>> +	     txtPcm19[] = "PCM 19", txtPcm20[] = "PCM 20",
>> +	     txtAnlg1[] = "Analog 1", txtAnlg2[] = "Analog 2",
>> +	     txtAnlg3[] = "Analog 3", txtAnlg4[] = "Analog 4",
>> +	     txtAnlg5[] = "Analog 5", txtAnlg6[] = "Analog 6",
>> +	     txtAnlg7[] = "Analog 7", txtAnlg8[] = "Analog 8",
>> +	     txtSpdif1[] = "SPDIF 1", txtSpdif2[] = "SPDIF 2",
>> +	     txtAdat1[] = "ADAT 1", txtAdat2[] = "ADAT 2",
>> +	     txtAdat3[] = "ADAT 3", txtAdat4[] = "ADAT 4",
>> +	     txtAdat5[] = "ADAT 5", txtAdat6[] = "ADAT 6",
>> +	     txtAdat7[] = "ADAT 7", txtAdat8[] = "ADAT 8",
>> +	     txtMix1[] = "Mix A", txtMix2[] = "Mix B",
>> +	     txtMix3[] = "Mix C", txtMix4[] = "Mix D",
>> +	     txtMix5[] = "Mix E", txtMix6[] = "Mix F",
>> +	     txtMix7[] = "Mix G", txtMix8[] = "Mix H";
> 
> This is too ugly.  Can we generate strings systematically?
> 

Hi, at some point we need an array of static strings to pass into
snd_ctl_enum_info, so in v3 I've created a macro to define the strings
and created per device array of strings. I can do this using a function
as well and dynamically allocate memory if that is a better approach.

>> +static const struct usb_mixer_elem_enum_info opt_pad = {
>> +	.start = 0,
>> +	.len = 2,
>> +	.names = (const char *[]){
> 
> Better to be "const char * const []"?
> 
>> +		txtOff, "-10dB"
>> +	}
>> +};
>> +
>> +static const struct usb_mixer_elem_enum_info opt_impedance = {
>> +	.start = 0,
>> +	.len = 2,
>> +	.names = (const char *[]){
>> +		"Line", "Hi-Z"
>> +	}
>> +};
>> +
>> +static const struct usb_mixer_elem_enum_info opt_clock = {
>> +	.start = 1,
>> +	.len = 3,
>> +	.names = (const char *[]){
>> +		"Internal", "SPDIF", "ADAT"
>> +	}
>> +};
>> +
>> +static const struct usb_mixer_elem_enum_info opt_sync = {
>> +	.start = 0,
>> +	.len = 2,
>> +	.names = (const char *[]){
>> +		"No Lock", "Locked"
>> +	}
>> +};
>> +
>> +static const struct usb_mixer_elem_enum_info opt_save = {
>> +	.start = 0,
>> +	.len = 2,
>> +	.names = (const char *[]){
>> +		"---", "Save"
>> +	}
>> +};
> 
> This enum item look strange.
> 

This control is activated much like a push button, so normally its in
the "---" state and if you active it then it triggers the "Save to HW"
function. Is there a better way to express this control?

>> +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;
>> +}
> 
> Use snd_ctl_boolean_mono_info().
> 

I've tried this but unfortunately some of the switch controls are stereo
and some are mono. I could create two separate controls for mono/stereo,
or perhaps I make this function more generate and add as
'snd_ctl_boolean_info' and allow it to check 'channels'?

>> +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; /* alsa uses 0: on, 1: off */
> 
> Hm?  ALSA uses 0:off 1:on in general.  The meaning of "mute" is
> inverted -- it turns off when it's 1.  So, in mixer.c, the mute
> control is assigned as USB_MIXER_INV_BOOLEAN.
> 
>> +		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 = -128 + SND_SCARLETT_LEVEL_BIAS;
> 
> This is 0, right?  IOW, SND_SCARLETT_LEVEL_BIAS was defined so that
> this becomes zero.
> 
>> +	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;
>> +
>> +	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
>> +	uinfo->count = elem->channels;
>> +	uinfo->value.enumerated.items = elem->opt->len;
>> +	if (uinfo->value.enumerated.item > uinfo->value.enumerated.items - 1)
>> +		uinfo->value.enumerated.item =
>> +			uinfo->value.enumerated.items - 1;
>> +	strcpy(uinfo->value.enumerated.name,
>> +	       elem->opt->names[uinfo->value.enumerated.item]);
>> +	return 0;
>> +}
> 
> Use snd_ctl_enum_info().
> 
>> +
>> +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;
>> +	int err, val;
>> +
>> +	err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
>> +	if (err < 0)
>> +		return err;
>> +
>> +	if ((elem->opt->start == -1) && (val > elem->opt->len)) /* >= 0x20 */
>> +		val = 0;
>> +	else
>> +		val = clamp(val - elem->opt->start, 0, elem->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;
>> +	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 + elem->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;
>> +
>> +		dev_info(&(elem->mixer->chip->dev->dev),
>> +			 "scarlett: saved settings to hardware.\n");
> 
> This can be usb_audio_info(chip, ...)
> 
>> +	}
>> +	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 usb_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;
>> +	elem->opt = opt;
>> +
>> +	kctl = snd_ctl_new1(ncontrol, elem);
>> +	if (!kctl) {
>> +		dev_err(&(mixer->chip->dev->dev), "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 init_ctl(struct usb_mixer_elem_info *elem, int value)
>> +{
>> +	int err, channel;
>> +
>> +	for (channel = 0; channel < elem->channels; channel++) {
>> +		err = snd_usb_set_cur_mix_value(elem, channel, channel, value);
>> +		if (err < 0)
>> +			return err;
>> +	}
>> +	return 0;
>> +}
>> +
>> +#define INIT(value) \
>> +	do { \
>> +		err = init_ctl(elem, value); \
>> +		if (err < 0) \
>> +			return err; \
>> +	} while (0)
>> +
>> +#define CTL_SWITCH(cmd, off, no, count, name) \
>> +	do { \
>> +		err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, cmd, off, \
>> +			no, USB_MIXER_S16, count, name, NULL, &elem); \
>> +		if (err < 0) \
>> +			return err; \
>> +	} while (0)
>> +
>> +/* no multichannel enum, always count == 1  (at least for now) */
>> +#define CTL_ENUM(cmd, off, no, name, opt) \
>> +	do { \
>> +		err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, cmd, off, \
>> +			no, USB_MIXER_S16, 1, name, opt, &elem); \
>> +		if (err < 0) \
>> +			return err; \
>> +	} while (0)
>> +
>> +#define CTL_MIXER(cmd, off, no, count, name) \
>> +	do { \
>> +		err = add_new_ctl(mixer, &usb_scarlett_ctl, cmd, off, \
>> +			no, USB_MIXER_S16, count, name, NULL, &elem); \
>> +		if (err < 0) \
>> +			return err; \
>> +		INIT(-32768); /* -128*256 */  \
>> +	} while (0)
>> +
>> +#define CTL_MASTER(cmd, off, no, count, name) \
>> +	do { \
>> +		err = add_new_ctl(mixer, &usb_scarlett_ctl_master, cmd, off, \
>> +			no, USB_MIXER_S16, count, name, NULL, &elem); \
>> +		if (err < 0) \
>> +			return err; \
>> +		INIT(0); \
>> +	} while (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);
>> +	CTL_SWITCH(0x0a, 0x01, 2*index+1, 2, mx);
>> +
>> +	/* Add volume control */
>> +	snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume",
>> +		index + 1, name);
>> +	CTL_MASTER(0x0a, 0x02, 2*index+1, 2, mx);
>> +
>> +	/* Add L channel source playback enumeration */
>> +	snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum",
>> +		index + 1, name);
>> +	CTL_ENUM(0x33, 0x00, 2*index, mx, &info->opt_master);
>> +	INIT(info->mix_start);
>> +
>> +	/* Add R channel source playback enumeration */
>> +	snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum",
>> +		index + 1, name);
>> +	CTL_ENUM(0x33, 0x00, 2*index+1, mx, &info->opt_master);
>> +	INIT(info->mix_start + 1);
>> +
>> +	return 0;
>> +}
>> +
>> +#define CTLS_OUTPUT(index, name) \
>> +	do { \
>> +		err = add_output_ctls(mixer, index, name, info); \
>> +		if (err < 0) \
>> +			return err;\
>> +	} while (0)
> 
> Hmm, these macros...  Can we build from a table instead of calling
> each function?  This would reduce the code size significantly, too,
> and the code flow would be more obvious.  The biggest disadvantage of
> this kind of macro is that the code flow is hidden.  It doesn't show
> that it can return secretly at error.
> 
> 

I've got this mostly working, and will remove these macros in v3. The
rest of the suggestions are implemented, and I've responded with some
questions before I submit v3.

Thanks,
--chris


> Takashi
> 
>> +/********************** device-specific config *************************/
>> +static int scarlet_s6i6_controls(struct usb_mixer_interface *mixer,
>> +				 const struct scarlett_device_info *info)
>> +{
>> +	struct usb_mixer_elem_info *elem;
>> +	int err;
>> +
>> +	CTLS_OUTPUT(0, "Monitor");
>> +	CTLS_OUTPUT(1, "Headphone 2");
>> +	CTLS_OUTPUT(2, "SPDIF");
>> +
>> +	CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
>> +
>> +	CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
>> +
>> +	CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
>> +	CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
>> +
>> +	return 0;
>> +}
>> +
>> +static int scarlet_s8i6_controls(struct usb_mixer_interface *mixer,
>> +				 const struct scarlett_device_info *info)
>> +{
>> +	struct usb_mixer_elem_info *elem;
>> +	int err;
>> +
>> +	CTLS_OUTPUT(0, "Monitor");
>> +	CTLS_OUTPUT(1, "Headphone");
>> +	CTLS_OUTPUT(2, "SPDIF");
>> +
>> +	CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
>> +
>> +	CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
>> +	CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
>> +
>> +	return 0;
>> +}
>> +
>> +static int scarlet_s18i6_controls(struct usb_mixer_interface *mixer,
>> +				  const struct scarlett_device_info *info)
>> +{
>> +	struct usb_mixer_elem_info *elem;
>> +	int err;
>> +
>> +	CTLS_OUTPUT(0, "Monitor");
>> +	CTLS_OUTPUT(1, "Headphone");
>> +	CTLS_OUTPUT(2, "SPDIF");
>> +
>> +	CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
>> +
>> +	return 0;
>> +}
>> +
>> +static int scarlet_s18i8_controls(struct usb_mixer_interface *mixer,
>> +				  const struct scarlett_device_info *info)
>> +{
>> +	struct usb_mixer_elem_info *elem;
>> +	int err;
>> +
>> +	CTLS_OUTPUT(0, "Monitor");
>> +	CTLS_OUTPUT(1, "Headphone 1");
>> +	CTLS_OUTPUT(2, "Headphone 2");
>> +	CTLS_OUTPUT(3, "SPDIF");
>> +
>> +	CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
>> +
>> +	CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
>> +
>> +	CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
>> +	CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
>> +
>> +	return 0;
>> +}
>> +
>> +static int scarlet_s18i20_controls(struct usb_mixer_interface *mixer,
>> +				   const struct scarlett_device_info *info)
>> +{
>> +	int err;
>> +
>> +	CTLS_OUTPUT(0, "Monitor");   /* 1/2 */
>> +	CTLS_OUTPUT(1, "Line 3/4");
>> +	CTLS_OUTPUT(2, "Line 5/6");
>> +	CTLS_OUTPUT(3, "Line 7/8");  /* = Headphone 1 */
>> +	CTLS_OUTPUT(4, "Line 9/10"); /* = Headphone 2 */
>> +	CTLS_OUTPUT(5, "SPDIF");
>> +	CTLS_OUTPUT(6, "ADAT 1/2");
>> +	CTLS_OUTPUT(7, "ADAT 3/4");
>> +	CTLS_OUTPUT(8, "ADAT 5/6");
>> +	CTLS_OUTPUT(9, "ADAT 7/8");
>> +
>> +/* ? real hardware switches
>> +	CTL_ENUM  (0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM  (0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
>> +
>> +	CTL_ENUM  (0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
>> +	CTL_ENUM  (0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
>> +
>> +	CTL_ENUM  (0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
>> +	CTL_ENUM  (0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
>> +*/
>> +
>> +	return 0;
>> +}
>> +
>> +static const char * const s6i6_names[] = {
>> +	txtOff, /* 'off' == 0xff */
>> +	txtPcm1, txtPcm2, txtPcm3, txtPcm4,
>> +	txtPcm5, txtPcm6, txtPcm7, txtPcm8,
>> +	txtPcm9, txtPcm10, txtPcm11, txtPcm12,
>> +	txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
>> +	txtSpdif1, txtSpdif2,
>> +	txtMix1, txtMix2, txtMix3, txtMix4,
>> +	txtMix5, txtMix6, txtMix7, txtMix8
>> +};
>> +
>> +/*  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
>> +	},
>> +
>> +	.controls_fn = scarlet_s6i6_controls,
>> +	.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[] = {
>> +	txtOff, /* 'off' == 0xff */
>> +	txtPcm1, txtPcm2, txtPcm3, txtPcm4,
>> +	txtPcm5, txtPcm6, txtPcm7, txtPcm8,
>> +	txtPcm9, txtPcm10, txtPcm11, txtPcm12,
>> +	txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
>> +	txtSpdif1, txtSpdif2,
>> +	txtMix1, txtMix2, txtMix3, txtMix4,
>> +	txtMix5, txtMix6
>> +};
>> +
>> +/*  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
>> +	},
>> +
>> +	.controls_fn = scarlet_s8i6_controls,
>> +	.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[] = {
>> +	txtOff, /* 'off' == 0xff */
>> +	txtPcm1, txtPcm2, txtPcm3, txtPcm4,
>> +	txtPcm5, txtPcm6,
>> +	txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
>> +	txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
>> +	txtSpdif1, txtSpdif2,
>> +	txtAdat1, txtAdat2, txtAdat3, txtAdat4,
>> +	txtAdat5, txtAdat6, txtAdat7, txtAdat8,
>> +	txtMix1, txtMix2, txtMix3, txtMix4,
>> +	txtMix5, txtMix6
>> +};
>> +
>> +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
>> +	},
>> +
>> +	.controls_fn = scarlet_s18i6_controls,
>> +	.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[] = {
>> +	txtOff, /* 'off' == 0xff  (original software: 0x22) */
>> +	txtPcm1, txtPcm2, txtPcm3, txtPcm4,
>> +	txtPcm5, txtPcm6, txtPcm7, txtPcm8,
>> +	txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
>> +	txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
>> +	txtSpdif1, txtSpdif2,
>> +	txtAdat1, txtAdat2, txtAdat3, txtAdat4,
>> +	txtAdat5, txtAdat6, txtAdat7, txtAdat8,
>> +	txtMix1, txtMix2, txtMix3, txtMix4,
>> +	txtMix5, txtMix6, txtMix7, txtMix8
>> +};
>> +
>> +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
>> +	},
>> +
>> +	.controls_fn = scarlet_s18i8_controls,
>> +	.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[] = {
>> +	txtOff, /* 'off' == 0xff  (original software: 0x22) */
>> +	txtPcm1, txtPcm2, txtPcm3, txtPcm4,
>> +	txtPcm5, txtPcm6, txtPcm7, txtPcm8,
>> +	txtPcm9, txtPcm10, txtPcm11, txtPcm12,
>> +	txtPcm13, txtPcm14, txtPcm15, txtPcm16,
>> +	txtPcm17, txtPcm18, txtPcm19, txtPcm20,
>> +	txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
>> +	txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
>> +	txtSpdif1, txtSpdif2,
>> +	txtAdat1, txtAdat2, txtAdat3, txtAdat4,
>> +	txtAdat5, txtAdat6, txtAdat7, txtAdat8,
>> +	txtMix1, txtMix2, txtMix3, txtMix4,
>> +	txtMix5, txtMix6, txtMix7, txtMix8
>> +};
>> +
>> +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
>> +	},
>> +
>> +	.controls_fn = scarlet_s18i20_controls,
>> +	.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 */
>> +	}
>> +};
>> +
>> +/*
>> + * 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' };
>> +
>> +	CTL_SWITCH(0x0a, 0x01, 0, 1, "Master Playback Switch");
>> +	CTL_MASTER(0x0a, 0x02, 0, 1, "Master Playback Volume");
>> +
>> +	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;
>> +	}
>> +
>> +	err = (*info->controls_fn)(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);
>> +		CTL_ENUM(0x32, 0x06, i, mx, &info->opt_matrix);
>> +		INIT(info->matrix_mux_init[i]);
>> +
>> +		for (o = 0; o < info->matrix_out; o++) {
>> +			sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1,
>> +				o+'A');
>> +			CTL_MIXER(0x3c, 0x00, (i << 3) + (o & 0x07), 1, mx);
>> +			if (((o == 0) &&
>> +			     (info->matrix_mux_init[i] == info->pcm_start)) ||
>> +			    ((o == 1) &&
>> +			     (info->matrix_mux_init[i] == info->pcm_start + 1))
>> +			   ) {
>> +				INIT(0); /* hack: enable PCM 1/2 on Mix A/B */
>> +			}
>> +		}
>> +	}
>> +
>> +	for (i = 0; i < info->input_len; i++) {
>> +		snprintf(mx, 32, "Input Source %02d Capture Route", i+1);
>> +		CTL_ENUM(0x34, 0x00, i, mx, &info->opt_master);
>> +		INIT(info->analog_start + i);
>> +	}
>> +
>> +	/* 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 */
>> -- 
>> 2.1.0
>>
> 


More information about the Alsa-devel mailing list