summary refs log tree commit diff
path: root/sound/usb/mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/usb/mixer.c')
-rw-r--r--sound/usb/mixer.c213
1 files changed, 116 insertions, 97 deletions
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 03ce971e0027..a060d005e209 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -78,39 +78,6 @@ enum {
 	USB_MIXER_U16,
 };
 
-enum {
-	USB_PROC_UPDOWN = 1,
-	USB_PROC_UPDOWN_SWITCH = 1,
-	USB_PROC_UPDOWN_MODE_SEL = 2,
-
-	USB_PROC_PROLOGIC = 2,
-	USB_PROC_PROLOGIC_SWITCH = 1,
-	USB_PROC_PROLOGIC_MODE_SEL = 2,
-
-	USB_PROC_3DENH = 3,
-	USB_PROC_3DENH_SWITCH = 1,
-	USB_PROC_3DENH_SPACE = 2,
-
-	USB_PROC_REVERB = 4,
-	USB_PROC_REVERB_SWITCH = 1,
-	USB_PROC_REVERB_LEVEL = 2,
-	USB_PROC_REVERB_TIME = 3,
-	USB_PROC_REVERB_DELAY = 4,
-
-	USB_PROC_CHORUS = 5,
-	USB_PROC_CHORUS_SWITCH = 1,
-	USB_PROC_CHORUS_LEVEL = 2,
-	USB_PROC_CHORUS_RATE = 3,
-	USB_PROC_CHORUS_DEPTH = 4,
-
-	USB_PROC_DCR = 6,
-	USB_PROC_DCR_SWITCH = 1,
-	USB_PROC_DCR_RATIO = 2,
-	USB_PROC_DCR_MAX_AMP = 3,
-	USB_PROC_DCR_THRESHOLD = 4,
-	USB_PROC_DCR_ATTACK = 5,
-	USB_PROC_DCR_RELEASE = 6,
-};
 
 /*E-mu 0202(0404) eXtension Unit(XU) control*/
 enum {
@@ -198,22 +165,24 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid,
 
 /*
  * find an audio control unit with the given unit id
- * this doesn't return any clock related units, so they need to be handled elsewhere
  */
 static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit)
 {
-	unsigned char *p;
+	/* we just parse the header */
+	struct uac_feature_unit_descriptor *hdr = NULL;
 
-	p = NULL;
-	while ((p = snd_usb_find_desc(state->buffer, state->buflen, p,
-				      USB_DT_CS_INTERFACE)) != NULL) {
-		if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC2_EXTENSION_UNIT_V2 && p[3] == unit)
-			return p;
+	while ((hdr = snd_usb_find_desc(state->buffer, state->buflen, hdr,
+					USB_DT_CS_INTERFACE)) != NULL) {
+		if (hdr->bLength >= 4 &&
+		    hdr->bDescriptorSubtype >= UAC_INPUT_TERMINAL &&
+		    hdr->bDescriptorSubtype <= UAC2_SAMPLE_RATE_CONVERTER &&
+		    hdr->bUnitID == unit)
+			return hdr;
 	}
+
 	return NULL;
 }
 
-
 /*
  * copy a string with the given id
  */
@@ -344,8 +313,8 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v
 			      buf, sizeof(buf), 1000);
 
 	if (ret < 0) {
-		snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
-			    request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type);
+		snd_printk(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+			   request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type);
 		return ret;
 	}
 
@@ -462,6 +431,16 @@ static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
 			     int index, int value)
 {
 	int err;
+	unsigned int read_only = (channel == 0) ?
+		cval->master_readonly :
+		cval->ch_readonly & (1 << (channel - 1));
+
+	if (read_only) {
+		snd_printdd(KERN_INFO "%s(): channel %d of control %d is read_only\n",
+			    __func__, channel, cval->control);
+		return 0;
+	}
+
 	err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel,
 			    value);
 	if (err < 0)
@@ -631,6 +610,7 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm
  */
 static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term)
 {
+	int err;
 	void *p1;
 
 	memset(term, 0, sizeof(*term));
@@ -651,6 +631,11 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_
 				term->channels = d->bNrChannels;
 				term->chconfig = le32_to_cpu(d->bmChannelConfig);
 				term->name = d->iTerminal;
+
+				/* call recursively to get the clock selectors */
+				err = check_input_term(state, d->bCSourceID, term);
+				if (err < 0)
+					return err;
 			}
 			return 0;
 		case UAC_FEATURE_UNIT: {
@@ -667,7 +652,8 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_
 			term->name = uac_mixer_unit_iMixer(d);
 			return 0;
 		}
