ALSA: usb-audio: Avoid unnecessary EP setups in prepare
[firefly-linux-kernel-4.4.55.git] / sound / usb / pcm.c
index a1298f379428280045bf661f89e5fa59bb0acb48..55e19e1b80ec4eeb282c91cea482ef7434cf8dce 100644 (file)
@@ -82,8 +82,7 @@ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream
 /*
  * find a matching audio format
  */
-static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned int format,
-                                      unsigned int rate, unsigned int channels)
+static struct audioformat *find_format(struct snd_usb_substream *subs)
 {
        struct list_head *p;
        struct audioformat *found = NULL;
@@ -92,16 +91,17 @@ static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned
        list_for_each(p, &subs->fmt_list) {
                struct audioformat *fp;
                fp = list_entry(p, struct audioformat, list);
-               if (!(fp->formats & (1uLL << format)))
+               if (!(fp->formats & (1uLL << subs->pcm_format)))
                        continue;
-               if (fp->channels != channels)
+               if (fp->channels != subs->channels)
                        continue;
-               if (rate < fp->rate_min || rate > fp->rate_max)
+               if (subs->cur_rate < fp->rate_min ||
+                   subs->cur_rate > fp->rate_max)
                        continue;
                if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) {
                        unsigned int i;
                        for (i = 0; i < fp->nr_rates; i++)
-                               if (fp->rate_table[i] == rate)
+                               if (fp->rate_table[i] == subs->cur_rate)
                                        break;
                        if (i >= fp->nr_rates)
                                continue;
@@ -212,7 +212,7 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
        }
 }
 
