Commit 3e8f3bd0 authored by Thomas Ebeling's avatar Thomas Ebeling Committed by Takashi Iwai
Browse files

ALSA: usb-audio: RME Babyface Pro mixer patch



Added mixer quirks to allow controlling the internal DSP of the
RME Babyface Pro and its successor Babyface Pro FS.

Signed-off-by: default avatarThomas Ebeling <penguins@bollie.de>
Link: https://lore.kernel.org/r/20200414211019.qprg7whepg2y7nei@bollie.ca9.eu


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent d94541fa
Loading
Loading
Loading
Loading
+418 −0
Original line number Diff line number Diff line
@@ -2181,6 +2181,421 @@ static int snd_rme_controls_create(struct usb_mixer_interface *mixer)
	return 0;
}

/*
 * RME Babyface Pro (FS)
 *
 * These devices exposes a couple of DSP functions via request to EP0.
 * Switches are available via control registers, while routing is controlled
 * by controlling the volume on each possible crossing point.
 * Volume control is linear, from -inf (dec. 0) to +6dB (dec. 46341) with
 * 0dB being at dec. 32768.
 */
enum {
	SND_BBFPRO_CTL_REG1 = 0,
	SND_BBFPRO_CTL_REG2
};

#define SND_BBFPRO_CTL_REG_MASK 1
#define SND_BBFPRO_CTL_IDX_MASK 0xff
#define SND_BBFPRO_CTL_IDX_SHIFT 1
#define SND_BBFPRO_CTL_VAL_MASK 1
#define SND_BBFPRO_CTL_VAL_SHIFT 9
#define SND_BBFPRO_CTL_REG1_CLK_MASTER 0
#define SND_BBFPRO_CTL_REG1_CLK_OPTICAL 1
#define SND_BBFPRO_CTL_REG1_SPDIF_PRO 7
#define SND_BBFPRO_CTL_REG1_SPDIF_EMPH 8
#define SND_BBFPRO_CTL_REG1_SPDIF_OPTICAL 10
#define SND_BBFPRO_CTL_REG2_48V_AN1 0
#define SND_BBFPRO_CTL_REG2_48V_AN2 1
#define SND_BBFPRO_CTL_REG2_SENS_IN3 2
#define SND_BBFPRO_CTL_REG2_SENS_IN4 3
#define SND_BBFPRO_CTL_REG2_PAD_AN1 4
#define SND_BBFPRO_CTL_REG2_PAD_AN2 5

#define SND_BBFPRO_MIXER_IDX_MASK 0x1ff
#define SND_BBFPRO_MIXER_VAL_MASK 0x3ffff
#define SND_BBFPRO_MIXER_VAL_SHIFT 9
#define SND_BBFPRO_MIXER_VAL_MIN 0 // -inf
#define SND_BBFPRO_MIXER_VAL_MAX 46341 // +6dB

#define SND_BBFPRO_USBREQ_CTL_REG1 0x10
#define SND_BBFPRO_USBREQ_CTL_REG2 0x17
#define SND_BBFPRO_USBREQ_MIXER 0x12

static int snd_bbfpro_ctl_update(struct usb_mixer_interface *mixer, u8 reg,
				 u8 index, u8 value)
{
	int err;
	u16 usb_req, usb_idx, usb_val;
	struct snd_usb_audio *chip = mixer->chip;

	err = snd_usb_lock_shutdown(chip);
	if (err < 0)
		return err;

	if (reg == SND_BBFPRO_CTL_REG1) {
		usb_req = SND_BBFPRO_USBREQ_CTL_REG1;
		if (index == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) {
			usb_idx = 3;
			usb_val = value ? 3 : 0;
		} else {
			usb_idx = 1 << index;
			usb_val = value ? usb_idx : 0;
		}
	} else {
		usb_req = SND_BBFPRO_USBREQ_CTL_REG2;
		usb_idx = 1 << index;
		usb_val = value ? usb_idx : 0;
	}

	err = snd_usb_ctl_msg(chip->dev,
			      usb_sndctrlpipe(chip->dev, 0), usb_req,
			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
			      usb_val, usb_idx, 0, 0);

	snd_usb_unlock_shutdown(chip);
	return err;
}

