// SPDX-License-Identifier: GPL-2.0+/* * u_audio.c -- interface to USB gadget "ALSA sound card" utilities * * Copyright (C) 2016 * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com> * * Sound card implementation was cut-and-pasted with changes * from f_uac2.c and has: * Copyright (C) 2011 * Yadwinder Singh (yadi.brar01@gmail.com) * Jaswinder Singh (jaswinder.singh@linaro.org) */ #include <linux/kernel.h>#include <linux/module.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/control.h>#include <sound/tlv.h>#include <linux/usb/audio.h> #include "u_audio.h" #define BUFF_SIZE_MAX (PAGE_SIZE * 16)#define PRD_SIZE_MAX PAGE_SIZE#define MIN_PERIODS 4 enum { UAC_FBACK_CTRL, UAC_P_PITCH_CTRL, UAC_MUTE_CTRL, UAC_VOLUME_CTRL, UAC_RATE_CTRL,}; /* 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 */ struct snd_pcm_substream *ss; /* Ring buffer */ ssize_t hw_ptr; void *rbuf; unsigned int pitch; /* Stream pitch ratio to 1000000 */ unsigned int max_psize; /* MaxPacketSize of endpoint */ struct usb_request **reqs; struct usb_request *req_fback; /* Feedback endpoint request */ bool fb_ep_enabled; /* if the ep is enabled */ /* Volume/Mute controls and their state */ int fu_id; /* Feature Unit ID */ struct snd_ctl_elem_id snd_kctl_volume_id; struct snd_ctl_elem_id snd_kctl_mute_id; s16 volume_min, volume_max, volume_res; s16 volume; int mute; struct snd_ctl_elem_id snd_kctl_rate_id; /* read-only current rate */ int srate; /* selected samplerate */ int active; /* playback/capture running */ spinlock_t lock; /* lock for control transfers */ }; 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; /* pre-calculated values for playback iso completion */ unsigned long long p_residue_mil; unsigned int p_interval; unsigned int p_framesize;}; static const 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_set_fback_frequency(enum usb_device_speed speed, struct usb_ep *out_ep, unsigned long long freq, unsigned int pitch, void *buf){ u32 ff = 0; const struct usb_endpoint_descriptor *ep_desc; /* * Because the pitch base is 1000000, the final divider here * will be 1000 * 1000000 = 1953125 << 9 * * Instead of dealing with big numbers lets fold this 9 left shift */ if (speed == USB_SPEED_FULL) { /* * Full-speed feedback endpoints report frequency * in samples/frame * Format is encoded in Q10.10 left-justified in the 24 bits, * so that it has a Q10.14 format. * * ff = (freq << 14) / 1000 */ freq <<= 5; } else { /* * High-speed feedback endpoints report frequency * in samples/microframe. * Format is encoded in Q12.13 fitted into four bytes so that * the binary point is located between the second and the third * byte fromat (that is Q16.16) * * ff = (freq << 16) / 8000 * * Win10 and OSX UAC2 drivers require number of samples per packet * in order to honor the feedback value. * Linux snd-usb-audio detects the applied bit-shift automatically. */ ep_desc = out_ep->desc; freq <<= 4 + (ep_desc->bInterval - 1); } ff = DIV_ROUND_CLOSEST_ULL((freq * pitch), 1953125); *(__le32 *)buf = cpu_to_le32(ff);} static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req){ unsigned int pending; unsigned int hw_ptr; int status = req->status; struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; unsigned int frames, p_pktsize; unsigned long long pitched_rate_mil, p_pktsize_residue_mil, residue_frames_mil, div_result; /* i/f shutting down */ if (!prm->ep_enabled) { usb_ep_free_request(ep, req); return; } if (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; snd_pcm_stream_lock(substream); runtime = substream->runtime; if (!runtime || !snd_pcm_running(substream)) { snd_pcm_stream_unlock(substream); goto exit; } 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. */ unsigned long long p_interval_mil = uac->p_interval * 1000000ULL; pitched_rate_mil = (unsigned long long) prm->srate * prm->pitch; div_result = pitched_rate_mil; do_div(div_result, uac->p_interval); do_div(div_result, 1000000); frames = (unsigned int) div_result; pr_debug("p_srate %d, pitch %d, interval_mil %llu, frames %d\n", prm->srate, prm->pitch, p_interval_mil, frames); p_pktsize = min_t(unsigned int, uac->p_framesize * frames, ep->maxpacket); if (p_pktsize < ep->maxpacket) { residue_frames_mil = pitched_rate_mil - frames * p_interval_mil; p_pktsize_residue_mil = uac->p_framesize * residue_frames_mil; } else p_pktsize_residue_mil = 0; req->length = p_pktsize; uac->p_residue_mil += p_pktsize_residue_mil; /* * Whenever there are more bytes in the accumulator p_residue_mil than we * need to add one more sample frame, increase this packet's * size and decrease the accumulator. */ div_result = uac->p_residue_mil; do_div(div_result, uac->p_interval); do_div(div_result, 1000000); if ((unsigned int) div_result >= uac->p_framesize) { req->length += uac->p_framesize; uac->p_residue_mil -= uac->p_framesize * p_interval_mil; pr_debug("increased req length to %d\n", req->length); } pr_debug("remains uac->p_residue_mil %llu\n", uac->p_residue_mil); req->actual = req->length; } hw_ptr = prm->hw_ptr; /* Pack USB load in ALSA ring buffer */ pending = runtime->dma_bytes - hw_ptr; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (unlikely(pending < req->actual)) { memcpy(req->buf, runtime->dma_area + hw_ptr, pending); memcpy(req->buf + pending, runtime->dma_area, req->actual - pending); } else { memcpy(req->buf, runtime->dma_area + hw_ptr, req->actual); } } else { if (unlikely(pending < req->actual)) { memcpy(runtime->dma_area + hw_ptr, req->buf, pending); memcpy(runtime->dma_area, req->buf + pending, req->actual - pending); } else { memcpy(runtime->dma_area + hw_ptr, req->buf, req->actual); } } /* update hw_ptr after data is copied to memory */ prm->hw_ptr = (hw_ptr + req->actual) % runtime->dma_bytes; hw_ptr = prm->hw_ptr; snd_pcm_stream_unlock(substream); if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < req->actual) snd_pcm_period_elapsed(substream); exit: if (usb_ep_queue(ep, req, GFP_ATOMIC)) dev_err(uac->card->dev, "%d Error!\n", __LINE__);} static void u_audio_iso_fback_complete(struct usb_ep *ep, struct usb_request *req){ struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; struct g_audio *audio_dev = uac->audio_dev; int status = req->status; /* i/f shutting down */ if (!prm->fb_ep_enabled) { kfree(req->buf); usb_ep_free_request(ep, req); return; } if (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); u_audio_set_fback_frequency(audio_dev->gadget->speed, audio_dev->out_ep, prm->srate, prm->pitch, req->buf); if (usb_ep_queue(ep, req, GFP_ATOMIC)) dev_err(uac->card->dev, "%d Error!\n", __LINE__);} 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; 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; /* 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; } /* 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 u64 uac_ssize_to_fmt(int ssize){ u64 ret; switch (ssize) { case 3: ret = SNDRV_PCM_FMTBIT_S24_3LE; break; case 4: ret = SNDRV_PCM_FMTBIT_S32_LE; break; default: ret = SNDRV_PCM_FMTBIT_S16_LE; break; } return ret;} 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; struct uac_rtd_params *prm; int p_ssize, c_ssize; 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_chmask = params->p_chmask; c_chmask = params->c_chmask; uac->p_residue_mil = 0; runtime->hw = uac_pcm_hardware; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { runtime->hw.formats = uac_ssize_to_fmt(p_ssize); runtime->hw.channels_min = num_channels(p_chmask); prm = &uac->p_prm; } else { runtime->hw.formats = uac_ssize_to_fmt(c_ssize); runtime->hw.channels_min = num_channels(c_chmask); prm = &uac->c_prm; } runtime->hw.period_bytes_min = 2 * prm->max_psize / runtime->hw.periods_min; runtime->hw.rate_min = prm->srate; 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 const struct snd_pcm_ops uac_pcm_ops = { .open = uac_pcm_open, .close = uac_pcm_null, .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; audio_dev = uac->audio_dev; params = &audio_dev->params; for (i = 0; i < params->req_number; i++) { if (prm->reqs[i]) { if (usb_ep_dequeue(ep, prm->reqs[i])) usb_ep_free_request(ep, prm->reqs[i]); /* * If usb_ep_dequeue() cannot successfully dequeue the * request, the request will be freed by the completion * callback. */ prm->reqs[i] = NULL; } } prm->ep_enabled = false; if (usb_ep_disable(ep)) dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);} static inline void free_ep_fback(struct uac_rtd_params *prm, struct usb_ep *ep){ struct snd_uac_chip *uac = prm->uac; if (!prm->fb_ep_enabled) return; if (prm->req_fback) { if (usb_ep_dequeue(ep, prm->req_fback)) { kfree(prm->req_fback->buf); usb_ep_free_request(ep, prm->req_fback); } prm->req_fback = NULL; } prm->fb_ep_enabled = false; if (usb_ep_disable(ep)) dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);} static void set_active(struct uac_rtd_params *prm, bool active){ // notifying through the Rate ctrl unsigned long flags; spin_lock_irqsave(&prm->lock, flags); if (prm->active != active) {Lines 1–500 of 1481. Load more
// SPDX-License-Identifier: GPL-2.0+ /* * u_audio.c -- interface to USB gadget "ALSA sound card" utilities * * Copyright (C) 2016 * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com> * * Sound card implementation was cut-and-pasted with changes * from f_uac2.c and has: * Copyright (C) 2011 * Yadwinder Singh (yadi.brar01@gmail.com) * Jaswinder Singh (jaswinder.singh@linaro.org) */ #include <linux/kernel.h> #include <linux/module.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/control.h> #include <sound/tlv.h> #include <linux/usb/audio.h> #include "u_audio.h" #define BUFF_SIZE_MAX (PAGE_SIZE * 16) #define PRD_SIZE_MAX PAGE_SIZE #define MIN_PERIODS 4 enum { UAC_FBACK_CTRL, UAC_P_PITCH_CTRL, UAC_MUTE_CTRL, UAC_VOLUME_CTRL, UAC_RATE_CTRL, }; /* 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 */ struct snd_pcm_substream *ss; /* Ring buffer */ ssize_t hw_ptr; void *rbuf; unsigned int pitch; /* Stream pitch ratio to 1000000 */ unsigned int max_psize; /* MaxPacketSize of endpoint */ struct usb_request **reqs; struct usb_request *req_fback; /* Feedback endpoint request */ bool fb_ep_enabled; /* if the ep is enabled */ /* Volume/Mute controls and their state */ int fu_id; /* Feature Unit ID */ struct snd_ctl_elem_id snd_kctl_volume_id; struct snd_ctl_elem_id snd_kctl_mute_id; s16 volume_min, volume_max, volume_res; s16 volume; int mute; struct snd_ctl_elem_id snd_kctl_rate_id; /* read-only current rate */ int srate; /* selected samplerate */ int active; /* playback/capture running */ spinlock_t lock; /* lock for control transfers */ }; 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; /* pre-calculated values for playback iso completion */ unsigned long long p_residue_mil; unsigned int p_interval; unsigned int p_framesize; }; static const 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_set_fback_frequency(enum usb_device_speed speed, struct usb_ep *out_ep, unsigned long long freq, unsigned int pitch, void *buf) { u32 ff = 0; const struct usb_endpoint_descriptor *ep_desc; /* * Because the pitch base is 1000000, the final divider here * will be 1000 * 1000000 = 1953125 << 9 * * Instead of dealing with big numbers lets fold this 9 left shift */ if (speed == USB_SPEED_FULL) { /* * Full-speed feedback endpoints report frequency * in samples/frame * Format is encoded in Q10.10 left-justified in the 24 bits, * so that it has a Q10.14 format. * * ff = (freq << 14) / 1000 */ freq <<= 5; } else { /* * High-speed feedback endpoints report frequency * in samples/microframe. * Format is encoded in Q12.13 fitted into four bytes so that * the binary point is located between the second and the third * byte fromat (that is Q16.16) * * ff = (freq << 16) / 8000 * * Win10 and OSX UAC2 drivers require number of samples per packet * in order to honor the feedback value. * Linux snd-usb-audio detects the applied bit-shift automatically. */ ep_desc = out_ep->desc; freq <<= 4 + (ep_desc->bInterval - 1); } ff = DIV_ROUND_CLOSEST_ULL((freq * pitch), 1953125); *(__le32 *)buf = cpu_to_le32(ff); } static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) { unsigned int pending; unsigned int hw_ptr; int status = req->status; struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; unsigned int frames, p_pktsize; unsigned long long pitched_rate_mil, p_pktsize_residue_mil, residue_frames_mil, div_result; /* i/f shutting down */ if (!prm->ep_enabled) { usb_ep_free_request(ep, req); return; } if (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; snd_pcm_stream_lock(substream); runtime = substream->runtime; if (!runtime || !snd_pcm_running(substream)) { snd_pcm_stream_unlock(substream); goto exit; } 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. */ unsigned long long p_interval_mil = uac->p_interval * 1000000ULL; pitched_rate_mil = (unsigned long long) prm->srate * prm->pitch; div_result = pitched_rate_mil; do_div(div_result, uac->p_interval); do_div(div_result, 1000000); frames = (unsigned int) div_result; pr_debug("p_srate %d, pitch %d, interval_mil %llu, frames %d\n", prm->srate, prm->pitch, p_interval_mil, frames); p_pktsize = min_t(unsigned int, uac->p_framesize * frames, ep->maxpacket); if (p_pktsize < ep->maxpacket) { residue_frames_mil = pitched_rate_mil - frames * p_interval_mil; p_pktsize_residue_mil = uac->p_framesize * residue_frames_mil; } else p_pktsize_residue_mil = 0; req->length = p_pktsize; uac->p_residue_mil += p_pktsize_residue_mil; /* * Whenever there are more bytes in the accumulator p_residue_mil than we * need to add one more sample frame, increase this packet's * size and decrease the accumulator. */ div_result = uac->p_residue_mil; do_div(div_result, uac->p_interval); do_div(div_result, 1000000); if ((unsigned int) div_result >= uac->p_framesize) { req->length += uac->p_framesize; uac->p_residue_mil -= uac->p_framesize * p_interval_mil; pr_debug("increased req length to %d\n", req->length); } pr_debug("remains uac->p_residue_mil %llu\n", uac->p_residue_mil); req->actual = req->length; } hw_ptr = prm->hw_ptr; /* Pack USB load in ALSA ring buffer */ pending = runtime->dma_bytes - hw_ptr; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (unlikely(pending < req->actual)) { memcpy(req->buf, runtime->dma_area + hw_ptr, pending); memcpy(req->buf + pending, runtime->dma_area, req->actual - pending); } else { memcpy(req->buf, runtime->dma_area + hw_ptr, req->actual); } } else { if (unlikely(pending < req->actual)) { memcpy(runtime->dma_area + hw_ptr, req->buf, pending); memcpy(runtime->dma_area, req->buf + pending, req->actual - pending); } else { memcpy(runtime->dma_area + hw_ptr, req->buf, req->actual); } } /* update hw_ptr after data is copied to memory */ prm->hw_ptr = (hw_ptr + req->actual) % runtime->dma_bytes; hw_ptr = prm->hw_ptr; snd_pcm_stream_unlock(substream); if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < req->actual) snd_pcm_period_elapsed(substream); exit: if (usb_ep_queue(ep, req, GFP_ATOMIC)) dev_err(uac->card->dev, "%d Error!\n", __LINE__); } static void u_audio_iso_fback_complete(struct usb_ep *ep, struct usb_request *req) { struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; struct g_audio *audio_dev = uac->audio_dev; int status = req->status; /* i/f shutting down */ if (!prm->fb_ep_enabled) { kfree(req->buf); usb_ep_free_request(ep, req); return; } if (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); u_audio_set_fback_frequency(audio_dev->gadget->speed, audio_dev->out_ep, prm->srate, prm->pitch, req->buf); if (usb_ep_queue(ep, req, GFP_ATOMIC)) dev_err(uac->card->dev, "%d Error!\n", __LINE__); } 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; 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; /* 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; } /* 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 u64 uac_ssize_to_fmt(int ssize) { u64 ret; switch (ssize) { case 3: ret = SNDRV_PCM_FMTBIT_S24_3LE; break; case 4: ret = SNDRV_PCM_FMTBIT_S32_LE; break; default: ret = SNDRV_PCM_FMTBIT_S16_LE; break; } return ret; } 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; struct uac_rtd_params *prm; int p_ssize, c_ssize; 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_chmask = params->p_chmask; c_chmask = params->c_chmask; uac->p_residue_mil = 0; runtime->hw = uac_pcm_hardware; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { runtime->hw.formats = uac_ssize_to_fmt(p_ssize); runtime->hw.channels_min = num_channels(p_chmask); prm = &uac->p_prm; } else { runtime->hw.formats = uac_ssize_to_fmt(c_ssize); runtime->hw.channels_min = num_channels(c_chmask); prm = &uac->c_prm; } runtime->hw.period_bytes_min = 2 * prm->max_psize / runtime->hw.periods_min; runtime->hw.rate_min = prm->srate; 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 const struct snd_pcm_ops uac_pcm_ops = { .open = uac_pcm_open, .close = uac_pcm_null, .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; audio_dev = uac->audio_dev; params = &audio_dev->params; for (i = 0; i < params->req_number; i++) { if (prm->reqs[i]) { if (usb_ep_dequeue(ep, prm->reqs[i])) usb_ep_free_request(ep, prm->reqs[i]); /* * If usb_ep_dequeue() cannot successfully dequeue the * request, the request will be freed by the completion * callback. */ prm->reqs[i] = NULL; } } prm->ep_enabled = false; if (usb_ep_disable(ep)) dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); } static inline void free_ep_fback(struct uac_rtd_params *prm, struct usb_ep *ep) { struct snd_uac_chip *uac = prm->uac; if (!prm->fb_ep_enabled) return; if (prm->req_fback) { if (usb_ep_dequeue(ep, prm->req_fback)) { kfree(prm->req_fback->buf); usb_ep_free_request(ep, prm->req_fback); } prm->req_fback = NULL; } prm->fb_ep_enabled = false; if (usb_ep_disable(ep)) dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); } static void set_active(struct uac_rtd_params *prm, bool active) { // notifying through the Rate ctrl unsigned long flags; spin_lock_irqsave(&prm->lock, flags); if (prm->active != active) {