[ALSA] usb-audio: start submitting URBs in the prepared state
[linux-2.6.git] / sound / usb / usbaudio.c
index 8818918c4237039a6aa6eaa1d1017816394d5397..99dae024b640bfd9f3ddadbf4de227cd5daca95c 100644 (file)
@@ -41,7 +41,6 @@
 #include <sound/driver.h>
 #include <linux/bitops.h>
 #include <linux/init.h>
-#include <linux/interrupt.h>
 #include <linux/list.h>
 #include <linux/slab.h>
 #include <linux/string.h>
@@ -185,7 +184,6 @@ struct snd_usb_substream {
        unsigned int num_formats;               /* number of supported audio formats (list) */
        struct list_head fmt_list;      /* format list */
        spinlock_t lock;
-       struct tasklet_struct start_period_elapsed;     /* for start trigger */
 
        struct snd_urb_ops ops;         /* callbacks (must be filled at init) */
 };
@@ -479,6 +477,28 @@ static int retire_playback_sync_urb_hs(snd_usb_substream_t *subs,
        return 0;
 }
 
+/*
+ * Prepare urb for streaming before playback starts.
+ *
+ * We don't care about (or have) any data, so we just send a transfer delimiter.
+ */
+static int prepare_startup_playback_urb(snd_usb_substream_t *subs,
+                                       snd_pcm_runtime_t *runtime,
+                                       struct urb *urb)
+{
+       unsigned int i;
+       snd_urb_ctx_t *ctx = urb->context;
+
+       urb->dev = ctx->subs->dev;
+       urb->number_of_packets = subs->packs_per_ms;
+       for (i = 0; i < subs->packs_per_ms; ++i) {
+               urb->iso_frame_desc[i].offset = 0;
+               urb->iso_frame_desc[i].length = 0;
+       }
+       urb->transfer_buffer_length = 0;
+       return 0;
+}
+
 /*
  * prepare urb for playback data pipe
  *
@@ -568,12 +588,8 @@ static int prepare_playback_urb(snd_usb_substream_t *subs,
                subs->hwptr_done -= runtime->buffer_size;
        spin_unlock_irqrestore(&subs->lock, flags);
        urb->transfer_buffer_length = offs * stride;
-       if (period_elapsed) {
-               if (likely(subs->running))
-                       snd_pcm_period_elapsed(subs->pcm_substream);
-               else
-                       tasklet_hi_schedule(&subs->start_period_elapsed);
-       }
+       if (period_elapsed)
+               snd_pcm_period_elapsed(subs->pcm_substream);
        return 0;
 }
 
@@ -588,22 +604,12 @@ static int retire_playback_urb(snd_usb_substream_t *subs,
        return 0;
 }
 
-/*
- * Delay the snd_pcm_period_elapsed() call until after the start trigger
- * callback so that we're not longer in the substream's lock.
- */
-static void start_period_elapsed(unsigned long data)
-{
-       snd_usb_substream_t *subs = (snd_usb_substream_t *)data;
-       snd_pcm_period_elapsed(subs->pcm_substream);
-}
-
 
 /*
  */
 static struct snd_urb_ops audio_urb_ops[2] = {
        {
-               .prepare =      prepare_playback_urb,
+               .prepare =      prepare_startup_playback_urb,
                .retire =       retire_playback_urb,
                .prepare_sync = prepare_playback_sync_urb,
                .retire_sync =  retire_playback_sync_urb,
@@ -618,7 +624,7 @@ static struct snd_urb_ops audio_urb_ops[2] = {
 
 static struct snd_urb_ops audio_urb_ops_high_speed[2] = {
        {
-               .prepare =      prepare_playback_urb,
+               .prepare =      prepare_startup_playback_urb,
                .retire =       retire_playback_urb,
                .prepare_sync = prepare_playback_sync_urb_hs,
                .retire_sync =  retire_playback_sync_urb_hs,
@@ -863,25 +869,40 @@ static snd_pcm_uframes_t snd_usb_pcm_pointer(snd_pcm_substream_t *substream)
 
 
 /*
- * start/stop substream
+ * start/stop playback substream
  */
-static int snd_usb_pcm_trigger(snd_pcm_substream_t *substream, int cmd)
+static int snd_usb_pcm_playback_trigger(snd_pcm_substream_t *substream,
+                                       int cmd)
 {
-       snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data;
-       int err;
+       snd_usb_substream_t *subs = substream->runtime->private_data;
 
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
-               err = start_urbs(subs, substream->runtime);
-               break;
+               subs->ops.prepare = prepare_playback_urb;
+               return 0;
        case SNDRV_PCM_TRIGGER_STOP:
-               err = deactivate_urbs(subs, 0, 0);
-               break;
+               return deactivate_urbs(subs, 0, 0);
        default:
-               err = -EINVAL;
-               break;
+               return -EINVAL;
+       }
+}
+
+/*
+ * start/stop capture substream
+ */
+static int snd_usb_pcm_capture_trigger(snd_pcm_substream_t *substream,
+                                      int cmd)
+{
+       snd_usb_substream_t *subs = substream->runtime->private_data;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               return start_urbs(subs, substream->runtime);
+       case SNDRV_PCM_TRIGGER_STOP:
+               return deactivate_urbs(subs, 0, 0);
+       default:
+               return -EINVAL;
        }
-       return err < 0 ? err : 0;
 }
 
 
@@ -1413,7 +1434,7 @@ static int snd_usb_hw_free(snd_pcm_substream_t *substream)
 static int snd_usb_pcm_prepare(snd_pcm_substream_t *substream)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
-       snd_usb_substream_t *subs = (snd_usb_substream_t *)runtime->private_data;
+       snd_usb_substream_t *subs = runtime->private_data;
 
        if (! subs->cur_audiofmt) {
                snd_printk(KERN_ERR "usbaudio: no format is specified!\n");
@@ -1433,7 +1454,13 @@ static int snd_usb_pcm_prepare(snd_pcm_substream_t *substream)
        deactivate_urbs(subs, 0, 1);
        wait_clear_urbs(subs);
 
-       return 0;
+       /* for playback, submit the URBs now; otherwise, the first hwptr_done
+        * updates for all URBs would happen at the same time when starting */
+       if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+               subs->ops.prepare = prepare_startup_playback_urb;
+               return start_urbs(subs, runtime);
+       } else
+               return 0;
 }
 
 static snd_pcm_hardware_t snd_usb_playback =
@@ -1847,7 +1874,7 @@ static snd_pcm_ops_t snd_usb_playback_ops = {
        .hw_params =    snd_usb_hw_params,
        .hw_free =      snd_usb_hw_free,
        .prepare =      snd_usb_pcm_prepare,
-       .trigger =      snd_usb_pcm_trigger,
+       .trigger =      snd_usb_pcm_playback_trigger,
        .pointer =      snd_usb_pcm_pointer,
        .page =         snd_pcm_get_vmalloc_page,
 };
@@ -1859,7 +1886,7 @@ static snd_pcm_ops_t snd_usb_capture_ops = {
        .hw_params =    snd_usb_hw_params,
        .hw_free =      snd_usb_hw_free,
        .prepare =      snd_usb_pcm_prepare,
-       .trigger =      snd_usb_pcm_trigger,
+       .trigger =      snd_usb_pcm_capture_trigger,
        .pointer =      snd_usb_pcm_pointer,
        .page =         snd_pcm_get_vmalloc_page,
 };
@@ -2078,9 +2105,6 @@ static void init_substream(snd_usb_stream_t *as, int stream, struct audioformat
 
        INIT_LIST_HEAD(&subs->fmt_list);
        spin_lock_init(&subs->lock);
-       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
-               tasklet_init(&subs->start_period_elapsed, start_period_elapsed,
-                            (unsigned long)subs);
 
        subs->stream = as;
        subs->direction = stream;