static int snd_bbfpro_ctl_get(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	u8 reg, idx, val;
	int pv;

	pv = kcontrol->private_value;
	reg = pv & SND_BBFPRO_CTL_REG_MASK;
	idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK;
	val = kcontrol->private_value >> SND_BBFPRO_CTL_VAL_SHIFT;

	if ((reg == SND_BBFPRO_CTL_REG1 &&
	     idx == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) ||
	    (reg == SND_BBFPRO_CTL_REG2 &&
	    (idx == SND_BBFPRO_CTL_REG2_SENS_IN3 ||
	     idx == SND_BBFPRO_CTL_REG2_SENS_IN4))) {
		ucontrol->value.enumerated.item[0] = val;
	} else {
		ucontrol->value.integer.value[0] = val;
	}
	return 0;
}

static int snd_bbfpro_ctl_info(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_info *uinfo)
{
	u8 reg, idx;
	int pv;

	pv = kcontrol->private_value;
	reg = pv & SND_BBFPRO_CTL_REG_MASK;
	idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK;

	if (reg == SND_BBFPRO_CTL_REG1 &&
	    idx == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) {
		static const char * const texts[2] = {
			"AutoSync",
			"Internal"
		};
		return snd_ctl_enum_info(uinfo, 1, 2, texts);
	} else if (reg == SND_BBFPRO_CTL_REG2 &&
		   (idx == SND_BBFPRO_CTL_REG2_SENS_IN3 ||
		    idx == SND_BBFPRO_CTL_REG2_SENS_IN4)) {
		static const char * const texts[2] = {
			"-10dBV",
			"+4dBu"
		};
		return snd_ctl_enum_info(uinfo, 1, 2, texts);
	}

	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	return 0;
}

static int snd_bbfpro_ctl_put(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	int err;
	u8 reg, idx;
	int old_value, pv, val;

	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
	struct usb_mixer_interface *mixer = list->mixer;

	pv = kcontrol->private_value;
	reg = pv & SND_BBFPRO_CTL_REG_MASK;
	idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK;
	old_value = (pv >> SND_BBFPRO_CTL_VAL_SHIFT) & SND_BBFPRO_CTL_VAL_MASK;

	if ((reg == SND_BBFPRO_CTL_REG1 &&
	     idx == SND_BBFPRO_CTL_REG1_CLK_OPTICAL) ||
	    (reg == SND_BBFPRO_CTL_REG2 &&
	    (idx == SND_BBFPRO_CTL_REG2_SENS_IN3 ||
	     idx == SND_BBFPRO_CTL_REG2_SENS_IN4))) {
		val = ucontrol->value.enumerated.item[0];
	} else {
		val = ucontrol->value.integer.value[0];
	}

	if (val > 1)
		return -EINVAL;

	if (val == old_value)
		return 0;

	kcontrol->private_value = reg
		| ((idx & SND_BBFPRO_CTL_IDX_MASK) << SND_BBFPRO_CTL_IDX_SHIFT)
		| ((val & SND_BBFPRO_CTL_VAL_MASK) << SND_BBFPRO_CTL_VAL_SHIFT);

	err = snd_bbfpro_ctl_update(mixer, reg, idx, val);
	return err < 0 ? err : 1;
}

static int snd_bbfpro_ctl_resume(struct usb_mixer_elem_list *list)
{
	u8 reg, idx;
	int value, pv;

	pv = list->kctl->private_value;
	reg = pv & SND_BBFPRO_CTL_REG_MASK;
	idx = (pv >> SND_BBFPRO_CTL_IDX_SHIFT) & SND_BBFPRO_CTL_IDX_MASK;
	value = (pv >> SND_BBFPRO_CTL_VAL_SHIFT) & SND_BBFPRO_CTL_VAL_MASK;

	return snd_bbfpro_ctl_update(list->mixer, reg, idx, value);
}

