On Thu, May 18, 2017 at 4:07 AM, Ruslan Bilovol <ruslan.bilovol@xxxxxxxxx> wrote: > Abstract the peripheral side ALSA sound card code from > the f_uac2 function into a component that can be called > by various functions, so the various flavors can be split > apart and selectively reused. > > Visible changes: > - add uac_params structure to pass audio paramteres for > g_audio_setup > - make ALSA sound card's name configurable > - add [in/out]_ep_maxpsize > - allocate snd_uac_chip structure during g_audio_setup > - add u_audio_[start/stop]_[capture/playback] functions > > Signed-off-by: Ruslan Bilovol <ruslan.bilovol@xxxxxxxxx> > --- > drivers/usb/gadget/Kconfig | 4 + > drivers/usb/gadget/function/Makefile | 1 + > drivers/usb/gadget/function/f_uac2.c | 721 ++++------------------------------ > drivers/usb/gadget/function/u_audio.c | 661 +++++++++++++++++++++++++++++++ > drivers/usb/gadget/function/u_audio.h | 95 +++++ > drivers/usb/gadget/legacy/Kconfig | 1 + > 6 files changed, 846 insertions(+), 637 deletions(-) > create mode 100644 drivers/usb/gadget/function/u_audio.c > create mode 100644 drivers/usb/gadget/function/u_audio.h > > diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig > index c164d6b..2ba0ace 100644 > --- a/drivers/usb/gadget/Kconfig > +++ b/drivers/usb/gadget/Kconfig > @@ -158,6 +158,9 @@ config USB_U_SERIAL > config USB_U_ETHER > tristate > > +config USB_U_AUDIO > + tristate > + > config USB_F_SERIAL > tristate > > @@ -381,6 +384,7 @@ config USB_CONFIGFS_F_UAC2 > depends on SND > select USB_LIBCOMPOSITE > select SND_PCM > + select USB_U_AUDIO > select USB_F_UAC2 > help > This Audio function is compatible with USB Audio Class > diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile > index cb8c225..b29f2ae 100644 > --- a/drivers/usb/gadget/function/Makefile > +++ b/drivers/usb/gadget/function/Makefile > @@ -32,6 +32,7 @@ usb_f_mass_storage-y := f_mass_storage.o storage_common.o > obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o > usb_f_fs-y := f_fs.o > obj-$(CONFIG_USB_F_FS) += usb_f_fs.o > +obj-$(CONFIG_USB_U_AUDIO) += u_audio.o > usb_f_uac1-y := f_uac1.o u_uac1.o > obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o > usb_f_uac2-y := f_uac2.o > diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c > index d4565b5..059a14a 100644 > --- a/drivers/usb/gadget/function/f_uac2.c > +++ b/drivers/usb/gadget/function/f_uac2.c > @@ -15,10 +15,7 @@ > #include <linux/usb/audio-v2.h> > #include <linux/module.h> > > -#include <sound/core.h> > -#include <sound/pcm.h> > -#include <sound/pcm_params.h> > - > +#include "u_audio.h" > #include "u_uac2.h" > > /* > @@ -50,455 +47,23 @@ > #define UNFLW_CTRL 8 > #define OVFLW_CTRL 10 > > -struct uac2_req { > - struct uac2_rtd_params *pp; /* parent param */ > - struct usb_request *req; > -}; > - > -struct uac2_rtd_params { > - struct snd_uac2_chip *uac2; /* parent chip */ > - bool ep_enabled; /* if the ep is enabled */ > - /* Size of the ring buffer */ > - size_t dma_bytes; > - unsigned char *dma_area; > - > - struct snd_pcm_substream *ss; > - > - /* Ring buffer */ > - ssize_t hw_ptr; > - > - void *rbuf; > - > - size_t period_size; > - > - unsigned max_psize; > - struct uac2_req *ureq; > - > - spinlock_t lock; > -}; > - > -struct snd_uac2_chip { > - struct uac2_rtd_params p_prm; > - struct uac2_rtd_params c_prm; > - > - 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; > +struct f_uac2 { > + struct g_audio g_audio; > + u8 ac_intf, as_in_intf, as_out_intf; > + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ > }; > > -#define BUFF_SIZE_MAX (PAGE_SIZE * 16) > -#define PRD_SIZE_MAX PAGE_SIZE > -#define MIN_PERIODS 4 > - > -static struct snd_pcm_hardware uac2_pcm_hardware = { > - .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER > - | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID > - | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, > - .rates = SNDRV_PCM_RATE_CONTINUOUS, > - .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, > - .buffer_bytes_max = BUFF_SIZE_MAX, > - .period_bytes_max = PRD_SIZE_MAX, > - .periods_min = MIN_PERIODS, > -}; > - > -struct audio_dev { > - u8 ac_intf, ac_alt; > - u8 as_out_intf, as_out_alt; > - u8 as_in_intf, as_in_alt; > - > - struct usb_ep *in_ep, *out_ep; > - struct usb_function func; > - struct usb_gadget *gadget; > - > - /* The ALSA Sound Card it represents on the USB-Client side */ > - struct snd_uac2_chip uac2; > -}; > - > -static inline > -struct audio_dev *func_to_agdev(struct usb_function *f) > +static inline struct f_uac2 *func_to_uac2(struct usb_function *f) > { > - return container_of(f, struct audio_dev, func); > + return container_of(f, struct f_uac2, g_audio.func); > } > > static inline > -struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u) > -{ > - return container_of(u, struct audio_dev, uac2); > -} > - > -static inline > -struct f_uac2_opts *agdev_to_uac2_opts(struct audio_dev *agdev) > +struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev) > { > return container_of(agdev->func.fi, struct f_uac2_opts, func_inst); > } > > -static inline > -uint num_channels(uint chanmask) > -{ > - uint num = 0; > - > - while (chanmask) { > - num += (chanmask & 1); > - chanmask >>= 1; > - } > - > - return num; > -} > - > -static void > -agdev_iso_complete(struct usb_ep *ep, struct usb_request *req) > -{ > - unsigned pending; > - unsigned long flags; > - unsigned int hw_ptr; > - bool update_alsa = false; > - int status = req->status; > - struct uac2_req *ur = req->context; > - struct snd_pcm_substream *substream; > - struct uac2_rtd_params *prm = ur->pp; > - struct snd_uac2_chip *uac2 = prm->uac2; > - > - /* i/f shutting down */ > - if (!prm->ep_enabled || req->status == -ESHUTDOWN) > - return; > - > - /* > - * We can't really do much about bad xfers. > - * Afterall, the ISOCH xfers could fail legitimately. > - */ > - if (status) > - pr_debug("%s: iso_complete status(%d) %d/%d\n", > - __func__, status, req->actual, req->length); > - > - substream = prm->ss; > - > - /* Do nothing if ALSA isn't active */ > - if (!substream) > - goto exit; > - > - spin_lock_irqsave(&prm->lock, flags); > - > - 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; > - if (pending >= prm->period_size) > - update_alsa = true; > - > - hw_ptr = prm->hw_ptr; > - prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; > - > - spin_unlock_irqrestore(&prm->lock, flags); > - > - /* Pack USB load in ALSA ring buffer */ > - pending = prm->dma_bytes - hw_ptr; > - > - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { > - if (unlikely(pending < req->actual)) { > - memcpy(req->buf, prm->dma_area + hw_ptr, pending); > - memcpy(req->buf + pending, prm->dma_area, > - req->actual - pending); > - } else { > - memcpy(req->buf, prm->dma_area + hw_ptr, req->actual); > - } > - } else { > - if (unlikely(pending < req->actual)) { > - memcpy(prm->dma_area + hw_ptr, req->buf, pending); > - memcpy(prm->dma_area, req->buf + pending, > - req->actual - pending); > - } else { > - memcpy(prm->dma_area + hw_ptr, req->buf, req->actual); > - } > - } > - > -exit: > - if (usb_ep_queue(ep, req, GFP_ATOMIC)) > - dev_err(uac2->card->dev, "%d Error!\n", __LINE__); > - > - if (update_alsa) > - snd_pcm_period_elapsed(substream); > - > - return; > -} > - > -static int > -uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd) > -{ > - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); > - struct audio_dev *agdev = uac2_to_agdev(uac2); > - struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev); > - struct uac2_rtd_params *prm; > - unsigned long flags; > - int err = 0; > - > - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > - prm = &uac2->p_prm; > - else > - prm = &uac2->c_prm; > - > - spin_lock_irqsave(&prm->lock, flags); > - > - /* Reset */ > - prm->hw_ptr = 0; > - > - switch (cmd) { > - case SNDRV_PCM_TRIGGER_START: > - case SNDRV_PCM_TRIGGER_RESUME: > - prm->ss = substream; > - break; > - case SNDRV_PCM_TRIGGER_STOP: > - case SNDRV_PCM_TRIGGER_SUSPEND: > - prm->ss = NULL; > - break; > - default: > - err = -EINVAL; > - } > - > - spin_unlock_irqrestore(&prm->lock, flags); > - > - /* Clear buffer after Play stops */ > - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) > - memset(prm->rbuf, 0, prm->max_psize * uac2_opts->req_number); > - > - return err; > -} > - > -static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream) > -{ > - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); > - struct uac2_rtd_params *prm; > - > - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > - prm = &uac2->p_prm; > - else > - prm = &uac2->c_prm; > - > - return bytes_to_frames(substream->runtime, prm->hw_ptr); > -} > - > -static int uac2_pcm_hw_params(struct snd_pcm_substream *substream, > - struct snd_pcm_hw_params *hw_params) > -{ > - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); > - struct uac2_rtd_params *prm; > - int err; > - > - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > - prm = &uac2->p_prm; > - else > - prm = &uac2->c_prm; > - > - err = snd_pcm_lib_malloc_pages(substream, > - params_buffer_bytes(hw_params)); > - if (err >= 0) { > - prm->dma_bytes = substream->runtime->dma_bytes; > - prm->dma_area = substream->runtime->dma_area; > - prm->period_size = params_period_bytes(hw_params); > - } > - > - return err; > -} > - > -static int uac2_pcm_hw_free(struct snd_pcm_substream *substream) > -{ > - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); > - struct uac2_rtd_params *prm; > - > - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > - prm = &uac2->p_prm; > - else > - prm = &uac2->c_prm; > - > - prm->dma_area = NULL; > - prm->dma_bytes = 0; > - prm->period_size = 0; > - > - return snd_pcm_lib_free_pages(substream); > -} > - > -static int uac2_pcm_open(struct snd_pcm_substream *substream) > -{ > - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); > - struct snd_pcm_runtime *runtime = substream->runtime; > - struct audio_dev *audio_dev; > - struct f_uac2_opts *opts; > - int p_ssize, c_ssize; > - int p_srate, c_srate; > - int p_chmask, c_chmask; > - > - audio_dev = uac2_to_agdev(uac2); > - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); > - p_ssize = opts->p_ssize; > - c_ssize = opts->c_ssize; > - p_srate = opts->p_srate; > - c_srate = opts->c_srate; > - p_chmask = opts->p_chmask; > - c_chmask = opts->c_chmask; > - uac2->p_residue = 0; > - > - runtime->hw = uac2_pcm_hardware; > - > - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { > - spin_lock_init(&uac2->p_prm.lock); > - runtime->hw.rate_min = p_srate; > - switch (p_ssize) { > - case 3: > - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; > - break; > - case 4: > - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; > - break; > - default: > - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; > - break; > - } > - runtime->hw.channels_min = num_channels(p_chmask); > - runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize > - / runtime->hw.periods_min; > - } else { > - spin_lock_init(&uac2->c_prm.lock); > - runtime->hw.rate_min = c_srate; > - switch (c_ssize) { > - case 3: > - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; > - break; > - case 4: > - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; > - break; > - default: > - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; > - break; > - } > - runtime->hw.channels_min = num_channels(c_chmask); > - runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize > - / runtime->hw.periods_min; > - } > - > - runtime->hw.rate_max = runtime->hw.rate_min; > - runtime->hw.channels_max = runtime->hw.channels_min; > - > - snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); > - > - return 0; > -} > - > -/* ALSA cries without these function pointers */ > -static int uac2_pcm_null(struct snd_pcm_substream *substream) > -{ > - return 0; > -} > - > -static struct snd_pcm_ops uac2_pcm_ops = { > - .open = uac2_pcm_open, > - .close = uac2_pcm_null, > - .ioctl = snd_pcm_lib_ioctl, > - .hw_params = uac2_pcm_hw_params, > - .hw_free = uac2_pcm_hw_free, > - .trigger = uac2_pcm_trigger, > - .pointer = uac2_pcm_pointer, > - .prepare = uac2_pcm_null, > -}; > - > -static int snd_uac2_probe(struct audio_dev *audio_dev) > -{ > - struct snd_uac2_chip *uac2 = &audio_dev->uac2; > - struct snd_card *card; > - struct snd_pcm *pcm; > - struct f_uac2_opts *opts; > - int err; > - int p_chmask, c_chmask; > - > - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); > - p_chmask = opts->p_chmask; > - c_chmask = opts->c_chmask; > - > - /* Choose any slot, with no id */ > - err = snd_card_new(&audio_dev->gadget->dev, > - -1, NULL, THIS_MODULE, 0, &card); > - if (err < 0) > - return err; > - > - uac2->card = card; > - > - /* > - * Create first PCM device > - * Create a substream only for non-zero channel streams > - */ > - err = snd_pcm_new(uac2->card, "UAC2 PCM", 0, > - p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); > - if (err < 0) > - goto snd_fail; > - > - strcpy(pcm->name, "UAC2 PCM"); > - pcm->private_data = uac2; > - > - uac2->pcm = pcm; > - > - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops); > - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops); > - > - strcpy(card->driver, "UAC2_Gadget"); > - strcpy(card->shortname, "UAC2_Gadget"); > - sprintf(card->longname, "UAC2_Gadget %i", card->dev->id); > - > - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, > - snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); > - > - err = snd_card_register(card); > - > - if (!err) > - return 0; > - > -snd_fail: > - snd_card_free(card); > - > - uac2->pcm = NULL; > - uac2->card = NULL; > - > - return err; > -} > - > -static int snd_uac2_remove(struct audio_dev *audio_dev) > -{ > - struct snd_card *card = audio_dev->uac2.card; > - > - if (card) > - return snd_card_free(card); > - > - return 0; > -} > - > - > /* --------- USB Function Interface ------------- */ > > enum { > @@ -886,32 +451,6 @@ struct cntrl_range_lay3 { > __u32 dRES; > } __packed; > > -static inline void > -free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep) > -{ > - struct snd_uac2_chip *uac2 = prm->uac2; > - struct audio_dev *agdev = uac2_to_agdev(uac2); > - struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev); > - int i; > - > - if (!prm->ep_enabled) > - return; > - > - prm->ep_enabled = false; > - > - for (i = 0; i < uac2_opts->req_number; i++) { > - if (prm->ureq[i].req) { > - usb_ep_dequeue(ep, prm->ureq[i].req); > - usb_ep_free_request(ep, prm->ureq[i].req); > - prm->ureq[i].req = NULL; > - } > - } > - > - if (usb_ep_disable(ep)) > - dev_err(uac2->card->dev, > - "%s:%d Error!\n", __func__, __LINE__); > -} > - > static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > struct usb_endpoint_descriptor *ep_desc, > unsigned int factor, bool is_playback) > @@ -938,12 +477,11 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > static int > afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) > { > - struct audio_dev *agdev = func_to_agdev(fn); > - struct snd_uac2_chip *uac2 = &agdev->uac2; > + struct f_uac2 *uac2 = func_to_uac2(fn); > + struct g_audio *agdev = func_to_g_audio(fn); > struct usb_composite_dev *cdev = cfg->cdev; > struct usb_gadget *gadget = cdev->gadget; > struct device *dev = &gadget->dev; > - struct uac2_rtd_params *prm; > struct f_uac2_opts *uac2_opts; > struct usb_string *us; > int ret; > @@ -990,8 +528,8 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > return ret; > } > std_ac_if_desc.bInterfaceNumber = ret; > - agdev->ac_intf = ret; > - agdev->ac_alt = 0; > + uac2->ac_intf = ret; > + uac2->ac_alt = 0; > > ret = usb_interface_id(cfg, fn); > if (ret < 0) { > @@ -1000,8 +538,8 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > } > std_as_out_if0_desc.bInterfaceNumber = ret; > std_as_out_if1_desc.bInterfaceNumber = ret; > - agdev->as_out_intf = ret; > - agdev->as_out_alt = 0; > + uac2->as_out_intf = ret; > + uac2->as_out_alt = 0; > > ret = usb_interface_id(cfg, fn); > if (ret < 0) { > @@ -1010,8 +548,8 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > } > std_as_in_if0_desc.bInterfaceNumber = ret; > std_as_in_if1_desc.bInterfaceNumber = ret; > - agdev->as_in_intf = ret; > - agdev->as_in_alt = 0; > + uac2->as_in_intf = ret; > + uac2->as_in_alt = 0; > > agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); > if (!agdev->out_ep) { > @@ -1025,15 +563,17 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > return ret; > } > > - uac2->p_prm.uac2 = uac2; > - uac2->c_prm.uac2 = uac2; > - > /* Calculate wMaxPacketSize according to audio bandwidth */ > set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); > set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false); > set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); > set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); > > + agdev->in_ep_maxpsize = max(fs_epin_desc.wMaxPacketSize, > + hs_epin_desc.wMaxPacketSize); > + agdev->out_ep_maxpsize = max(fs_epout_desc.wMaxPacketSize, > + hs_epout_desc.wMaxPacketSize); > + > hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; > hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; > > @@ -1044,46 +584,18 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > > agdev->gadget = gadget; > > - prm = &agdev->uac2.c_prm; > - prm->max_psize = hs_epout_desc.wMaxPacketSize; > - prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req), > - GFP_KERNEL); > - if (!prm->ureq) { > - ret = -ENOMEM; > - goto err_free_descs; > - } > - prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL); > - if (!prm->rbuf) { > - prm->max_psize = 0; > - ret = -ENOMEM; > - goto err_free_descs; > - } > - > - prm = &agdev->uac2.p_prm; > - prm->max_psize = hs_epin_desc.wMaxPacketSize; > - prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req), > - GFP_KERNEL); > - if (!prm->ureq) { > - ret = -ENOMEM; > - goto err_free_descs; > - } > - prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL); > - if (!prm->rbuf) { > - prm->max_psize = 0; > - ret = -ENOMEM; > - goto err_no_memory; > - } > - > - ret = snd_uac2_probe(agdev); > + agdev->params.p_chmask = uac2_opts->p_chmask; > + agdev->params.p_srate = uac2_opts->p_srate; > + agdev->params.p_ssize = uac2_opts->p_ssize; > + agdev->params.c_chmask = uac2_opts->c_chmask; > + agdev->params.c_srate = uac2_opts->c_srate; > + agdev->params.c_ssize = uac2_opts->c_ssize; > + agdev->params.req_number = uac2_opts->req_number; > + ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); > if (ret) > - goto err_no_memory; > + goto err_free_descs; > return 0; > > -err_no_memory: > - kfree(agdev->uac2.p_prm.ureq); > - kfree(agdev->uac2.c_prm.ureq); > - kfree(agdev->uac2.p_prm.rbuf); > - kfree(agdev->uac2.c_prm.rbuf); > err_free_descs: > usb_free_all_descriptors(fn); > agdev->gadget = NULL; > @@ -1094,15 +606,10 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) > { > struct usb_composite_dev *cdev = fn->config->cdev; > - struct audio_dev *agdev = func_to_agdev(fn); > - struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev); > - struct snd_uac2_chip *uac2 = &agdev->uac2; > + struct f_uac2 *uac2 = func_to_uac2(fn); > struct usb_gadget *gadget = cdev->gadget; > struct device *dev = &gadget->dev; > - struct usb_request *req; > - struct usb_ep *ep; > - struct uac2_rtd_params *prm; > - int req_len, i; > + int ret = 0; > > /* No i/f has more than 2 alt settings */ > if (alt > 1) { > @@ -1110,7 +617,7 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > return -EINVAL; > } > > - if (intf == agdev->ac_intf) { > + if (intf == uac2->ac_intf) { > /* Control I/f has only 1 AltSetting - 0 */ > if (alt) { > dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); > @@ -1119,92 +626,40 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > return 0; > } > > - if (intf == agdev->as_out_intf) { > - ep = agdev->out_ep; > - 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) { > - 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 = 8000; > - } > - > - /* 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 = factor / (1 << (ep_desc->bInterval - 1)); > - uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval, > - prm->max_psize); > + if (intf == uac2->as_out_intf) { > + uac2->as_out_alt = alt; > > - if (uac2->p_pktsize < prm->max_psize) > - uac2->p_pktsize_residue = rate % uac2->p_interval; > + if (alt) > + ret = u_audio_start_capture(&uac2->g_audio); > else > - uac2->p_pktsize_residue = 0; > + u_audio_stop_capture(&uac2->g_audio); > + } else if (intf == uac2->as_in_intf) { > + uac2->as_in_alt = alt; > > - req_len = uac2->p_pktsize; > - uac2->p_residue = 0; > + if (alt) > + ret = u_audio_start_playback(&uac2->g_audio); > + else > + u_audio_stop_playback(&uac2->g_audio); > } else { > dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); > return -EINVAL; > } > > - if (alt == 0) { > - free_ep(prm, ep); > - return 0; > - } > - > - prm->ep_enabled = true; > - usb_ep_enable(ep); > - > - for (i = 0; i < opts->req_number; i++) { > - if (!prm->ureq[i].req) { > - req = usb_ep_alloc_request(ep, GFP_ATOMIC); > - if (req == NULL) > - return -ENOMEM; > - > - prm->ureq[i].req = req; > - prm->ureq[i].pp = prm; > - > - req->zero = 0; > - req->context = &prm->ureq[i]; > - req->length = req_len; > - req->complete = agdev_iso_complete; > - req->buf = prm->rbuf + i * prm->max_psize; > - } > - > - if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) > - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); > - } > - > - return 0; > + return ret; > } > > static int > afunc_get_alt(struct usb_function *fn, unsigned intf) > { > - struct audio_dev *agdev = func_to_agdev(fn); > - > - if (intf == agdev->ac_intf) > - return agdev->ac_alt; > - else if (intf == agdev->as_out_intf) > - return agdev->as_out_alt; > - else if (intf == agdev->as_in_intf) > - return agdev->as_in_alt; > + struct f_uac2 *uac2 = func_to_uac2(fn); > + struct g_audio *agdev = func_to_g_audio(fn); > + > + if (intf == uac2->ac_intf) > + return uac2->ac_alt; > + else if (intf == uac2->as_out_intf) > + return uac2->as_out_alt; > + else if (intf == uac2->as_in_intf) > + return uac2->as_in_alt; > else > dev_err(&agdev->gadget->dev, > "%s:%d Invalid Interface %d!\n", > @@ -1216,21 +671,19 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > static void > afunc_disable(struct usb_function *fn) > { > - struct audio_dev *agdev = func_to_agdev(fn); > - struct snd_uac2_chip *uac2 = &agdev->uac2; > - > - free_ep(&uac2->p_prm, agdev->in_ep); > - agdev->as_in_alt = 0; > + struct f_uac2 *uac2 = func_to_uac2(fn); > > - free_ep(&uac2->c_prm, agdev->out_ep); > - agdev->as_out_alt = 0; > + uac2->as_in_alt = 0; > + uac2->as_out_alt = 0; > + u_audio_stop_capture(&uac2->g_audio); > + u_audio_stop_playback(&uac2->g_audio); > } > > static int > in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) > { > struct usb_request *req = fn->config->cdev->req; > - struct audio_dev *agdev = func_to_agdev(fn); > + struct g_audio *agdev = func_to_g_audio(fn); > struct f_uac2_opts *opts; > u16 w_length = le16_to_cpu(cr->wLength); > u16 w_index = le16_to_cpu(cr->wIndex); > @@ -1240,7 +693,7 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > int value = -EOPNOTSUPP; > int p_srate, c_srate; > > - opts = agdev_to_uac2_opts(agdev); > + opts = g_audio_to_uac2_opts(agdev); > p_srate = opts->p_srate; > c_srate = opts->c_srate; > > @@ -1271,7 +724,7 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) > { > struct usb_request *req = fn->config->cdev->req; > - struct audio_dev *agdev = func_to_agdev(fn); > + struct g_audio *agdev = func_to_g_audio(fn); > struct f_uac2_opts *opts; > u16 w_length = le16_to_cpu(cr->wLength); > u16 w_index = le16_to_cpu(cr->wIndex); > @@ -1282,7 +735,7 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > int value = -EOPNOTSUPP; > int p_srate, c_srate; > > - opts = agdev_to_uac2_opts(agdev); > + opts = g_audio_to_uac2_opts(agdev); > p_srate = opts->p_srate; > c_srate = opts->c_srate; > > @@ -1336,11 +789,12 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > static int > setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr) > { > - struct audio_dev *agdev = func_to_agdev(fn); > + struct f_uac2 *uac2 = func_to_uac2(fn); > + struct g_audio *agdev = func_to_g_audio(fn); > u16 w_index = le16_to_cpu(cr->wIndex); > u8 intf = w_index & 0xff; > > - if (intf != agdev->ac_intf) { > + if (intf != uac2->ac_intf) { > dev_err(&agdev->gadget->dev, > "%s:%d Error!\n", __func__, __LINE__); > return -EOPNOTSUPP; > @@ -1358,7 +812,7 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, > afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) > { > struct usb_composite_dev *cdev = fn->config->cdev; > - struct audio_dev *agdev = func_to_agdev(fn); > + struct g_audio *agdev = func_to_g_audio(fn); > struct usb_request *req = cdev->req; > u16 w_length = le16_to_cpu(cr->wLength); > int value = -EOPNOTSUPP; > @@ -1504,10 +958,10 @@ static struct usb_function_instance *afunc_alloc_inst(void) > > static void afunc_free(struct usb_function *f) > { > - struct audio_dev *agdev; > + struct g_audio *agdev; > struct f_uac2_opts *opts; > > - agdev = func_to_agdev(f); > + agdev = func_to_g_audio(f); > opts = container_of(f->fi, struct f_uac2_opts, func_inst); > kfree(agdev); > mutex_lock(&opts->lock); > @@ -1517,17 +971,9 @@ static void afunc_free(struct usb_function *f) > > static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) > { > - struct audio_dev *agdev = func_to_agdev(f); > - struct uac2_rtd_params *prm; > - > - snd_uac2_remove(agdev); > - > - prm = &agdev->uac2.p_prm; > - kfree(prm->rbuf); > + struct g_audio *agdev = func_to_g_audio(f); > > - prm = &agdev->uac2.c_prm; > - kfree(prm->rbuf); > - kfree(prm->ureq); > + g_audio_cleanup(agdev); > usb_free_all_descriptors(f); > > agdev->gadget = NULL; > @@ -1535,11 +981,11 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) > > static struct usb_function *afunc_alloc(struct usb_function_instance *fi) > { > - struct audio_dev *agdev; > + struct f_uac2 *uac2; > struct f_uac2_opts *opts; > > - agdev = kzalloc(sizeof(*agdev), GFP_KERNEL); > - if (agdev == NULL) > + uac2 = kzalloc(sizeof(*uac2), GFP_KERNEL); > + if (uac2 == NULL) > return ERR_PTR(-ENOMEM); > > opts = container_of(fi, struct f_uac2_opts, func_inst); > @@ -1547,19 +993,20 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi) > ++opts->refcnt; > mutex_unlock(&opts->lock); > > - agdev->func.name = "uac2_func"; > - agdev->func.bind = afunc_bind; > - agdev->func.unbind = afunc_unbind; > - agdev->func.set_alt = afunc_set_alt; > - agdev->func.get_alt = afunc_get_alt; > - agdev->func.disable = afunc_disable; > - agdev->func.setup = afunc_setup; > - agdev->func.free_func = afunc_free; > + uac2->g_audio.func.name = "uac2_func"; > + uac2->g_audio.func.bind = afunc_bind; > + uac2->g_audio.func.unbind = afunc_unbind; > + uac2->g_audio.func.set_alt = afunc_set_alt; > + uac2->g_audio.func.get_alt = afunc_get_alt; > + uac2->g_audio.func.disable = afunc_disable; > + uac2->g_audio.func.setup = afunc_setup; > + uac2->g_audio.func.free_func = afunc_free; > > - return &agdev->func; > + return &uac2->g_audio.func; > } > > DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); > MODULE_LICENSE("GPL"); > MODULE_AUTHOR("Yadwinder Singh"); > MODULE_AUTHOR("Jaswinder Singh"); > +MODULE_AUTHOR("Ruslan Bilovol"); > drivers/usb/gadget/function/f_uac2.c | 721 ++++------------------------------ Basically you move out ALSA sound card implementation and %s/agdev/uac2/ I am not sure churn warrants a MODULE_AUTHOR. People have made far more important contribution to the UAC2, > diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c > new file mode 100644 > index 0000000..8ee65b8 > --- /dev/null > +++ b/drivers/usb/gadget/function/u_audio.c > @@ -0,0 +1,661 @@ > +/* > + * u_audio.c -- interface to USB gadget "ALSA sound card" utilities > + * > + * Copyright (C) 2016 > + * Author: Ruslan Bilovol <ruslan.bilovol@xxxxxxxxx> > + * > + * Based on f_uac2.c which is: > + * Copyright (C) 2011 > + * Yadwinder Singh (yadi.brar01@xxxxxxxxx) > + * Jaswinder Singh (jaswinder.singh@xxxxxxxxxx) > + * Again this is not "based on", rather where you paste the code you cut from f_uac2.c Of course you also pad it with minor changes to make it useful to your uac1 code. I am not happy with losing copyright on the sound card implementation for the UAC drivers. > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#include <linux/module.h> > +#include <sound/core.h> > +#include <sound/pcm.h> > +#include <sound/pcm_params.h> > + > +#include "u_audio.h" > + > +#define BUFF_SIZE_MAX (PAGE_SIZE * 16) > +#define PRD_SIZE_MAX PAGE_SIZE > +#define MIN_PERIODS 4 > + > +struct uac_req { > + struct uac_rtd_params *pp; /* parent param */ > + struct usb_request *req; > +}; > + > +/* Runtime data params for one stream */ > +struct uac_rtd_params { > + struct snd_uac_chip *uac; /* parent chip */ > + bool ep_enabled; /* if the ep is enabled */ > + /* Size of the ring buffer */ > + size_t dma_bytes; > + unsigned char *dma_area; > + > + struct snd_pcm_substream *ss; > + > + /* Ring buffer */ > + ssize_t hw_ptr; > + > + void *rbuf; > + > + size_t period_size; > + > + unsigned max_psize; /* MaxPacketSize of endpoint */ > + struct uac_req *ureq; > + > + spinlock_t lock; > +}; > + > +struct snd_uac_chip { > + struct g_audio *audio_dev; > + > + struct uac_rtd_params p_prm; > + struct uac_rtd_params c_prm; > + > + 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; > +}; > + > +static struct snd_pcm_hardware uac_pcm_hardware = { > + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER > + | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID > + | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, > + .rates = SNDRV_PCM_RATE_CONTINUOUS, > + .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, > + .buffer_bytes_max = BUFF_SIZE_MAX, > + .period_bytes_max = PRD_SIZE_MAX, > + .periods_min = MIN_PERIODS, > +}; > + > +static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) > +{ > + unsigned pending; > + unsigned long flags; > + unsigned int hw_ptr; > + bool update_alsa = false; > + int status = req->status; > + struct uac_req *ur = req->context; > + struct snd_pcm_substream *substream; > + struct uac_rtd_params *prm = ur->pp; > + struct snd_uac_chip *uac = prm->uac; > + > + /* i/f shutting down */ > + if (!prm->ep_enabled || req->status == -ESHUTDOWN) > + return; > + > + /* > + * We can't really do much about bad xfers. > + * Afterall, the ISOCH xfers could fail legitimately. > + */ > + if (status) > + pr_debug("%s: iso_complete status(%d) %d/%d\n", > + __func__, status, req->actual, req->length); > + > + substream = prm->ss; > + > + /* Do nothing if ALSA isn't active */ > + if (!substream) > + goto exit; > + > + spin_lock_irqsave(&prm->lock, flags); > + > + 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 = uac->p_pktsize; > + uac->p_residue += uac->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 (uac->p_residue / uac->p_interval >= uac->p_framesize) { > + req->length += uac->p_framesize; > + uac->p_residue -= uac->p_framesize * > + uac->p_interval; > + } > + > + req->actual = req->length; > + } > + > + pending = prm->hw_ptr % prm->period_size; > + pending += req->actual; > + if (pending >= prm->period_size) > + update_alsa = true; > + > + hw_ptr = prm->hw_ptr; > + prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; > + > + spin_unlock_irqrestore(&prm->lock, flags); > + > + /* Pack USB load in ALSA ring buffer */ > + pending = prm->dma_bytes - hw_ptr; > + > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { > + if (unlikely(pending < req->actual)) { > + memcpy(req->buf, prm->dma_area + hw_ptr, pending); > + memcpy(req->buf + pending, prm->dma_area, > + req->actual - pending); > + } else { > + memcpy(req->buf, prm->dma_area + hw_ptr, req->actual); > + } > + } else { > + if (unlikely(pending < req->actual)) { > + memcpy(prm->dma_area + hw_ptr, req->buf, pending); > + memcpy(prm->dma_area, req->buf + pending, > + req->actual - pending); > + } else { > + memcpy(prm->dma_area + hw_ptr, req->buf, req->actual); > + } > + } > + > +exit: > + if (usb_ep_queue(ep, req, GFP_ATOMIC)) > + dev_err(uac->card->dev, "%d Error!\n", __LINE__); > + > + if (update_alsa) > + snd_pcm_period_elapsed(substream); > +} > + > +static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) > +{ > + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); > + struct uac_rtd_params *prm; > + struct g_audio *audio_dev; > + struct uac_params *params; > + unsigned long flags; > + int err = 0; > + > + audio_dev = uac->audio_dev; > + params = &audio_dev->params; > + > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > + prm = &uac->p_prm; > + else > + prm = &uac->c_prm; > + > + spin_lock_irqsave(&prm->lock, flags); > + > + /* Reset */ > + prm->hw_ptr = 0; > + > + switch (cmd) { > + case SNDRV_PCM_TRIGGER_START: > + case SNDRV_PCM_TRIGGER_RESUME: > + prm->ss = substream; > + break; > + case SNDRV_PCM_TRIGGER_STOP: > + case SNDRV_PCM_TRIGGER_SUSPEND: > + prm->ss = NULL; > + break; > + default: > + err = -EINVAL; > + } > + > + spin_unlock_irqrestore(&prm->lock, flags); > + > + /* Clear buffer after Play stops */ > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) > + memset(prm->rbuf, 0, prm->max_psize * params->req_number); > + > + return err; > +} > + > +static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream) > +{ > + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); > + struct uac_rtd_params *prm; > + > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > + prm = &uac->p_prm; > + else > + prm = &uac->c_prm; > + > + return bytes_to_frames(substream->runtime, prm->hw_ptr); > +} > + > +static int uac_pcm_hw_params(struct snd_pcm_substream *substream, > + struct snd_pcm_hw_params *hw_params) > +{ > + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); > + struct uac_rtd_params *prm; > + int err; > + > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > + prm = &uac->p_prm; > + else > + prm = &uac->c_prm; > + > + err = snd_pcm_lib_malloc_pages(substream, > + params_buffer_bytes(hw_params)); > + if (err >= 0) { > + prm->dma_bytes = substream->runtime->dma_bytes; > + prm->dma_area = substream->runtime->dma_area; > + prm->period_size = params_period_bytes(hw_params); > + } > + > + return err; > +} > + > +static int uac_pcm_hw_free(struct snd_pcm_substream *substream) > +{ > + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); > + struct uac_rtd_params *prm; > + > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) > + prm = &uac->p_prm; > + else > + prm = &uac->c_prm; > + > + prm->dma_area = NULL; > + prm->dma_bytes = 0; > + prm->period_size = 0; > + > + return snd_pcm_lib_free_pages(substream); > +} > + > +static int uac_pcm_open(struct snd_pcm_substream *substream) > +{ > + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); > + struct snd_pcm_runtime *runtime = substream->runtime; > + struct g_audio *audio_dev; > + struct uac_params *params; > + int p_ssize, c_ssize; > + int p_srate, c_srate; > + int p_chmask, c_chmask; > + > + audio_dev = uac->audio_dev; > + params = &audio_dev->params; > + p_ssize = params->p_ssize; > + c_ssize = params->c_ssize; > + p_srate = params->p_srate; > + c_srate = params->c_srate; > + p_chmask = params->p_chmask; > + c_chmask = params->c_chmask; > + uac->p_residue = 0; > + > + runtime->hw = uac_pcm_hardware; > + > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { > + spin_lock_init(&uac->p_prm.lock); > + runtime->hw.rate_min = p_srate; > + switch (p_ssize) { > + case 3: > + runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; > + break; > + case 4: > + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; > + break; > + default: > + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; > + break; > + } > + runtime->hw.channels_min = num_channels(p_chmask); > + runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize > + / runtime->hw.periods_min; > + } else { > + spin_lock_init(&uac->c_prm.lock); > + runtime->hw.rate_min = c_srate; > + switch (c_ssize) { > + case 3: > + runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; > + break; > + case 4: > + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; > + break; > + default: > + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; > + break; > + } > + runtime->hw.channels_min = num_channels(c_chmask); > + runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize > + / runtime->hw.periods_min; > + } > + > + runtime->hw.rate_max = runtime->hw.rate_min; > + runtime->hw.channels_max = runtime->hw.channels_min; > + > + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); > + > + return 0; > +} > + > +/* ALSA cries without these function pointers */ > +static int uac_pcm_null(struct snd_pcm_substream *substream) > +{ > + return 0; > +} > + > +static struct snd_pcm_ops uac_pcm_ops = { > + .open = uac_pcm_open, > + .close = uac_pcm_null, > + .ioctl = snd_pcm_lib_ioctl, > + .hw_params = uac_pcm_hw_params, > + .hw_free = uac_pcm_hw_free, > + .trigger = uac_pcm_trigger, > + .pointer = uac_pcm_pointer, > + .prepare = uac_pcm_null, > +}; > + > +static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep) > +{ > + struct snd_uac_chip *uac = prm->uac; > + struct g_audio *audio_dev; > + struct uac_params *params; > + int i; > + > + if (!prm->ep_enabled) > + return; > + > + prm->ep_enabled = false; > + > + audio_dev = uac->audio_dev; > + params = &audio_dev->params; > + > + for (i = 0; i < params->req_number; i++) { > + if (prm->ureq[i].req) { > + usb_ep_dequeue(ep, prm->ureq[i].req); > + usb_ep_free_request(ep, prm->ureq[i].req); > + prm->ureq[i].req = NULL; > + } > + } > + > + if (usb_ep_disable(ep)) > + dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); > +} > + > + > +int u_audio_start_capture(struct g_audio *audio_dev) > +{ > + struct snd_uac_chip *uac = audio_dev->uac; > + struct usb_gadget *gadget = audio_dev->gadget; > + struct device *dev = &gadget->dev; > + struct usb_request *req; > + struct usb_ep *ep; > + struct uac_rtd_params *prm; > + struct uac_params *params = &audio_dev->params; > + int req_len, i; > + > + ep = audio_dev->out_ep; > + prm = &uac->c_prm; > + config_ep_by_speed(gadget, &audio_dev->func, ep); > + req_len = prm->max_psize; > + > + prm->ep_enabled = true; > + usb_ep_enable(ep); > + > + for (i = 0; i < params->req_number; i++) { > + if (!prm->ureq[i].req) { > + req = usb_ep_alloc_request(ep, GFP_ATOMIC); > + if (req == NULL) > + return -ENOMEM; > + > + prm->ureq[i].req = req; > + prm->ureq[i].pp = prm; > + > + req->zero = 0; > + req->context = &prm->ureq[i]; > + req->length = req_len; > + req->complete = u_audio_iso_complete; > + req->buf = prm->rbuf + i * prm->max_psize; > + } > + > + if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) > + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); > + } > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(u_audio_start_capture); > + > +void u_audio_stop_capture(struct g_audio *audio_dev) > +{ > + struct snd_uac_chip *uac = audio_dev->uac; > + > + free_ep(&uac->c_prm, audio_dev->out_ep); > +} > +EXPORT_SYMBOL_GPL(u_audio_stop_capture); > + > +int u_audio_start_playback(struct g_audio *audio_dev) > +{ > + struct snd_uac_chip *uac = audio_dev->uac; > + struct usb_gadget *gadget = audio_dev->gadget; > + struct device *dev = &gadget->dev; > + struct usb_request *req; > + struct usb_ep *ep; > + struct uac_rtd_params *prm; > + struct uac_params *params = &audio_dev->params; > + unsigned int factor, rate; > + const struct usb_endpoint_descriptor *ep_desc; > + int req_len, i; > + > + ep = audio_dev->in_ep; > + prm = &uac->p_prm; > + config_ep_by_speed(gadget, &audio_dev->func, ep); > + > + ep_desc = ep->desc; > + > + /* pre-calculate the playback endpoint's interval */ > + if (gadget->speed == USB_SPEED_FULL) > + factor = 1000; > + else > + factor = 8000; > + > + /* pre-compute some values for iso_complete() */ > + uac->p_framesize = params->p_ssize * > + num_channels(params->p_chmask); > + rate = params->p_srate * uac->p_framesize; > + uac->p_interval = factor / (1 << (ep_desc->bInterval - 1)); > + uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval, > + prm->max_psize); > + > + if (uac->p_pktsize < prm->max_psize) > + uac->p_pktsize_residue = rate % uac->p_interval; > + else > + uac->p_pktsize_residue = 0; > + > + req_len = uac->p_pktsize; > + uac->p_residue = 0; > + > + prm->ep_enabled = true; > + usb_ep_enable(ep); > + > + for (i = 0; i < params->req_number; i++) { > + if (!prm->ureq[i].req) { > + req = usb_ep_alloc_request(ep, GFP_ATOMIC); > + if (req == NULL) > + return -ENOMEM; > + > + prm->ureq[i].req = req; > + prm->ureq[i].pp = prm; > + > + req->zero = 0; > + req->context = &prm->ureq[i]; > + req->length = req_len; > + req->complete = u_audio_iso_complete; > + req->buf = prm->rbuf + i * prm->max_psize; > + } > + > + if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) > + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); > + } > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(u_audio_start_playback); > + > +void u_audio_stop_playback(struct g_audio *audio_dev) > +{ > + struct snd_uac_chip *uac = audio_dev->uac; > + > + free_ep(&uac->p_prm, audio_dev->in_ep); > +} > +EXPORT_SYMBOL_GPL(u_audio_stop_playback); > + > +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, > + const char *card_name) > +{ > + struct snd_uac_chip *uac; > + struct snd_card *card; > + struct snd_pcm *pcm; > + struct uac_params *params; > + int p_chmask, c_chmask; > + int err; > + > + if (!g_audio) > + return -EINVAL; > + > + uac = kzalloc(sizeof(*uac), GFP_KERNEL); > + if (!uac) > + return -ENOMEM; > + g_audio->uac = uac; > + uac->audio_dev = g_audio; > + > + params = &g_audio->params; > + p_chmask = params->p_chmask; > + c_chmask = params->c_chmask; > + > + if (c_chmask) { > + struct uac_rtd_params *prm = &uac->c_prm; > + > + uac->c_prm.uac = uac; > + prm->max_psize = g_audio->out_ep_maxpsize; > + > + prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), > + GFP_KERNEL); > + if (!prm->ureq) { > + err = -ENOMEM; > + goto fail; > + } > + > + prm->rbuf = kcalloc(params->req_number, prm->max_psize, > + GFP_KERNEL); > + if (!prm->rbuf) { > + prm->max_psize = 0; > + err = -ENOMEM; > + goto fail; > + } > + } > + > + if (p_chmask) { > + struct uac_rtd_params *prm = &uac->p_prm; > + > + uac->p_prm.uac = uac; > + prm->max_psize = g_audio->in_ep_maxpsize; > + > + prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), > + GFP_KERNEL); > + if (!prm->ureq) { > + err = -ENOMEM; > + goto fail; > + } > + > + prm->rbuf = kcalloc(params->req_number, prm->max_psize, > + GFP_KERNEL); > + if (!prm->rbuf) { > + prm->max_psize = 0; > + err = -ENOMEM; > + goto fail; > + } > + } > + > + /* Choose any slot, with no id */ > + err = snd_card_new(&g_audio->gadget->dev, > + -1, NULL, THIS_MODULE, 0, &card); > + if (err < 0) > + goto fail; > + > + uac->card = card; > + > + /* > + * Create first PCM device > + * Create a substream only for non-zero channel streams > + */ > + err = snd_pcm_new(uac->card, pcm_name, 0, > + p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); > + if (err < 0) > + goto snd_fail; > + > + strcpy(pcm->name, pcm_name); > + pcm->private_data = uac; > + uac->pcm = pcm; > + > + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); > + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); > + > + strcpy(card->driver, card_name); > + strcpy(card->shortname, card_name); > + sprintf(card->longname, "%s %i", card_name, card->dev->id); > + > + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, > + snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); > + > + err = snd_card_register(card); > + > + if (!err) > + return 0; > + > +snd_fail: > + snd_card_free(card); > +fail: > + kfree(uac->p_prm.ureq); > + kfree(uac->c_prm.ureq); > + kfree(uac->p_prm.rbuf); > + kfree(uac->c_prm.rbuf); > + kfree(uac); > + > + return err; > +} > +EXPORT_SYMBOL_GPL(g_audio_setup); > + > +void g_audio_cleanup(struct g_audio *g_audio) > +{ > + struct snd_uac_chip *uac; > + struct snd_card *card; > + > + if (!g_audio || !g_audio->uac) > + return; > + > + uac = g_audio->uac; > + card = uac->card; > + if (card) > + snd_card_free(card); > + > + kfree(uac->p_prm.ureq); > + kfree(uac->c_prm.ureq); > + kfree(uac->p_prm.rbuf); > + kfree(uac->c_prm.rbuf); > + kfree(uac); > +} > +EXPORT_SYMBOL_GPL(g_audio_cleanup); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities"); > +MODULE_AUTHOR("Ruslan Bilovol"); > diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h > new file mode 100644 > index 0000000..07e1378 > --- /dev/null > +++ b/drivers/usb/gadget/function/u_audio.h > @@ -0,0 +1,95 @@ > +/* > + * u_audio.h -- interface to USB gadget "ALSA sound card" utilities > + * > + * Copyright (C) 2016 > + * Author: Ruslan Bilovol <ruslan.bilovol@xxxxxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#ifndef __U_AUDIO_H > +#define __U_AUDIO_H > + > +#include <linux/usb/composite.h> > + > +struct uac_params { > + /* playback */ > + int p_chmask; /* channel mask */ > + int p_srate; /* rate in Hz */ > + int p_ssize; /* sample size */ > + > + /* capture */ > + int c_chmask; /* channel mask */ > + int c_srate; /* rate in Hz */ > + int c_ssize; /* sample size */ > + > + int req_number; /* number of preallocated requests */ > +}; > + > +struct g_audio { > + struct usb_function func; > + struct usb_gadget *gadget; > + > + struct usb_ep *in_ep; > + struct usb_ep *out_ep; > + > + /* Max packet size for all in_ep possible speeds */ > + unsigned int in_ep_maxpsize; > + /* Max packet size for all out_ep possible speeds */ > + unsigned int out_ep_maxpsize; > + > + /* The ALSA Sound Card it represents on the USB-Client side */ > + struct snd_uac_chip *uac; > + > + struct uac_params params; > +}; > + > +static inline struct g_audio *func_to_g_audio(struct usb_function *f) > +{ > + return container_of(f, struct g_audio, func); > +} > + > +static inline uint num_channels(uint chanmask) > +{ > + uint num = 0; > + > + while (chanmask) { > + num += (chanmask & 1); > + chanmask >>= 1; > + } > + > + return num; > +} > + > +/* > + * g_audio_setup - initialize one virtual ALSA sound card > + * @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize > + * @pcm_name: the id string for a PCM instance of this sound card > + * @card_name: name of this soundcard > + * > + * This sets up the single virtual ALSA sound card that may be exported by a > + * gadget driver using this framework. > + * > + * Context: may sleep > + * > + * Returns zero on success, or a negative error on failure. > + */ > +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, > + const char *card_name); > +void g_audio_cleanup(struct g_audio *g_audio); > + > +int u_audio_start_capture(struct g_audio *g_audio); > +void u_audio_stop_capture(struct g_audio *g_audio); > +int u_audio_start_playback(struct g_audio *g_audio); > +void u_audio_stop_playback(struct g_audio *g_audio); > + > +#endif /* __U_AUDIO_H */ > diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig > index 0b36878..5344064 100644 > --- a/drivers/usb/gadget/legacy/Kconfig > +++ b/drivers/usb/gadget/legacy/Kconfig > @@ -56,6 +56,7 @@ config USB_AUDIO > select SND_PCM > select USB_F_UAC1 if GADGET_UAC1 > select USB_F_UAC2 if !GADGET_UAC1 > + select USB_U_AUDIO if USB_F_UAC2 > help > This Gadget Audio driver is compatible with USB Audio Class > specification 2.0. It implements 1 AudioControl interface, > -- > 1.9.1 > -- 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