-static int start_endpoints(struct snd_usb_substream *subs)
+static int start_endpoints(struct snd_usb_substream *subs, int can_sleep)
 {
        int err;
 
@@ -225,7 +225,7 @@ static int start_endpoints(struct snd_usb_substream *subs)
                snd_printdd(KERN_DEBUG "Starting data EP @%p\n", ep);
 
                ep->data_subs = subs;
-               err = snd_usb_endpoint_start(ep);
+               err = snd_usb_endpoint_start(ep, can_sleep);
                if (err < 0) {
                        clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags);
                        return err;
@@ -236,10 +236,25 @@ static int start_endpoints(struct snd_usb_substream *subs)
            !test_and_set_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {
                struct snd_usb_endpoint *ep = subs->sync_endpoint;
 
+               if (subs->data_endpoint->iface != subs->sync_endpoint->iface ||
+                   subs->data_endpoint->alt_idx != subs->sync_endpoint->alt_idx) {
+                       err = usb_set_interface(subs->dev,
+                                               subs->sync_endpoint->iface,
+                                               subs->sync_endpoint->alt_idx);
+                       if (err < 0) {
+                               snd_printk(KERN_ERR
+                                          "%d:%d:%d: cannot set interface (%d)\n",
+                                          subs->dev->devnum,
+                                          subs->sync_endpoint->iface,
+                                          subs->sync_endpoint->alt_idx, err);
+                               return -EIO;
+                       }
+               }
+
                snd_printdd(KERN_DEBUG "Starting sync EP @%p\n", ep);
 
                ep->sync_slave = subs->data_endpoint;
-               err = snd_usb_endpoint_start(ep);
+               err = snd_usb_endpoint_start(ep, can_sleep);
                if (err < 0) {
                        clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);
                        return err;
@@ -420,6 +435,42 @@ add_sync_ep:
        return 0;
 }
 
+/*
+ * configure endpoint params
+ *
+ * called  during initial setup and upon resume
+ */
+static int configure_endpoint(struct snd_usb_substream *subs)
+{
+       int ret;
+
+       mutex_lock(&subs->stream->chip->shutdown_mutex);
+       /* format changed */
+       stop_endpoints(subs, 0, 0, 0);
+       ret = snd_usb_endpoint_set_params(subs->data_endpoint,
+                                         subs->pcm_format,
+                                         subs->channels,
+                                         subs->period_bytes,
+                                         subs->cur_rate,
+                                         subs->cur_audiofmt,
+                                         subs->sync_endpoint);
+       if (ret < 0)
+               goto unlock;
+
+       if (subs->sync_endpoint)
+               ret = snd_usb_endpoint_set_params(subs->data_endpoint,
+                                                 subs->pcm_format,
+                                                 subs->channels,
+                                                 subs->period_bytes,
+                                                 subs->cur_rate,
+                                                 subs->cur_audiofmt,
+                                                 NULL);
+
+unlock:
+       mutex_unlock(&subs->stream->chip->shutdown_mutex);
+       return ret;
+}
+
 /*
  * hw_params callback
  *
@@ -435,63 +486,33 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
 {
        struct snd_usb_substream *subs = substream->runtime->private_data;
        struct audioformat *fmt;
-       unsigned int channels, rate, format;
-       int ret, changed;
+       int ret;
 
        ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
                                               params_buffer_bytes(hw_params));
        if (ret < 0)
                return ret;
 
-       format = params_format(hw_params);
-       rate = params_rate(hw_params);
-       channels = params_channels(hw_params);
-       fmt = find_format(subs, format, rate, channels);
+       subs->pcm_format = params_format(hw_params);
+       subs->period_bytes = params_period_bytes(hw_params);
+       subs->channels = params_channels(hw_params);
+       subs->cur_rate = params_rate(hw_params);
+
+       fmt = find_format(subs);
        if (!fmt) {
                snd_printd(KERN_DEBUG "cannot set format: format = %#x, rate = %d, channels = %d\n",
-                          format, rate, channels);
+                          subs->pcm_format, subs->cur_rate, subs->channels);
                return -EINVAL;
        }
 
-       changed = subs->cur_audiofmt != fmt ||
-               subs->period_bytes != params_period_bytes(hw_params) ||
-               subs->cur_rate != rate;
        if ((ret = set_format(subs, fmt)) < 0)
                return ret;
 
-       if (subs->cur_rate != rate) {
-               struct usb_host_interface *alts;
-               struct usb_interface *iface;
-               iface = usb_ifnum_to_if(subs->dev, fmt->iface);
-               alts = &iface->altsetting[fmt->altset_idx];
-               ret = snd_usb_init_sample_rate(subs->stream->chip, fmt->iface, alts, fmt, rate);
-               if (ret < 0)
-                       return ret;
-               subs->cur_rate = rate;
-       }
-
-       if (changed) {
-               mutex_lock(&subs->stream->chip->shutdown_mutex);
-               /* format changed */
-               stop_endpoints(subs, 0, 0, 0);
-               ret = snd_usb_endpoint_set_params(subs->data_endpoint, hw_params, fmt,
-                                                 subs->sync_endpoint);
-               if (ret < 0)
-                       goto unlock;
+       subs->interface = fmt->iface;
+       subs->altset_idx = fmt->altset_idx;
+       subs->need_setup_ep = true;
 
-               if (subs->sync_endpoint)
-                       ret = snd_usb_endpoint_set_params(subs->sync_endpoint,
-                                                         hw_params, fmt, NULL);
-unlock:
-               mutex_unlock(&subs->stream->chip->shutdown_mutex);
-       }
-
-       if (ret == 0) {
-               subs->interface = fmt->iface;
-               subs->altset_idx = fmt->altset_idx;
-       }
-
-       return ret;
+       return 0;
 }
 
 /*
@@ -522,6 +543,9 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct snd_usb_substream *subs = runtime->private_data;
+       struct usb_host_interface *alts;
+       struct usb_interface *iface;
+       int ret;
 
        if (! subs->cur_audiofmt) {
                snd_printk(KERN_ERR "usbaudio: no format is specified!\n");
@@ -531,6 +555,27 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
        if (snd_BUG_ON(!subs->data_endpoint))
                return -EIO;
 
+       ret = set_format(subs, subs->cur_audiofmt);
+       if (ret < 0)
+               return ret;
+
+       iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface);
+       alts = &iface->altsetting[subs->cur_audiofmt->altset_idx];
+       ret = snd_usb_init_sample_rate(subs->stream->chip,
+                                      subs->cur_audiofmt->iface,
+                                      alts,
+                                      subs->cur_audiofmt,
+                                      subs->cur_rate);
+       if (ret < 0)
+               return ret;
+
+       if (subs->need_setup_ep) {
+               ret = configure_endpoint(subs);
+               if (ret < 0)
+                       return ret;
+               subs->need_setup_ep = false;
+       }
+
        /* some unit conversions in runtime */
        subs->data_endpoint->maxframesize =
                bytes_to_frames(runtime, subs->data_endpoint->maxpacksize);