static int snd_bbfpro_vol_update(struct usb_mixer_interface *mixer, u16 index,
				 u32 value)
{
	struct snd_usb_audio *chip = mixer->chip;
	int err;
	u16 idx;
	u16 usb_idx, usb_val;
	u32 v;

	err = snd_usb_lock_shutdown(chip);
	if (err < 0)
		return err;

	idx = index & SND_BBFPRO_MIXER_IDX_MASK;
	// 18 bit linear volume, split so 2 bits end up in index.
	v = value & SND_BBFPRO_MIXER_VAL_MASK;
	usb_idx = idx | (v & 0x3) << 14;
	usb_val = (v >> 2) & 0xffff;

	err = snd_usb_ctl_msg(chip->dev,
			      usb_sndctrlpipe(chip->dev, 0),
			      SND_BBFPRO_USBREQ_MIXER,
			      USB_DIR_OUT | USB_TYPE_VENDOR |
			      USB_RECIP_DEVICE,
			      usb_val, usb_idx, 0, 0);

	snd_usb_unlock_shutdown(chip);
	return err;
}

static int snd_bbfpro_vol_get(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	ucontrol->value.integer.value[0] =
		kcontrol->private_value >> SND_BBFPRO_MIXER_VAL_SHIFT;
	return 0;
}

static int snd_bbfpro_vol_info(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = SND_BBFPRO_MIXER_VAL_MIN;
	uinfo->value.integer.max = SND_BBFPRO_MIXER_VAL_MAX;
	return 0;
}

static int snd_bbfpro_vol_put(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	int err;
	u16 idx;
	u32 new_val, old_value, uvalue;
	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
	struct usb_mixer_interface *mixer = list->mixer;

	uvalue = ucontrol->value.integer.value[0];
	idx = kcontrol->private_value & SND_BBFPRO_MIXER_IDX_MASK;
	old_value = kcontrol->private_value >> SND_BBFPRO_MIXER_VAL_SHIFT;

	if (uvalue > SND_BBFPRO_MIXER_VAL_MAX)
		return -EINVAL;

	if (uvalue == old_value)
		return 0;

	new_val = uvalue & SND_BBFPRO_MIXER_VAL_MASK;

	kcontrol->private_value = idx
		| (new_val << SND_BBFPRO_MIXER_VAL_SHIFT);

	err = snd_bbfpro_vol_update(mixer, idx, new_val);
	return err < 0 ? err : 1;
}

static int snd_bbfpro_vol_resume(struct usb_mixer_elem_list *list)
{
	int pv = list->kctl->private_value;
	u16 idx = pv & SND_BBFPRO_MIXER_IDX_MASK;
	u32 val = (pv >> SND_BBFPRO_MIXER_VAL_SHIFT)
		& SND_BBFPRO_MIXER_VAL_MASK;
	return snd_bbfpro_vol_update(list->mixer, idx, val);
}

// Predfine elements
static const struct snd_kcontrol_new snd_bbfpro_ctl_control = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.index = 0,
	.info = snd_bbfpro_ctl_info,
	.get = snd_bbfpro_ctl_get,
	.put = snd_bbfpro_ctl_put
};

static const struct snd_kcontrol_new snd_bbfpro_vol_control = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.index = 0,
	.info = snd_bbfpro_vol_info,
	.get = snd_bbfpro_vol_get,
	.put = snd_bbfpro_vol_put
};