-		case UAC_SELECTOR_UNIT: {
+		case UAC_SELECTOR_UNIT:
+		case UAC2_CLOCK_SELECTOR: {
 			struct uac_selector_unit_descriptor *d = p1;
 			/* call recursively to retrieve the channel info */
 			if (check_input_term(state, d->baSourceID[0], term) < 0)
@@ -690,6 +676,13 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_
 			term->name = uac_processing_unit_iProcessing(d, state->mixer->protocol);
 			return 0;
 		}
+		case UAC2_CLOCK_SOURCE: {
+			struct uac_clock_source_descriptor *d = p1;
+			term->type = d->bDescriptorSubtype << 16; /* virtual type */
+			term->id = id;
+			term->name = d->iClockSource;
+			return 0;
+		}
 		default:
 			return -ENODEV;
 		}
@@ -709,16 +702,20 @@ struct usb_feature_control_info {
 };
 
 static struct usb_feature_control_info audio_feature_info[] = {
-	{ "Mute",		USB_MIXER_INV_BOOLEAN },
-	{ "Volume",		USB_MIXER_S16 },
+	{ "Mute",			USB_MIXER_INV_BOOLEAN },
+	{ "Volume",			USB_MIXER_S16 },
 	{ "Tone Control - Bass",	USB_MIXER_S8 },
 	{ "Tone Control - Mid",		USB_MIXER_S8 },
 	{ "Tone Control - Treble",	USB_MIXER_S8 },
 	{ "Graphic Equalizer",		USB_MIXER_S8 }, /* FIXME: not implemeted yet */
-	{ "Auto Gain Control",	USB_MIXER_BOOLEAN },
-	{ "Delay Control",	USB_MIXER_U16 },
-	{ "Bass Boost",		USB_MIXER_BOOLEAN },
-	{ "Loudness",		USB_MIXER_BOOLEAN },
+	{ "Auto Gain Control",		USB_MIXER_BOOLEAN },
+	{ "Delay Control",		USB_MIXER_U16 },
+	{ "Bass Boost",			USB_MIXER_BOOLEAN },
+	{ "Loudness",			USB_MIXER_BOOLEAN },
+	/* UAC2 specific */
+	{ "Input Gain Control",		USB_MIXER_U16 },
+	{ "Input Gain Pad Control",	USB_MIXER_BOOLEAN },
+	{ "Phase Inverter Control",	USB_MIXER_BOOLEAN },
 };
 
 
@@ -958,7 +955,7 @@ static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str)
 static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
 			      unsigned int ctl_mask, int control,
 			      struct usb_audio_term *iterm, int unitid,
