On Mon, May 22, 2017 at 6:58 PM, Jassi Brar <jassisinghbrar@xxxxxxxxx> wrote: > 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, Yes, I added it after cumulative effect of this patch and patch 1/3 where I removed platform driver/device creation from f_uac2. I haven't find any rules on this and just counted changed lines, however in this particular case it's better to use something like MODULE_CLEANER(). Just joking :) I'll remove this line then. > > >> 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. I don't remember why I wrote it as "based on" back in 2016, probably looking into another patches as example. As per my understanding, "based on" + your name in Copyright means that you still continue to hold copyright on the sound card implementation (otherwise why mention it?). However I'm not a lawyer so can't say for sure. Will change this description in next patchset to make it clear, so you won't loose your copyright. Best regards Ruslan > >> + * 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-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html