[ALSA] Test volume resolution of usb audio at initialization
Takashi Iwai [Tue, 28 Mar 2006 15:58:28 +0000 (17:58 +0200)]
Test the volume of usb audio whether actually it works and adjusts
the resolution value according to it.

Some USB audio devices report a lower resolution than it reacts.
The only possible check is to write and read a volume value.

Signed-off-by: Takashi Iwai <tiwai@suse.de>

sound/usb/usbmixer.c

index 8d08b34..ce86283 100644 (file)
@@ -306,8 +306,8 @@ static int get_relative_value(struct usb_mixer_elem_info *cval, int val)
                cval->res = 1;
        if (val < cval->min)
                return 0;
-       else if (val > cval->max)
-               return (cval->max - cval->min) / cval->res;
+       else if (val >= cval->max)
+               return (cval->max - cval->min + cval->res - 1) / cval->res;
        else
                return (val - cval->min) / cval->res;
 }
@@ -670,6 +670,36 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
                }
                if (cval->res == 0)
                        cval->res = 1;
+
+               /* Additional checks for the proper resolution
+                *
+                * Some devices report smaller resolutions than actually
+                * reacting.  They don't return errors but simply clip
+                * to the lower aligned value.
+                */
+               if (cval->min + cval->res < cval->max) {
+                       int last_valid_res = cval->res;
+                       int saved, test, check;
+                       get_cur_mix_value(cval, minchn, &saved);
+                       for (;;) {
+                               test = saved;
+                               if (test < cval->max)
+                                       test += cval->res;
+                               else
+                                       test -= cval->res;
+                               if (test < cval->min || test > cval->max ||
+                                   set_cur_mix_value(cval, minchn, test) ||
+                                   get_cur_mix_value(cval, minchn, &check)) {
+                                       cval->res = last_valid_res;
+                                       break;
+                               }
+                               if (test == check)
+                                       break;
+                               cval->res *= 2;
+                       }
+                       set_cur_mix_value(cval, minchn, saved);
+               }
+
                cval->initialized = 1;
        }
        return 0;
@@ -695,7 +725,8 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_
                if (! cval->initialized)
                        get_min_max(cval,  0);
                uinfo->value.integer.min = 0;
-               uinfo->value.integer.max = (cval->max - cval->min) / cval->res;
+               uinfo->value.integer.max =
+                       (cval->max - cval->min + cval->res - 1) / cval->res;
        }
        return 0;
 }