static int snd_bbfpro_ctl_add(struct usb_mixer_interface *mixer, u8 reg,
			      u8 index, char *name)
{
	struct snd_kcontrol_new knew = snd_bbfpro_ctl_control;

	knew.name = name;
	knew.private_value = (reg & SND_BBFPRO_CTL_REG_MASK)
		| ((index & SND_BBFPRO_CTL_IDX_MASK)
			<< SND_BBFPRO_CTL_IDX_SHIFT);

	return add_single_ctl_with_resume(mixer, 0, snd_bbfpro_ctl_resume,
		&knew, NULL);
}

static int snd_bbfpro_vol_add(struct usb_mixer_interface *mixer, u16 index,
			      char *name)
{
	struct snd_kcontrol_new knew = snd_bbfpro_vol_control;

	knew.name = name;
	knew.private_value = index & SND_BBFPRO_MIXER_IDX_MASK;

	return add_single_ctl_with_resume(mixer, 0, snd_bbfpro_vol_resume,
		&knew, NULL);
}

static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer)
{
	int err, i, o;
	char name[48];

	static const char * const input[] = {
		"AN1", "AN2", "IN3", "IN4", "AS1", "AS2", "ADAT3",
		"ADAT4", "ADAT5", "ADAT6", "ADAT7", "ADAT8"};

	static const char * const output[] = {
		"AN1", "AN2", "PH3", "PH4", "AS1", "AS2", "ADAT3", "ADAT4",
		"ADAT5", "ADAT6", "ADAT7", "ADAT8"};

	for (o = 0 ; o < 12 ; ++o) {
		for (i = 0 ; i < 12 ; ++i) {
			// Line routing
			snprintf(name, sizeof(name),
				 "%s-%s-%s Playback Volume",
				 (i < 2 ? "Mic" : "Line"),
				 input[i], output[o]);
			err = snd_bbfpro_vol_add(mixer, (26 * o + i), name);
			if (err < 0)
				return err;

			// PCM routing... yes, it is output remapping
			snprintf(name, sizeof(name),
				 "PCM-%s-%s Playback Volume",
				 output[i], output[o]);
			err = snd_bbfpro_vol_add(mixer, (26 * o + 12 + i),
						 name);
			if (err < 0)
				return err;
		}
	}

	// Control Reg 1
	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1,
				 SND_BBFPRO_CTL_REG1_CLK_OPTICAL,
				 "Sample Clock Source");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1,
				 SND_BBFPRO_CTL_REG1_SPDIF_PRO,
				 "IEC958 Pro Mask");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1,
				 SND_BBFPRO_CTL_REG1_SPDIF_EMPH,
				 "IEC958 Emphasis");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1,
				 SND_BBFPRO_CTL_REG1_SPDIF_OPTICAL,
				 "IEC958 Switch");
	if (err < 0)
		return err;

	// Control Reg 2
	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2,
				 SND_BBFPRO_CTL_REG2_48V_AN1,
				 "Mic-AN1 48V");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2,
				 SND_BBFPRO_CTL_REG2_48V_AN2,
				 "Mic-AN2 48V");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2,
				 SND_BBFPRO_CTL_REG2_SENS_IN3,
				 "Line-IN3 Sens.");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2,
				 SND_BBFPRO_CTL_REG2_SENS_IN4,
				 "Line-IN4 Sens.");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2,
				 SND_BBFPRO_CTL_REG2_PAD_AN1,
				 "Mic-AN1 PAD");
	if (err < 0)
		return err;

	err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG2,
				 SND_BBFPRO_CTL_REG2_PAD_AN2,
				 "Mic-AN2 PAD");
	if (err < 0)
		return err;

	return 0;
}

int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
{
	int err = 0;
@@ -2282,6 +2697,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
	case USB_ID(0x0194f, 0x010c): /* Presonus Studio 1810c */
		err = snd_sc1810_init_mixer(mixer);
		break;
	case USB_ID(0x2a39, 0x3fb0): /* RME Babyface Pro FS */
		err = snd_bbfpro_controls_create(mixer);
		break;
	}

	return err;