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. We need data rate to match, as accurately as possible, the sampling rate expressed by the UAC2 topology to the Host. We do this by sending packets of varying length (1 sample more than usual) in a pattern so that we get the desired data rate over a period of a second or sooner. The payload pattern is calculated only once (using "Alan's Algo"), when the Host enables the interface, and saved in an array so that the 2 ping-pong usb_requests directly index into the pattern array to figure out the payload length they are supposed to transfer next. Note that the increased overhead in agdev_iso_complete() is almost zero. Signed-off-by: Jassi Brar <jaswinder.singh@xxxxxxxxxx> --- Changes since v1: o Fix pattern[] to show payload size in bytes, rather than samples, by multiplying each element with frame-size. drivers/usb/gadget/function/f_uac2.c | 76 ++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 246a778..edc189e 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -59,8 +59,15 @@ const char *uac2_name = "snd_uac2"; struct uac2_req { struct uac2_rtd_params *pp; /* parent param */ struct usb_request *req; + unsigned idx; /* current element of length-pattern loop */ }; +/* + * 5512.5Hz is going to need the maximum number of elements (80), + * in the length-pattern loop, among standard ALSA supported rates. + */ +#define MAX_LOOP_LEN 80 + struct uac2_rtd_params { struct snd_uac2_chip *uac2; /* parent chip */ bool ep_enabled; /* if the ep is enabled */ @@ -80,6 +87,9 @@ struct uac2_rtd_params { unsigned max_psize; struct uac2_req ureq[USB_XFERS]; + unsigned pattern[MAX_LOOP_LEN]; + unsigned plen; /* total entries in pattern[] */ + spinlock_t lock; }; @@ -191,8 +201,12 @@ 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) { + /* Update length for next payload */ + ur->idx = (ur->idx + USB_XFERS) % prm->plen; + req->length = prm->pattern[ur->idx]; req->actual = req->length; + } pending = prm->hw_ptr % prm->period_size; pending += req->actual; @@ -1066,6 +1080,31 @@ err: return -EINVAL; } +/* + * Find optimal pattern of payloads for a given number + * of samples and maximum sync period (in ms) over which + * we have to distribute them uniformly. + */ +static unsigned +get_pattern(unsigned samples, unsigned sync, unsigned *pt) +{ + unsigned n, x = 0, i = 0, p = samples % sync; + + do { + x += p; + n = samples / sync; + if (x >= sync) { + n += 1; + x -= sync; + } + if (pt) + pt[i] = n; + i++; + } while (x); + + return i; +} + static int afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) { @@ -1097,11 +1136,41 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) if (intf == agdev->as_out_intf) { ep = agdev->out_ep; prm = &uac2->c_prm; + prm->plen = 1; + prm->pattern[0] = prm->max_psize; config_ep_by_speed(gadget, fn, ep); agdev->as_out_alt = alt; } else if (intf == agdev->as_in_intf) { + struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev); + unsigned intvl, rate, fsz; + + if (gadget->speed == USB_SPEED_FULL) + intvl = (1 << (fs_epin_desc.bInterval - 1)) * 1000; + else + intvl = (1 << (hs_epin_desc.bInterval - 1)) * 125; + + rate = opts->p_srate; + if (rate == 5512) { /* which implies 5512.5 practically */ + rate = 55125; + intvl *= 10; + } + ep = agdev->in_ep; prm = &uac2->p_prm; + prm->plen = get_pattern(rate, intvl, NULL); /* dry run */ + /* We try to support arbitrary rates too */ + if (prm->plen > MAX_LOOP_LEN) { + prm->plen = 1; + prm->pattern[0] = rate / intvl; + } else { + prm->plen = get_pattern(rate, intvl, prm->pattern); + } + + fsz = opts->p_ssize * num_channels(opts->p_chmask); + /* Convert samples to bytes */ + for (i = 0; i < prm->plen; i++) + prm->pattern[i] *= fsz; + config_ep_by_speed(gadget, fn, ep); agdev->as_in_alt = alt; } else { @@ -1125,12 +1194,13 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) prm->ureq[i].req = req; prm->ureq[i].pp = prm; + prm->ureq[i].idx = i % prm->plen; req->zero = 0; req->context = &prm->ureq[i]; - req->length = prm->max_psize; + req->length = prm->pattern[prm->ureq[i].idx]; 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)) -- 1.8.1.2 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html