[PATCHv2] usb: gadget: f_uac2: modulate playback data rate

Message ID 1409301875-21643-1-git-send-email-jaswinder.singh@linaro.org
State New
Headers show

Commit Message

Jassi Brar Aug. 29, 2014, 8:44 a.m.
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@linaro.org>
---
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(-)

Patch

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))