ALSA: usb - Don't create "Speaker" mixer controls on headphones and headsets
[pandora-kernel.git] / sound / usb / mixer.c
index fe56c9d..ed4d89c 100644 (file)
@@ -287,25 +287,32 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int v
        unsigned char buf[2];
        int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
        int timeout = 10;
-       int err;
+       int idx = 0, err;
 
        err = snd_usb_autoresume(cval->mixer->chip);
        if (err < 0)
                return -EIO;
+       down_read(&chip->shutdown_rwsem);
        while (timeout-- > 0) {
+               if (chip->shutdown)
+                       break;
+               idx = snd_usb_ctrl_intf(chip) | (cval->id << 8);
                if (snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request,
                                    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
-                                   validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
-                                   buf, val_len) >= val_len) {
+                                   validx, idx, buf, val_len) >= val_len) {
                        *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len));
-                       snd_usb_autosuspend(cval->mixer->chip);
-                       return 0;
+                       err = 0;
+                       goto out;
                }
        }
-       snd_usb_autosuspend(cval->mixer->chip);
        snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
-                   request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type);
-       return -EINVAL;
+                   request, validx, idx, cval->val_type);
+       err = -EINVAL;
+
+ out:
+       up_read(&chip->shutdown_rwsem);
+       snd_usb_autosuspend(cval->mixer->chip);
+       return err;
 }
 
 static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
@@ -313,7 +320,7 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v
        struct snd_usb_audio *chip = cval->mixer->chip;
        unsigned char buf[2 + 3*sizeof(__u16)]; /* enough space for one range */
        unsigned char *val;
-       int ret, size;
+       int idx = 0, ret, size;
        __u8 bRequest;
 
        if (request == UAC_GET_CUR) {
@@ -330,16 +337,22 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v
        if (ret)
                goto error;
 
-       ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
+       down_read(&chip->shutdown_rwsem);
+       if (chip->shutdown)
+               ret = -ENODEV;
+       else {
+               idx = snd_usb_ctrl_intf(chip) | (cval->id << 8);
+               ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
                              USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
-                             validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
-                             buf, size);
+                             validx, idx, buf, size);
+       }
+       up_read(&chip->shutdown_rwsem);
        snd_usb_autosuspend(chip);
 
        if (ret < 0) {
 error:
                snd_printk(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
-                          request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type);
+                          request, validx, idx, cval->val_type);
                return ret;
        }
 
@@ -369,6 +382,8 @@ error:
 
 static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
 {
+       validx += cval->idx_off;
+
        return (cval->mixer->protocol == UAC_VERSION_1) ?
                get_ctl_value_v1(cval, request, validx, value_ret) :
                get_ctl_value_v2(cval, request, validx, value_ret);
@@ -417,7 +432,9 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
 {
        struct snd_usb_audio *chip = cval->mixer->chip;
        unsigned char buf[2];
-       int val_len, err, timeout = 10;
+       int idx = 0, val_len, err, timeout = 10;
+
+       validx += cval->idx_off;
 
        if (cval->mixer->protocol == UAC_VERSION_1) {
                val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
@@ -440,19 +457,27 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
        err = snd_usb_autoresume(chip);
        if (err < 0)
                return -EIO;
-       while (timeout-- > 0)
+       down_read(&chip->shutdown_rwsem);
+       while (timeout-- > 0) {
+               if (chip->shutdown)
+                       break;
+               idx = snd_usb_ctrl_intf(chip) | (cval->id << 8);
                if (snd_usb_ctl_msg(chip->dev,
                                    usb_sndctrlpipe(chip->dev, 0), request,
                                    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
-                                   validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
-                                   buf, val_len) >= 0) {
-                       snd_usb_autosuspend(chip);
-                       return 0;
+                                   validx, idx, buf, val_len) >= 0) {
+                       err = 0;
+                       goto out;
                }
-       snd_usb_autosuspend(chip);
+       }
        snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n",
-                   request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type, buf[0], buf[1]);
-       return -EINVAL;
+                   request, validx, idx, cval->val_type, buf[0], buf[1]);
+       err = -EINVAL;
+
+ out:
+       up_read(&chip->shutdown_rwsem);
+       snd_usb_autosuspend(chip);
+       return err;
 }
 
 static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value)
