sound: usb-audio: allow switching altsetting on Roland USB MIDI devices
Clemens Ladisch [Thu, 22 Oct 2009 07:06:19 +0000 (09:06 +0200)]
Add a mixer control to select between the two altsettings on Roland USB
MIDI devices where the input endpoint is either bulk or interrupt.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

sound/usb/usbmidi.c

index e5b0689..80b2845 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * usbmidi.c - ALSA USB MIDI driver
  *
- * Copyright (c) 2002-2007 Clemens Ladisch
+ * Copyright (c) 2002-2009 Clemens Ladisch
  * All rights reserved.
  *
  * Based on the OSS usb-midi driver by NAGANO Daisuke,
@@ -47,6 +47,7 @@
 #include <linux/usb.h>
 #include <linux/wait.h>
 #include <sound/core.h>
+#include <sound/control.h>
 #include <sound/rawmidi.h>
 #include <sound/asequencer.h>
 #include "usbaudio.h"
@@ -109,13 +110,17 @@ struct snd_usb_midi {
        struct list_head list;
        struct timer_list error_timer;
        spinlock_t disc_lock;
+       struct mutex mutex;
 
        struct snd_usb_midi_endpoint {
                struct snd_usb_midi_out_endpoint *out;
                struct snd_usb_midi_in_endpoint *in;
        } endpoints[MIDI_MAX_ENDPOINTS];
        unsigned long input_triggered;
+       unsigned int opened;
        unsigned char disconnected;
+
+       struct snd_kcontrol *roland_load_ctl;
 };
 
 struct snd_usb_midi_out_endpoint {
@@ -879,6 +884,50 @@ static struct usb_protocol_ops snd_usbmidi_emagic_ops = {
 };
 
 
+static void update_roland_altsetting(struct snd_usb_midi* umidi)
+{
+       struct usb_interface *intf;
+       struct usb_host_interface *hostif;
+       struct usb_interface_descriptor *intfd;
+       int is_light_load;
+
+       intf = umidi->iface;
+       is_light_load = intf->cur_altsetting != intf->altsetting;
+       if (umidi->roland_load_ctl->private_value == is_light_load)
+               return;
+       hostif = &intf->altsetting[umidi->roland_load_ctl->private_value];
+       intfd = get_iface_desc(hostif);
+       snd_usbmidi_input_stop(&umidi->list);
+       usb_set_interface(umidi->chip->dev, intfd->bInterfaceNumber,
+                         intfd->bAlternateSetting);
+       snd_usbmidi_input_start(&umidi->list);
+}
+
+static void substream_open(struct snd_rawmidi_substream *substream, int open)
+{
+       struct snd_usb_midi* umidi = substream->rmidi->private_data;
+       struct snd_kcontrol *ctl;
+
+       mutex_lock(&umidi->mutex);
+       if (open) {
+               if (umidi->opened++ == 0 && umidi->roland_load_ctl) {
+                       ctl = umidi->roland_load_ctl;
+                       ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+                       snd_ctl_notify(umidi->chip->card,
+                                      SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+                       update_roland_altsetting(umidi);
+               }
+       } else {
+               if (--umidi->opened == 0 && umidi->roland_load_ctl) {
+                       ctl = umidi->roland_load_ctl;
+                       ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+                       snd_ctl_notify(umidi->chip->card,
+                                      SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+               }
+       }
+       mutex_unlock(&umidi->mutex);
+}
+
 static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
 {
        struct snd_usb_midi* umidi = substream->rmidi->private_data;
@@ -898,11 +947,13 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
        }
        substream->runtime->private_data = port;
        port->state = STATE_UNKNOWN;
+       substream_open(substream, 1);
        return 0;
 }
 
 static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream)
 {
+       substream_open(substream, 0);
        return 0;
 }
 
@@ -954,11 +1005,13 @@ static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream)
 
 static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream)
 {
+       substream_open(substream, 1);
        return 0;
 }
 
 static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream)
 {
+       substream_open(substream, 0);
        return 0;
 }
 
@@ -1163,6 +1216,7 @@ static void snd_usbmidi_free(struct snd_usb_midi* umidi)
                if (ep->in)
                        snd_usbmidi_in_endpoint_delete(ep->in);
        }
+       mutex_destroy(&umidi->mutex);
        kfree(umidi);
 }
 
@@ -1524,6 +1578,52 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi* umidi,
        return 0;
 }
 
+static int roland_load_info(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_info *info)
+{
+       static const char *const names[] = { "High Load", "Light Load" };
+
+       info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       info->count = 1;
+       info->value.enumerated.items = 2;
+       if (info->value.enumerated.item > 1)
+               info->value.enumerated.item = 1;
+       strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+       return 0;
+}
+
+static int roland_load_get(struct snd_kcontrol *kcontrol,
+                          struct snd_ctl_elem_value *value)
+{
+       value->value.enumerated.item[0] = kcontrol->private_value;
+       return 0;
+}
+
+static int roland_load_put(struct snd_kcontrol *kcontrol,
+                          struct snd_ctl_elem_value *value)
+{
+       struct snd_usb_midi* umidi = kcontrol->private_data;
+       int changed;
+
+       if (value->value.enumerated.item[0] > 1)
+               return -EINVAL;
+       mutex_lock(&umidi->mutex);
+       changed = value->value.enumerated.item[0] != kcontrol->private_value;
+       if (changed)
+               kcontrol->private_value = value->value.enumerated.item[0];
+       mutex_unlock(&umidi->mutex);
+       return changed;
+}
+
+static struct snd_kcontrol_new roland_load_ctl = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "MIDI Input Mode",
+       .info = roland_load_info,
+       .get = roland_load_get,
+       .put = roland_load_put,
+       .private_value = 1,
+};
+
 /*
  * On Roland devices, use the second alternate setting to be able to use
  * the interrupt input endpoint.
@@ -1549,6 +1649,10 @@ static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi* umidi)
                    intfd->bAlternateSetting);
        usb_set_interface(umidi->chip->dev, intfd->bInterfaceNumber,
                          intfd->bAlternateSetting);
+
+       umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi);
+       if (snd_ctl_add(umidi->chip->card, umidi->roland_load_ctl) < 0)
+               umidi->roland_load_ctl = NULL;
 }
 
 /*
@@ -1834,6 +1938,7 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip,
        umidi->usb_protocol_ops = &snd_usbmidi_standard_ops;
        init_timer(&umidi->error_timer);
        spin_lock_init(&umidi->disc_lock);
+       mutex_init(&umidi->mutex);
        umidi->error_timer.function = snd_usbmidi_error_timer;
        umidi->error_timer.data = (unsigned long)umidi;