@@ -547,7 +592,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
        /* 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)
-               return start_endpoints(subs);
+               return start_endpoints(subs, 1);
 
        return 0;
 }
@@ -1029,6 +1074,7 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
                                 struct urb *urb)
 {
        struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+       struct snd_usb_endpoint *ep = subs->data_endpoint;
        struct snd_urb_ctx *ctx = urb->context;
        unsigned int counts, frames, bytes;
        int i, stride, period_elapsed = 0;
@@ -1040,7 +1086,11 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
        urb->number_of_packets = 0;
        spin_lock_irqsave(&subs->lock, flags);
        for (i = 0; i < ctx->packets; i++) {
-               counts = ctx->packet_size[i];
+               if (ctx->packet_size[i])
+                       counts = ctx->packet_size[i];
+               else
+                       counts = snd_usb_endpoint_next_packet_size(ep);
+
                /* set up descriptor */
                urb->iso_frame_desc[i].offset = frames * stride;
                urb->iso_frame_desc[i].length = counts * stride;
@@ -1091,7 +1141,16 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
        subs->hwptr_done += bytes;
        if (subs->hwptr_done >= runtime->buffer_size * stride)
                subs->hwptr_done -= runtime->buffer_size * stride;
+
+       /* update delay with exact number of samples queued */
+       runtime->delay = subs->last_delay;
        runtime->delay += frames;
+       subs->last_delay = runtime->delay;
+
+       /* realign last_frame_number */
+       subs->last_frame_number = usb_get_current_frame_number(subs->dev);
+       subs->last_frame_number &= 0xFF; /* keep 8 LSBs */
+
        spin_unlock_irqrestore(&subs->lock, flags);
        urb->transfer_buffer_length = bytes;
        if (period_elapsed)
@@ -1109,12 +1168,32 @@ static void retire_playback_urb(struct snd_usb_substream *subs,
        struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
        int stride = runtime->frame_bits >> 3;
        int processed = urb->transfer_buffer_length / stride;
+       int est_delay;
+
+       /* ignore the delay accounting when procssed=0 is given, i.e.
+        * silent payloads are procssed before handling the actual data
+        */
+       if (!processed)
+               return;
 
        spin_lock_irqsave(&subs->lock, flags);
-       if (processed > runtime->delay)
-               runtime->delay = 0;
+       est_delay = snd_usb_pcm_delay(subs, runtime->rate);
+       /* update delay with exact number of samples played */
+       if (processed > subs->last_delay)
+               subs->last_delay = 0;
        else
-               runtime->delay -= processed;
+               subs->last_delay -= processed;
+       runtime->delay = subs->last_delay;
+
+       /*
+        * Report when delay estimate is off by more than 2ms.
+        * The error should be lower than 2ms since the estimate relies
+        * on two reads of a counter updated every ms.
+        */
+       if (abs(est_delay - subs->last_delay) * 1000 > runtime->rate * 2)
+               snd_printk(KERN_DEBUG "delay: estimated %d, actual %d\n",
+                       est_delay, subs->last_delay);
+
        spin_unlock_irqrestore(&subs->lock, flags);
 }
 
@@ -1172,7 +1251,7 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream
 
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
-               err = start_endpoints(subs);
+               err = start_endpoints(subs, 0);
                if (err < 0)
                        return err;