usb: gadget: f_uac2: send reasonably sized packets
authorDaniel Mack <zonque@gmail.com>
Wed, 27 Aug 2014 17:09:07 +0000 (19:09 +0200)
committerFelipe Balbi <balbi@ti.com>
Tue, 2 Sep 2014 14:28:00 +0000 (09:28 -0500)
The UAC2 function driver currently responds to all packets at all times
with wMaxPacketSize packets. That results in way too fast audio
playback as the function driver (which is in fact supposed to define
the audio stream pace) delivers as fast as it can.

Fix this by sizing each packet correctly with the following steps:

 a) Set the packet's size by dividing the nominal data rate by the
    playback endpoint's interval.

 b) If there is a residual value from the calculation in a), add
    it to a accumulator to keep track of it across packets.

 c) If the accumulator has gathered at least the number of bytes
    that are needed for one sample frame, increase the packet size.

This way, the packet size calculation will get rid of any kind of
imprecision that would otherwise occur with a simple division over
time.

Some of the variables that are needed while processing each packet
are pre-computed for performance reasons.

Signed-off-by: Daniel Mack <zonque@gmail.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/gadget/function/f_uac2.c

index 246a7784e0121180fc772524baec9b3f78d398c7..a5a27a504d67099e09670cd7ca9e48355c1c4f70 100644 (file)
@@ -92,6 +92,15 @@ struct snd_uac2_chip {
 
        struct snd_card *card;
        struct snd_pcm *pcm;
+
+       /* timekeeping for the playback endpoint */
+       unsigned int p_interval;
+       unsigned int p_residue;
+
+       /* pre-calculated values for playback iso completion */
+       unsigned int p_pktsize;
+       unsigned int p_pktsize_residue;
+       unsigned int p_framesize;
 };
 
 #define BUFF_SIZE_MAX  (PAGE_SIZE * 16)
@@ -191,8 +200,29 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
 
        spin_lock_irqsave(&prm->lock, flags);
 
-       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               /*
+                * For each IN packet, take the quotient of the current data
+                * rate and the endpoint's interval as the base packet size.
+                * If there is a residue from this division, add it to the
+                * residue accumulator.
+                */
+               req->length = uac2->p_pktsize;
+               uac2->p_residue += uac2->p_pktsize_residue;
+
+               /*
+                * Whenever there are more bytes in the accumulator than we
+                * need to add one more sample frame, increase this packet's
+                * size and decrease the accumulator.
+                */
+               if (uac2->p_residue / uac2->p_interval >= uac2->p_framesize) {
+                       req->length += uac2->p_framesize;
+                       uac2->p_residue -= uac2->p_framesize *
+                                          uac2->p_interval;
+               }
+
                req->actual = req->length;
+       }
 
        pending = prm->hw_ptr % prm->period_size;
        pending += req->actual;
@@ -346,6 +376,7 @@ static int uac2_pcm_open(struct snd_pcm_substream *substream)
        c_srate = opts->c_srate;
        p_chmask = opts->p_chmask;
        c_chmask = opts->c_chmask;
+       uac2->p_residue = 0;
 
        runtime->hw = uac2_pcm_hardware;
 
@@ -1077,7 +1108,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
        struct usb_request *req;
        struct usb_ep *ep;
        struct uac2_rtd_params *prm;
-       int i;
+       int req_len, i;
 
        /* No i/f has more than 2 alt settings */
        if (alt > 1) {
@@ -1099,11 +1130,41 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
                prm = &uac2->c_prm;
                config_ep_by_speed(gadget, fn, ep);
                agdev->as_out_alt = alt;
+               req_len = prm->max_psize;
        } else if (intf == agdev->as_in_intf) {
+               struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev);
+               unsigned int factor, rate;
+               struct usb_endpoint_descriptor *ep_desc;
+
                ep = agdev->in_ep;
                prm = &uac2->p_prm;
                config_ep_by_speed(gadget, fn, ep);
                agdev->as_in_alt = alt;
+
+               /* pre-calculate the playback endpoint's interval */
+               if (gadget->speed == USB_SPEED_FULL) {
+                       ep_desc = &fs_epin_desc;
+                       factor = 1000;
+               } else {
+                       ep_desc = &hs_epin_desc;
+                       factor = 125;
+               }
+
+               /* pre-compute some values for iso_complete() */
+               uac2->p_framesize = opts->p_ssize *
+                                   num_channels(opts->p_chmask);
+               rate = opts->p_srate * uac2->p_framesize;
+               uac2->p_interval = (1 << (ep_desc->bInterval - 1)) * factor;
+               uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval,
+                                       prm->max_psize);
+
+               if (uac2->p_pktsize < prm->max_psize)
+                       uac2->p_pktsize_residue = rate % uac2->p_interval;
+               else
+                       uac2->p_pktsize_residue = 0;
+
+               req_len = uac2->p_pktsize;
+               uac2->p_residue = 0;
        } else {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                return -EINVAL;
@@ -1128,9 +1189,9 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 
                        req->zero = 0;
                        req->context = &prm->ureq[i];
-                       req->length = prm->max_psize;
+                       req->length = req_len;
                        req->complete = agdev_iso_complete;
-                       req->buf = prm->rbuf + i * req->length;
+                       req->buf = prm->rbuf + i * prm->max_psize;
                }
 
                if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))