@@ -698,8 +723,19 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_
                        return 0;
                }
                case UAC1_PROCESSING_UNIT:
-               case UAC1_EXTENSION_UNIT: {
+               case UAC1_EXTENSION_UNIT:
+               /* UAC2_PROCESSING_UNIT_V2 */
+               /* UAC2_EFFECT_UNIT */ {
                        struct uac_processing_unit_descriptor *d = p1;
+
+                       if (state->mixer->protocol == UAC_VERSION_2 &&
+                               hdr[2] == UAC2_EFFECT_UNIT) {
+                               /* UAC2/UAC1 unit IDs overlap here in an
+                                * uncompatible way. Ignore this unit for now.
+                                */
+                               return 0;
+                       }
+
                        if (d->bNrInPins) {
                                id = d->baSourceID[0];
                                break; /* continue to parse */
@@ -770,6 +806,33 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval,
                                  struct snd_kcontrol *kctl)
 {
        switch (cval->mixer->chip->usb_id) {
+       case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */
+               if (strcmp(kctl->id.name, "Effect Duration") == 0) {
+                       cval->min = 0x0000;
+                       cval->max = 0xffff;
+                       cval->res = 0x00e6;
+                       break;
+               }
+               if (strcmp(kctl->id.name, "Effect Volume") == 0 ||
+                   strcmp(kctl->id.name, "Effect Feedback Volume") == 0) {
+                       cval->min = 0x00;
+                       cval->max = 0xff;
+                       break;
+               }
+               if (strstr(kctl->id.name, "Effect Return") != NULL) {
+                       cval->min = 0xb706;
+                       cval->max = 0xff7b;
+                       cval->res = 0x0073;
+                       break;
+               }
+               if ((strstr(kctl->id.name, "Playback Volume") != NULL) ||
+                       (strstr(kctl->id.name, "Effect Send") != NULL)) {
+                       cval->min = 0xb5fb; /* -73 dB = 0xb6ff */
+                       cval->max = 0xfcfe;
+                       cval->res = 0x0073;
+               }
+               break;
+
        case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */
        case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */
                if (strcmp(kctl->id.name, "Effect Duration") == 0) {
@@ -1073,6 +1136,32 @@ static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str)
        return strlcat(kctl->id.name, str, sizeof(kctl->id.name));
 }
 
+/* A lot of headsets/headphones have a "Speaker" mixer. Make sure we
+   rename it to "Headphone". We determine if something is a headphone
+   similar to how udev determines form factor. */
+static void check_no_speaker_on_headset(struct snd_kcontrol *kctl,
+                                       struct snd_card *card)
+{
+       const char *names_to_check[] = {
+               "Headset", "headset", "Headphone", "headphone", NULL};
+       const char **s;
+       bool found = 0;
+
+       if (strcmp("Speaker", kctl->id.name))
+               return;
+
+       for (s = names_to_check; *s; s++)
+               if (strstr(card->shortname, *s)) {
+                       found = 1;
+                       break;
+               }
+
+       if (!found)
+               return;
+
+       strlcpy(kctl->id.name, "Headphone", sizeof(kctl->id.name));
+}
+
 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,
@@ -1159,6 +1248,10 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
                                len = snprintf(kctl->id.name, sizeof(kctl->id.name),
                                               "Feature %d", unitid);
                }
+
+               if (!mapped_name)
+                       check_no_speaker_on_headset(kctl, state->mixer->chip->card);
+
                /* determine the stream direction:
                 * if the connected output is USB stream, then it's likely a
                 * capture stream.  otherwise it should be playback (hopefully :)