diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 246a7784e0121180fc772524baec9b3f78d398c7..a5a27a504d67099e09670cd7ca9e48355c1c4f70 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -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))