]> Pileus Git - ~andy/linux/blobdiff - sound/usb/card.c
ALSA: usbaudio: implement USB autosuspend
[~andy/linux] / sound / usb / card.c
index 4eabafa5b037db66b250db64cc0ce05aa783f595..40722f8711adc82001001d4fa3f69b8c35b34253 100644 (file)
@@ -65,6 +65,7 @@
 #include "pcm.h"
 #include "urb.h"
 #include "format.h"
+#include "power.h"
 
 MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
 MODULE_DESCRIPTION("USB Audio");
@@ -300,9 +301,13 @@ static int snd_usb_audio_create(struct usb_device *dev, int idx,
 
        *rchip = NULL;
 
-       if (snd_usb_get_speed(dev) != USB_SPEED_LOW &&
-           snd_usb_get_speed(dev) != USB_SPEED_FULL &&
-           snd_usb_get_speed(dev) != USB_SPEED_HIGH) {
+       switch (snd_usb_get_speed(dev)) {
+       case USB_SPEED_LOW:
+       case USB_SPEED_FULL:
+       case USB_SPEED_HIGH:
+       case USB_SPEED_SUPER:
+               break;
+       default:
                snd_printk(KERN_ERR "unknown device speed %d\n", snd_usb_get_speed(dev));
                return -ENXIO;
        }
@@ -319,12 +324,14 @@ static int snd_usb_audio_create(struct usb_device *dev, int idx,
                return -ENOMEM;
        }
 
+       mutex_init(&chip->shutdown_mutex);
        chip->index = idx;
        chip->dev = dev;
        chip->card = card;
        chip->setup = device_setup[idx];
        chip->nrpacks = nrpacks;
        chip->async_unlink = async_unlink;
+       chip->probing = 1;
 
        chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
                              le16_to_cpu(dev->descriptor.idProduct));
@@ -378,11 +385,22 @@ static int snd_usb_audio_create(struct usb_device *dev, int idx,
        if (len < sizeof(card->longname))
                usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
 
-       strlcat(card->longname,
-               snd_usb_get_speed(dev) == USB_SPEED_LOW ? ", low speed" :
-               snd_usb_get_speed(dev) == USB_SPEED_FULL ? ", full speed" :
-               ", high speed",
-               sizeof(card->longname));
+       switch (snd_usb_get_speed(dev)) {
+       case USB_SPEED_LOW:
+               strlcat(card->longname, ", low speed", sizeof(card->longname));
+               break;
+       case USB_SPEED_FULL:
+               strlcat(card->longname, ", full speed", sizeof(card->longname));
+               break;
+       case USB_SPEED_HIGH:
+               strlcat(card->longname, ", high speed", sizeof(card->longname));
+               break;
+       case USB_SPEED_SUPER:
+               strlcat(card->longname, ", super speed", sizeof(card->longname));
+               break;
+       default:
+               break;
+       }
 
        snd_usb_audio_create_proc(chip);
 
@@ -435,6 +453,7 @@ static void *snd_usb_audio_probe(struct usb_device *dev,
                                goto __error;
                        }
                        chip = usb_chip[i];
+                       chip->probing = 1;
                        break;
                }
        }
@@ -450,6 +469,7 @@ static void *snd_usb_audio_probe(struct usb_device *dev,
                                        goto __error;
                                }
                                snd_card_set_dev(chip->card, &intf->dev);
+                               chip->pm_intf = intf;
                                break;
                        }
                if (!chip) {
@@ -489,6 +509,7 @@ static void *snd_usb_audio_probe(struct usb_device *dev,
 
        usb_chip[chip->index] = chip;
        chip->num_interfaces++;
+       chip->probing = 0;
        mutex_unlock(&register_mutex);
        return chip;
 
@@ -516,6 +537,7 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr)
        chip = ptr;
        card = chip->card;
        mutex_lock(&register_mutex);
+       mutex_lock(&chip->shutdown_mutex);
        chip->shutdown = 1;
        chip->num_interfaces--;
        if (chip->num_interfaces <= 0) {
@@ -533,9 +555,11 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr)
                        snd_usb_mixer_disconnect(p);
                }
                usb_chip[chip->index] = NULL;
+               mutex_unlock(&chip->shutdown_mutex);
                mutex_unlock(&register_mutex);
                snd_card_free_when_closed(card);
        } else {
+               mutex_unlock(&chip->shutdown_mutex);
                mutex_unlock(&register_mutex);
        }
 }
@@ -562,29 +586,61 @@ static void usb_audio_disconnect(struct usb_interface *intf)
 }
 
 #ifdef CONFIG_PM
+
+int snd_usb_autoresume(struct snd_usb_audio *chip)
+{
+       int err = -ENODEV;
+
+       if (!chip->shutdown && !chip->probing)
+               err = usb_autopm_get_interface(chip->pm_intf);
+
+       return err;
+}
+
+void snd_usb_autosuspend(struct snd_usb_audio *chip)
+{
+       if (!chip->shutdown && !chip->probing)
+               usb_autopm_put_interface(chip->pm_intf);
+}
+
 static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
 {
        struct snd_usb_audio *chip = usb_get_intfdata(intf);
        struct list_head *p;
        struct snd_usb_stream *as;
+       struct usb_mixer_interface *mixer;
 
        if (chip == (void *)-1L)
                return 0;
 
-       snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
-       if (!chip->num_suspended_intf++) {
-               list_for_each(p, &chip->pcm_list) {
-                       as = list_entry(p, struct snd_usb_stream, list);
-                       snd_pcm_suspend_all(as->pcm);
-               }
+       if (!(message.event & PM_EVENT_AUTO)) {
+               snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
+               if (!chip->num_suspended_intf++) {
+                       list_for_each(p, &chip->pcm_list) {
+                               as = list_entry(p, struct snd_usb_stream, list);
+                               snd_pcm_suspend_all(as->pcm);
+                       }
+               }
+       } else {
+               /*
+                * otherwise we keep the rest of the system in the dark
+                * to keep this transparent
+                */
+               if (!chip->num_suspended_intf++)
+                       chip->autosuspended = 1;
        }
 
+       list_for_each_entry(mixer, &chip->mixer_list, list)
+               snd_usb_mixer_inactivate(mixer);
+
        return 0;
 }
 
 static int usb_audio_resume(struct usb_interface *intf)
 {
        struct snd_usb_audio *chip = usb_get_intfdata(intf);
+       struct usb_mixer_interface *mixer;
+       int err = 0;
 
        if (chip == (void *)-1L)
                return 0;
@@ -592,12 +648,20 @@ static int usb_audio_resume(struct usb_interface *intf)
                return 0;
        /*
         * ALSA leaves material resumption to user space
-        * we just notify
+        * we just notify and restart the mixers
         */
+       list_for_each_entry(mixer, &chip->mixer_list, list) {
+               err = snd_usb_mixer_activate(mixer);
+               if (err < 0)
+                       goto err_out;
+       }
 
-       snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+       if (!chip->autosuspended)
+               snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+       chip->autosuspended = 0;
 
-       return 0;
+err_out:
+       return err;
 }
 #else
 #define usb_audio_suspend      NULL
@@ -625,6 +689,7 @@ static struct usb_driver usb_audio_driver = {
        .suspend =      usb_audio_suspend,
        .resume =       usb_audio_resume,
        .id_table =     usb_audio_ids,
+       .supports_autosuspend = 1,
 };
 
 static int __init snd_usb_audio_init(void)