|
@@ -1235,6 +1235,21 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol,
|
|
|
return changed;
|
|
|
}
|
|
|
|
|
|
+/* get the current value from a mixer element */
|
|
|
+static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
|
|
|
+ int val, err;
|
|
|
+
|
|
|
+ err = snd_usb_get_cur_mix_value(cval, 0, 0, &val);
|
|
|
+ if (err < 0)
|
|
|
+ return filter_error(cval, err);
|
|
|
+ val = (val != 0);
|
|
|
+ ucontrol->value.integer.value[0] = val;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static struct snd_kcontrol_new usb_feature_unit_ctl = {
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
.name = "", /* will be filled later manually */
|
|
@@ -1252,6 +1267,16 @@ static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
|
|
|
.put = NULL,
|
|
|
};
|
|
|
|
|
|
+/* A UAC connector mixer control */
|
|
|
+static struct snd_kcontrol_new usb_connector_ctl_ro = {
|
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
|
|
+ .name = "", /* will be filled later manually */
|
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
|
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
|
+ .get = mixer_ctl_connector_get,
|
|
|
+ .put = NULL,
|
|
|
+};
|
|
|
+
|
|
|
/*
|
|
|
* This symbol is exported in order to allow the mixer quirks to
|
|
|
* hook up to the standard feature unit control mechanism
|
|
@@ -1464,6 +1489,54 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
|
|
|
snd_usb_mixer_add_control(&cval->head, kctl);
|
|
|
}
|
|
|
|
|
|
+static void get_connector_control_name(struct mixer_build *state,
|
|
|
+ struct usb_audio_term *term,
|
|
|
+ bool is_input, char *name, int name_size)
|
|
|
+{
|
|
|
+ int name_len = get_term_name(state, term, name, name_size, 0);
|
|
|
+
|
|
|
+ if (name_len == 0)
|
|
|
+ strlcpy(name, "Unknown", name_size);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * sound/core/ctljack.c has a convention of naming jack controls
|
|
|
+ * by ending in " Jack". Make it slightly more useful by
|
|
|
+ * indicating Input or Output after the terminal name.
|
|
|
+ */
|
|
|
+ if (is_input)
|
|
|
+ strlcat(name, " - Input Jack", name_size);
|
|
|
+ else
|
|
|
+ strlcat(name, " - Output Jack", name_size);
|
|
|
+}
|
|
|
+
|
|
|
+/* Build a mixer control for a UAC connector control (jack-detect) */
|
|
|
+static void build_connector_control(struct mixer_build *state,
|
|
|
+ struct usb_audio_term *term, bool is_input)
|
|
|
+{
|
|
|
+ struct snd_kcontrol *kctl;
|
|
|
+ struct usb_mixer_elem_info *cval;
|
|
|
+
|
|
|
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
|
|
|
+ if (!cval)
|
|
|
+ return;
|
|
|
+ snd_usb_mixer_elem_init_std(&cval->head, state->mixer, term->id);
|
|
|
+ cval->control = UAC2_TE_CONNECTOR;
|
|
|
+ cval->val_type = USB_MIXER_BOOLEAN;
|
|
|
+ cval->channels = 1; /* report true if any channel is connected */
|
|
|
+ cval->min = 0;
|
|
|
+ cval->max = 1;
|
|
|
+ kctl = snd_ctl_new1(&usb_connector_ctl_ro, cval);
|
|
|
+ if (!kctl) {
|
|
|
+ usb_audio_err(state->chip, "cannot malloc kcontrol\n");
|
|
|
+ kfree(cval);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ get_connector_control_name(state, term, is_input, kctl->id.name,
|
|
|
+ sizeof(kctl->id.name));
|
|
|
+ kctl->private_free = snd_usb_mixer_elem_free;
|
|
|
+ snd_usb_mixer_add_control(&cval->head, kctl);
|
|
|
+}
|
|
|
+
|
|
|
static int parse_clock_source_unit(struct mixer_build *state, int unitid,
|
|
|
void *_ftr)
|
|
|
{
|
|
@@ -1770,6 +1843,23 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
|
|
|
snd_usb_mixer_add_control(&cval->head, kctl);
|
|
|
}
|
|
|
|
|
|
+static int parse_audio_input_terminal(struct mixer_build *state, int unitid,
|
|
|
+ void *raw_desc)
|
|
|
+{
|
|
|
+ struct usb_audio_term iterm;
|
|
|
+ struct uac2_input_terminal_descriptor *d = raw_desc;
|
|
|
+
|
|
|
+ check_input_term(state, d->bTerminalID, &iterm);
|
|
|
+ if (state->mixer->protocol == UAC_VERSION_2) {
|
|
|
+ /* Check for jack detection. */
|
|
|
+ if (uac_v2v3_control_is_readable(d->bmControls,
|
|
|
+ UAC2_TE_CONNECTOR)) {
|
|
|
+ build_connector_control(state, &iterm, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* parse a mixer unit
|
|
|
*/
|
|
@@ -2320,7 +2410,7 @@ static int parse_audio_unit(struct mixer_build *state, int unitid)
|
|
|
if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) {
|
|
|
switch (p1[2]) {
|
|
|
case UAC_INPUT_TERMINAL:
|
|
|
- return 0; /* NOP */
|
|
|
+ return parse_audio_input_terminal(state, unitid, p1);
|
|
|
case UAC_MIXER_UNIT:
|
|
|
return parse_audio_mixer_unit(state, unitid, p1);
|
|
|
case UAC2_CLOCK_SOURCE:
|
|
@@ -2463,6 +2553,12 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
|
|
|
err = parse_audio_unit(&state, desc->bCSourceID);
|
|
|
if (err < 0 && err != -EINVAL)
|
|
|
return err;
|
|
|
+
|
|
|
+ if (uac_v2v3_control_is_readable(desc->bmControls,
|
|
|
+ UAC2_TE_CONNECTOR)) {
|
|
|
+ build_connector_control(&state, &state.oterm,
|
|
|
+ false);
|
|
|
+ }
|
|
|
} else { /* UAC_VERSION_3 */
|
|
|
struct uac3_output_terminal_descriptor *desc = p;
|
|
|
|