Merge tag 'asoc-v4.7-fix-hdac' of git://git.kernel.org/pub/scm/linux/kernel/git/broon...
[cascardo/linux.git] / sound / usb / mixer.c
index 4f85757..2f8c388 100644 (file)
@@ -45,6 +45,7 @@
 #include <linux/bitops.h>
 #include <linux/init.h>
 #include <linux/list.h>
+#include <linux/log2.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/usb.h>
@@ -1378,6 +1379,71 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
        snd_usb_mixer_add_control(&cval->head, kctl);
 }
 
+static int parse_clock_source_unit(struct mixer_build *state, int unitid,
+                                  void *_ftr)
+{
+       struct uac_clock_source_descriptor *hdr = _ftr;
+       struct usb_mixer_elem_info *cval;
+       struct snd_kcontrol *kctl;
+       char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+       int ret;
+
+       if (state->mixer->protocol != UAC_VERSION_2)
+               return -EINVAL;
+
+       if (hdr->bLength != sizeof(*hdr)) {
+               usb_audio_dbg(state->chip,
+                             "Bogus clock source descriptor length of %d, ignoring.\n",
+                             hdr->bLength);
+               return 0;
+       }
+
+       /*
+        * The only property of this unit we are interested in is the
+        * clock source validity. If that isn't readable, just bail out.
+        */
+       if (!uac2_control_is_readable(hdr->bmControls,
+                                     ilog2(UAC2_CS_CONTROL_CLOCK_VALID)))
+               return 0;
+
+       cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+       if (!cval)
+               return -ENOMEM;
+
+       snd_usb_mixer_elem_init_std(&cval->head, state->mixer, hdr->bClockID);
+
+       cval->min = 0;
+       cval->max = 1;
+       cval->channels = 1;
+       cval->val_type = USB_MIXER_BOOLEAN;
+       cval->control = UAC2_CS_CONTROL_CLOCK_VALID;
+
+       if (uac2_control_is_writeable(hdr->bmControls,
+                                     ilog2(UAC2_CS_CONTROL_CLOCK_VALID)))
+               kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+       else {
+               cval->master_readonly = 1;
+               kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
+       }
+
+       if (!kctl) {
+               kfree(cval);
+               return -ENOMEM;
+       }
+
+       kctl->private_free = snd_usb_mixer_elem_free;
+       ret = snd_usb_copy_string_desc(state, hdr->iClockSource,
+                                      name, sizeof(name));
+       if (ret > 0)
+               snprintf(kctl->id.name, sizeof(kctl->id.name),
+                        "%s Validity", name);
+       else
+               snprintf(kctl->id.name, sizeof(kctl->id.name),
+                        "Clock Source %d Validity", hdr->bClockID);
+
+       return snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
 /*
  * parse a feature unit
  *
@@ -2126,10 +2192,11 @@ 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 UAC2_CLOCK_SOURCE:
+               return parse_clock_source_unit(state, unitid, p1);
        case UAC_SELECTOR_UNIT:
        case UAC2_CLOCK_SELECTOR:
                return parse_audio_selector_unit(state, unitid, p1);
@@ -2307,6 +2374,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
        __u8 unitid = (index >> 8) & 0xff;
        __u8 control = (value >> 8) & 0xff;
        __u8 channel = value & 0xff;
+       unsigned int count = 0;
 
        if (channel >= MAX_CHANNELS) {
                usb_audio_dbg(mixer->chip,
@@ -2315,6 +2383,12 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
                return;
        }
 
+       for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem)
+               count++;
+
+       if (count == 0)
+               return;
+
        for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem) {
                struct usb_mixer_elem_info *info;
 
@@ -2322,7 +2396,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
                        continue;
 
                info = (struct usb_mixer_elem_info *)list;
-               if (info->control != control)
+               if (count > 1 && info->control != control)
                        continue;
 
                switch (attribute) {