-			      int read_only)
+			      int readonly_mask)
 {
 	struct uac_feature_unit_descriptor *desc = raw_desc;
 	unsigned int len = 0;
@@ -970,7 +967,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
 
 	control++; /* change from zero-based to 1-based value */
 
-	if (control == UAC_GRAPHIC_EQUALIZER_CONTROL) {
+	if (control == UAC_FU_GRAPHIC_EQUALIZER) {
 		/* FIXME: not supported yet */
 		return;
 	}
@@ -989,20 +986,25 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
 	cval->control = control;
 	cval->cmask = ctl_mask;
 	cval->val_type = audio_feature_info[control-1].type;
-	if (ctl_mask == 0)
+	if (ctl_mask == 0) {
 		cval->channels = 1;	/* master channel */
-	else {
+		cval->master_readonly = readonly_mask;
+	} else {
 		int i, c = 0;
 		for (i = 0; i < 16; i++)
 			if (ctl_mask & (1 << i))
 				c++;
 		cval->channels = c;
+		cval->ch_readonly = readonly_mask;
 	}
 
 	/* get min/max values */
 	get_min_max(cval, 0);
 
-	if (read_only)
+	/* if all channels in the mask are marked read-only, make the control
+	 * read-only. set_cur_mix_value() will check the mask again and won't
+	 * issue write commands to read-only channels. */
+	if (cval->channels == readonly_mask)
 		kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
 	else
 		kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
@@ -1021,8 +1023,8 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
 				kctl->id.name, sizeof(kctl->id.name));
 
 	switch (control) {
-	case UAC_MUTE_CONTROL:
-	case UAC_VOLUME_CONTROL:
+	case UAC_FU_MUTE:
+	case UAC_FU_VOLUME:
 		/* determine the control name.  the rule is:
 		 * - if a name id is given in descriptor, use it.
 		 * - if the connected input can be determined, then use the name
@@ -1049,9 +1051,9 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
 				len = append_ctl_name(kctl, " Playback");
 			}
 		}
-		append_ctl_name(kctl, control == UAC_MUTE_CONTROL ?
+		append_ctl_name(kctl, control == UAC_FU_MUTE ?
 				" Switch" : " Volume");
-		if (control == UAC_VOLUME_CONTROL) {
+		if (control == UAC_FU_VOLUME) {
 			kctl->tlv.c = mixer_vol_tlv;
 			kctl->vd[0].access |= 
 				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
@@ -1150,7 +1152,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
 		snd_printk(KERN_INFO
 			   "usbmixer: master volume quirk for PCM2702 chip\n");
 		/* disable non-functional volume control */
-		master_bits &= ~UAC_FU_VOLUME;
+		master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME);
 		break;
 	}
 	if (channels > 0)
@@ -1188,19 +1190,22 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
 
 			for (j = 0; j < channels; j++) {
 				unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
-				if (mask & (1 << (i * 2))) {
+				if (uac2_control_is_readable(mask, i)) {
 					ch_bits |= (1 << j);
-					if (~mask & (1 << ((i * 2) + 1)))
+					if (!uac2_control_is_writeable(mask, i))
 						ch_read_only |= (1 << j);
 				}
 			}
 
-			/* FIXME: the whole unit is read-only if any of the channels is marked read-only */
+			/* NOTE: build_feature_ctl() will mark the control read-only if all channels
+			 * are marked read-only in the descriptors. Otherwise, the control will be
+			 * reported as writeable, but the driver will not actually issue a write
+			 * command for read-only channels */
 			if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
-				build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, !!ch_read_only);
-			if (master_bits & (1 << i * 2))
+				build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, ch_read_only);
+			if (uac2_control_is_readable(master_bits, i))
 				build_feature_ctl(state, _ftr, 0, i, &iterm, unitid,
-						  ~master_bits & (1 << ((i * 2) + 1)));
+						  !uac2_control_is_writeable(master_bits, i));
 		}
 	}
 
