[alsa-devel] [PATCH v3 4/4] ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
Takashi Iwai
tiwai at suse.de
Thu Oct 30 08:43:57 CET 2014
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?
> +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.
> +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().
> +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.
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