ALSA: usb-audio: add UAC2 sepecific Feature Unit controls
[safe/jmp/linux-2.6] / sound / usb / mixer.c
index e350f05..8be6bf2 100644 (file)
@@ -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 {
@@ -462,6 +429,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)
@@ -709,16 +686,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 +939,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 +951,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 +970,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 +1007,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 +1035,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 |
@@ -1126,7 +1112,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
        } else {
                struct uac2_feature_unit_descriptor *ftr = _ftr;
                csize = 4;
-               channels = (hdr->bLength - 6) / 4;
+               channels = (hdr->bLength - 6) / 4 - 1;
                bmaControls = ftr->bmaControls;
        }
 
@@ -1150,7 +1136,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 +1174,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,59 +1381,59 @@ 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 },
 };
 /*
  * predefined data for extension units
  */
 static struct procunit_value_info clock_rate_xu_info[] = {
-       { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 },
-       { 0 }
+       { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 },
+       { 0 }
 };
 static struct procunit_value_info clock_source_xu_info[] = {
        { USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN },
@@ -1524,7 +1513,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;
@@ -1967,26 +1956,98 @@ static void snd_usb_mixer_proc_read(struct snd_info_entry *entry,
        }
 }
 
-static void snd_usb_mixer_status_complete(struct urb *urb)
+static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
+                                      int attribute, int value, int index)
+{
+       struct usb_mixer_elem_info *info;
+       __u8 unitid = (index >> 8) & 0xff;
+       __u8 control = (value >> 8) & 0xff;
+       __u8 channel = value & 0xff;
+
+       if (channel >= MAX_CHANNELS) {
+               snd_printk(KERN_DEBUG "%s(): bogus channel number %d\n",
+                               __func__, channel);
+               return;
+       }
+
+       for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) {
+               if (info->control != control)
+                       continue;
+
+               switch (attribute) {
+               case UAC2_CS_CUR:
+                       /* invalidate cache, so the value is read from the device */
+                       if (channel)
+                               info->cached &= ~(1 << channel);
+                       else /* master channel */
+                               info->cached = 0;
+
+                       snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+                                       info->elem_id);
+                       break;
+
+               case UAC2_CS_RANGE:
+                       /* TODO */
+                       break;
+
+               case UAC2_CS_MEM:
+                       /* TODO */
+                       break;
+
+               default:
+                       snd_printk(KERN_DEBUG "unknown attribute %d in interrupt\n",
+                                               attribute);
+                       break;
+               } /* switch */
+       }
+}
+
+static void snd_usb_mixer_interrupt(struct urb *urb)
 {
        struct usb_mixer_interface *mixer = urb->context;
+       int len = urb->actual_length;
+
+       if (urb->status != 0)
+               goto requeue;
 
-       if (urb->status == 0) {
-               u8 *buf = urb->transfer_buffer;
-               int i;
+       if (mixer->protocol == UAC_VERSION_1) {
+               struct uac1_status_word *status;
 
-               for (i = urb->actual_length; i >= 2; buf += 2, i -= 2) {
+               for (status = urb->transfer_buffer;
+                    len >= sizeof(*status);
+                    len -= sizeof(*status), status++) {
                        snd_printd(KERN_DEBUG "status interrupt: %02x %02x\n",
-                                  buf[0], buf[1]);
+                                               status->bStatusType,
+                                               status->bOriginator);
+
                        /* ignore any notifications not from the control interface */
-                       if ((buf[0] & 0x0f) != 0)
+                       if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) !=
+                               UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF)
                                continue;
-                       if (!(buf[0] & 0x40))
-                               snd_usb_mixer_notify_id(mixer, buf[1]);
+
+                       if (status->bStatusType & UAC1_STATUS_TYPE_MEM_CHANGED)
+                               snd_usb_mixer_rc_memory_change(mixer, status->bOriginator);
                        else
-                               snd_usb_mixer_rc_memory_change(mixer, buf[1]);
+                               snd_usb_mixer_notify_id(mixer, status->bOriginator);
+               }
+       } else { /* UAC_VERSION_2 */
+               struct uac2_interrupt_data_msg *msg;
+
+               for (msg = urb->transfer_buffer;
+                    len >= sizeof(*msg);
+                    len -= sizeof(*msg), msg++) {
+                       /* drop vendor specific and endpoint requests */
+                       if ((msg->bInfo & UAC2_INTERRUPT_DATA_MSG_VENDOR) ||
+                           (msg->bInfo & UAC2_INTERRUPT_DATA_MSG_EP))
+                               continue;
+
+                       snd_usb_mixer_interrupt_v2(mixer, msg->bAttribute,
+                                                  le16_to_cpu(msg->wValue),
+                                                  le16_to_cpu(msg->wIndex));
                }
        }
+
+requeue:
        if (urb->status != -ENOENT && urb->status != -ECONNRESET) {
                urb->dev = mixer->chip->dev;
                usb_submit_urb(urb, GFP_ATOMIC);
@@ -2023,7 +2084,7 @@ static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer)
        usb_fill_int_urb(mixer->urb, mixer->chip->dev,
                         usb_rcvintpipe(mixer->chip->dev, epnum),
                         transfer_buffer, buffer_length,
-                        snd_usb_mixer_status_complete, mixer, ep->bInterval);
+                        snd_usb_mixer_interrupt, mixer, ep->bInterval);
        usb_submit_urb(mixer->urb, GFP_KERNEL);
        return 0;
 }
@@ -2055,7 +2116,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
        }
 
        host_iface = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0];
-       mixer->protocol = host_iface->desc.bInterfaceProtocol;
+       mixer->protocol = get_iface_desc(host_iface)->bInterfaceProtocol;
 
        if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
            (err = snd_usb_mixer_status_create(mixer)) < 0)