@@ -1392,51 +1397,51 @@ struct procunit_info {
 };
 
 static struct procunit_value_info updown_proc_info[] = {
-	{ USB_PROC_UPDOWN_SWITCH, "Switch", USB_MIXER_BOOLEAN },
-	{ USB_PROC_UPDOWN_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 },
+	{ UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
 	{ 0 }
 };
 static struct procunit_value_info prologic_proc_info[] = {
-	{ USB_PROC_PROLOGIC_SWITCH, "Switch", USB_MIXER_BOOLEAN },
-	{ USB_PROC_PROLOGIC_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 },
+	{ UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
 	{ 0 }
 };
 static struct procunit_value_info threed_enh_proc_info[] = {
-	{ USB_PROC_3DENH_SWITCH, "Switch", USB_MIXER_BOOLEAN },
-	{ USB_PROC_3DENH_SPACE, "Spaciousness", USB_MIXER_U8 },
+	{ UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 },
 	{ 0 }
 };
 static struct procunit_value_info reverb_proc_info[] = {
-	{ USB_PROC_REVERB_SWITCH, "Switch", USB_MIXER_BOOLEAN },
-	{ USB_PROC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
-	{ USB_PROC_REVERB_TIME, "Time", USB_MIXER_U16 },
-	{ USB_PROC_REVERB_DELAY, "Delay", USB_MIXER_U8 },
+	{ UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
+	{ UAC_REVERB_TIME, "Time", USB_MIXER_U16 },
+	{ UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 },
 	{ 0 }
 };
 static struct procunit_value_info chorus_proc_info[] = {
-	{ USB_PROC_CHORUS_SWITCH, "Switch", USB_MIXER_BOOLEAN },
-	{ USB_PROC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
-	{ USB_PROC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
-	{ USB_PROC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
+	{ UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
+	{ UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
+	{ UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
 	{ 0 }
 };
 static struct procunit_value_info dcr_proc_info[] = {
-	{ USB_PROC_DCR_SWITCH, "Switch", USB_MIXER_BOOLEAN },
-	{ USB_PROC_DCR_RATIO, "Ratio", USB_MIXER_U16 },
-	{ USB_PROC_DCR_MAX_AMP, "Max Amp", USB_MIXER_S16 },
-	{ USB_PROC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
-	{ USB_PROC_DCR_ATTACK, "Attack Time", USB_MIXER_U16 },
-	{ USB_PROC_DCR_RELEASE, "Release Time", USB_MIXER_U16 },
+	{ UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_DCR_RATE, "Ratio", USB_MIXER_U16 },
+	{ UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 },
+	{ UAC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
+	{ UAC_DCR_ATTACK_TIME, "Attack Time", USB_MIXER_U16 },
+	{ UAC_DCR_RELEASE_TIME, "Release Time", USB_MIXER_U16 },
 	{ 0 }
 };
 
 static struct procunit_info procunits[] = {
-	{ USB_PROC_UPDOWN, "Up Down", updown_proc_info },
-	{ USB_PROC_PROLOGIC, "Dolby Prologic", prologic_proc_info },
-	{ USB_PROC_3DENH, "3D Stereo Extender", threed_enh_proc_info },
-	{ USB_PROC_REVERB, "Reverb", reverb_proc_info },
-	{ USB_PROC_CHORUS, "Chorus", chorus_proc_info },
-	{ USB_PROC_DCR, "DCR", dcr_proc_info },
+	{ UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info },
+	{ UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info },
+	{ UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info },
+	{ UAC_PROCESS_REVERB, "Reverb", reverb_proc_info },
+	{ UAC_PROCESS_CHORUS, "Chorus", chorus_proc_info },
+	{ UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info },
 	{ 0 },
 };
 /*
@@ -1524,7 +1529,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw
 		cval->channels = 1;
 
 		/* get min/max values */
-		if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) {
+		if (type == UAC_PROCESS_UP_DOWNMIX && cval->control == UAC_UD_MODE_SELECT) {
 			__u8 *control_spec = uac_processing_unit_specific(desc, state->mixer->protocol);
 			/* FIXME: hard-coded */
 			cval->min = 1;
@@ -1619,7 +1624,7 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_
 	struct usb_mixer_elem_info *cval = kcontrol->private_data;
 	int val, err;
 
-	err = get_cur_ctl_value(cval, 0, &val);
+	err = get_cur_ctl_value(cval, cval->control << 8, &val);
 	if (err < 0) {
 		if (cval->mixer->ignore_ctl_error) {
 			ucontrol->value.enumerated.item[0] = 0;
@@ -1638,7 +1643,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
 	struct usb_mixer_elem_info *cval = kcontrol->private_data;
 	int val, oval, err;
 
-	err = get_cur_ctl_value(cval, 0, &oval);
+	err = get_cur_ctl_value(cval, cval->control << 8, &oval);
 	if (err < 0) {
 		if (cval->mixer->ignore_ctl_error)
 			return 0;
@@ -1647,7 +1652,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
 	val = ucontrol->value.enumerated.item[0];
 	val = get_abs_value(cval, val);
 	if (val != oval) {
-		set_cur_ctl_value(cval, 0, val);
+		set_cur_ctl_value(cval, cval->control << 8, val);
 		return 1;
 	}
 	return 0;
@@ -1729,6 +1734,11 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
 	cval->res = 1;
 	cval->initialized = 1;
 
+	if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR)
+		cval->control = UAC2_CX_CLOCK_SELECTOR;
+	else
+		cval->control = 0;
+
 	namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL);
 	if (! namelist) {
 		snd_printk(KERN_ERR "cannot malloc\n");
@@ -1778,7 +1788,9 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void
 		if (! len)
 			strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name));
 
-		if ((state->oterm.type & 0xff00) == 0x0100)
+		if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR)
+			append_ctl_name(kctl, " Clock Source");
+		else if ((state->oterm.type & 0xff00) == 0x0100)
 			append_ctl_name(kctl, " Capture Source");
 		else
 			append_ctl_name(kctl, " Playback Source");
@@ -1812,10 +1824,12 @@ static int parse_audio_unit(struct mixer_build *state, int unitid)
 
 	switch (p1[2]) {
 	case UAC_INPUT_TERMINAL:
+	case UAC2_CLOCK_SOURCE:
 		return 0; /* NOP */
 	case UAC_MIXER_UNIT:
 		return parse_audio_mixer_unit(state, unitid, p1);
 	case UAC_SELECTOR_UNIT:
+	case UAC2_CLOCK_SELECTOR:
 		return parse_audio_selector_unit(state, unitid, p1);
 	case UAC_FEATURE_UNIT:
 		return parse_audio_feature_unit(state, unitid, p1);
@@ -1912,6 +1926,11 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
 			err = parse_audio_unit(&state, desc->bSourceID);
 			if (err < 0)
 				return err;
+
+			/* for UAC2, use the same approach to also add the clock selectors */
+			err = parse_audio_unit(&state, desc->bCSourceID);
+			if (err < 0)
+				return err;
 		}
 	}