#include <linux/init.h>
#include <linux/slab.h>
#include <linux/bitrev.h>
#include <linux/ratelimit.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "usbaudio.h"
#include "card.h"
#include "quirks.h"
#include "endpoint.h"
#include "helper.h"
#include "pcm.h"
#include "clock.h"
#include "power.h"
#include "media.h"
#include "implicit.h"
#define SUBSTREAM_FLAG_DATA_EP_STARTED 0
#define SUBSTREAM_FLAG_SYNC_EP_STARTED 1
static snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
struct snd_pcm_runtime *runtime)
{
unsigned int current_frame_number;
unsigned int frame_diff;
int est_delay;
int queued;
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
queued = bytes_to_frames(runtime, subs->inflight_bytes);
if (!queued)
return 0;
} else if (!subs->running) {
return 0;
}
current_frame_number = usb_get_current_frame_number(subs->dev);
frame_diff = (current_frame_number - subs->last_frame_number) & 0xff;
est_delay = frame_diff * runtime->rate / 1000;
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
est_delay = queued - est_delay;
if (est_delay < 0)
est_delay = 0;
}
return est_delay;
}
static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usb_substream *subs = runtime->private_data;
unsigned int hwptr_done;
if (atomic_read(&subs->stream->chip->shutdown))
return SNDRV_PCM_POS_XRUN;
spin_lock(&subs->lock);
hwptr_done = subs->hwptr_done;
runtime->delay = snd_usb_pcm_delay(subs, runtime);
spin_unlock(&subs->lock);
return bytes_to_frames(runtime, hwptr_done);
}
static const struct audioformat *
find_format(struct list_head *fmt_list_head, snd_pcm_format_t format,
unsigned int rate, unsigned int channels, bool strict_match,
struct snd_usb_substream *subs)
{
const struct audioformat *fp;
const struct audioformat *found = NULL;
int cur_attr = 0, attr;
list_for_each_entry(fp, fmt_list_head, list) {
if (strict_match) {
if (!(fp->formats & pcm_format_to_bits(format)))
continue;
if (fp->channels != channels)
continue;
}
if (rate < fp->rate_min || rate > fp->rate_max)
continue;
if (!(fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) {
unsigned int i;
for (i = 0; i < fp->nr_rates; i++)
if (fp->rate_table[i] == rate)
break;
if (i >= fp->nr_rates)
continue;
}
attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE;
if (!found) {
found = fp;
cur_attr = attr;
continue;
}
if (subs && attr != cur_attr) {
if ((attr == USB_ENDPOINT_SYNC_ASYNC &&
subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
(attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
subs->direction == SNDRV_PCM_STREAM_CAPTURE))
continue;
if ((cur_attr == USB_ENDPOINT_SYNC_ASYNC &&
subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
(cur_attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
subs->direction == SNDRV_PCM_STREAM_CAPTURE)) {
found = fp;
cur_attr = attr;
continue;
}
}
if (fp->maxpacksize > found->maxpacksize) {
found = fp;
cur_attr = attr;
}
}
return found;
}
static const struct audioformat *
find_substream_format(struct snd_usb_substream *subs,
const struct snd_pcm_hw_params *params)
{
return find_format(&subs->fmt_list, params_format(params),
params_rate(params), params_channels(params),
true, subs);
}
bool snd_usb_pcm_has_fixed_rate(struct snd_usb_substream *subs)
{
const struct audioformat *fp;
struct snd_usb_audio *chip;
int rate = -1;
if (!subs)
return false;
chip = subs->stream->chip;
if (!(chip->quirk_flags & QUIRK_FLAG_FIXED_RATE))
return false;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)
return false;
if (fp->nr_rates < 1)
continue;
if (fp->nr_rates > 1)
return false;
if (rate < 0) {
rate = fp->rate_table[0];
continue;
}
if (rate != fp->rate_table[0])
return false;
}
return true;
}
static int init_pitch_v1(struct snd_usb_audio *chip, int ep)
{
struct usb_device *dev = chip->dev;
unsigned char data[1];
int err;
data[0] = 1;
err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep,
data, sizeof(data));
return err;
}
static int init_pitch_v2(struct snd_usb_audio *chip, int ep)
{
struct usb_device *dev = chip->dev;
unsigned char data[1];
int err;
data[0] = 1;
err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
UAC2_EP_CS_PITCH << 8, 0,
data, sizeof(data));
return err;
}
int snd_usb_init_pitch(struct snd_usb_audio *chip,
const struct audioformat *fmt)
{
int err;
if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL))
return 0;
usb_audio_dbg(chip, "enable PITCH for EP 0x%x\n", fmt->endpoint);
switch (fmt->protocol) {
case UAC_VERSION_1:
err = init_pitch_v1(chip, fmt->endpoint);
break;
case UAC_VERSION_2:
err = init_pitch_v2(chip, fmt->endpoint);
break;
default:
return 0;
}
if (err < 0) {
usb_audio_err(chip, "failed to enable PITCH for EP 0x%x\n",
fmt->endpoint);
return err;
}
return 0;
}
static bool stop_endpoints(struct snd_usb_substream *subs, bool keep_pending)
{
bool stopped = 0;
if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {
snd_usb_endpoint_stop(subs->sync_endpoint, keep_pending);
stopped = true;
}
if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) {
snd_usb_endpoint_stop(subs->data_endpoint, keep_pending);
stopped = true;
}
return stopped;
}
static int start_endpoints(struct snd_usb_substream *subs)
{
int err;
if (!subs->data_endpoint)
return -EINVAL;
if (!test_and_set_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) {
err = snd_usb_endpoint_start(subs->data_endpoint);
if (err < 0) {
clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags);
goto error;
}
}
if (subs->sync_endpoint &&
!test_and_set_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {
err = snd_usb_endpoint_start(subs->sync_endpoint);
if (err < 0) {
clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);
goto error;
}
}
return 0;
error:
stop_endpoints(subs, false);
return err;
}
static void sync_pending_stops(struct snd_usb_substream *subs)
{
snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint);
snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
}
static int snd_usb_pcm_sync_stop(struct snd_pcm_substream *substream)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
sync_pending_stops(subs);
return 0;
}
int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip,
struct audioformat *fmt)
{
struct usb_device *dev = chip->dev;
struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
unsigned int ep, attr, sync_attr;
bool is_playback;
int err;
if (fmt->sync_ep)
return 0;
alts = snd_usb_get_host_interface(chip, fmt->iface, fmt->altsetting);
if (!alts)
return 0;
altsd = get_iface_desc(alts);
err = snd_usb_parse_implicit_fb_quirk(chip, fmt, alts);
if (err > 0)
return 0;
if (fmt->ep_idx > 0 || altsd->bNumEndpoints < 2)
return 0;
is_playback = !(get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN);
attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE;
if ((is_playback && (attr == USB_ENDPOINT_SYNC_SYNC ||
attr == USB_ENDPOINT_SYNC_ADAPTIVE)) ||
(!is_playback && attr != USB_ENDPOINT_SYNC_ADAPTIVE))
return 0;
sync_attr = get_endpoint(alts, 1)->bmAttributes;
if ((sync_attr & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC ||
(get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
get_endpoint(alts, 1)->bSynchAddress != 0)) {
dev_err(&dev->dev,
"%d:%d : invalid sync pipe. bmAttributes %02x, bLength %d, bSynchAddress %02x\n",
fmt->iface, fmt->altsetting,
get_endpoint(alts, 1)->bmAttributes,
get_endpoint(alts, 1)->bLength,
get_endpoint(alts, 1)->bSynchAddress);
if (is_playback && attr == USB_ENDPOINT_SYNC_NONE)
return 0;
return -EINVAL;
}
ep = get_endpoint(alts, 1)->bEndpointAddress;
if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
get_endpoint(alts, 0)->bSynchAddress != 0 &&
((is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) ||
(!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) {
dev_err(&dev->dev,
"%d:%d : invalid sync pipe. is_playback %d, ep %02x, bSynchAddress %02x\n",
fmt->iface, fmt->altsetting,
is_playback, ep, get_endpoint(alts, 0)->bSynchAddress);
if (is_playback && attr == USB_ENDPOINT_SYNC_NONE)
return 0;
return -EINVAL;
}
fmt->sync_ep = ep;
fmt->sync_iface = altsd->bInterfaceNumber;
fmt->sync_altsetting = altsd->bAlternateSetting;
fmt->sync_ep_idx = 1;
if ((sync_attr & USB_ENDPOINT_USAGE_MASK) == USB_ENDPOINT_USAGE_IMPLICIT_FB)
fmt->implicit_fb = 1;
dev_dbg(&dev->dev, "%d:%d: found sync_ep=0x%x, iface=%d, alt=%d, implicit_fb=%d\n",
fmt->iface, fmt->altsetting, fmt->sync_ep, fmt->sync_iface,
fmt->sync_altsetting, fmt->implicit_fb);
return 0;
}
static int snd_usb_pcm_change_state(struct snd_usb_substream *subs, int state)
{
int ret;
if (!subs->str_pd)
return 0;
ret = snd_usb_power_domain_set(subs->stream->chip, subs->str_pd, state);
if (ret < 0) {
dev_err(&subs->dev->dev,
"Cannot change Power Domain ID: %d to state: %d. Err: %d\n",
subs->str_pd->pd_id, state, ret);
return ret;
}
return 0;
}
int snd_usb_pcm_suspend(struct snd_usb_stream *as)
{
int ret;
ret = snd_usb_pcm_change_state(&as->substream[0], UAC3_PD_STATE_D2);
if (ret < 0)
return ret;
ret = snd_usb_pcm_change_state(&as->substream[1], UAC3_PD_STATE_D2);
if (ret < 0)
return ret;
return 0;
}
int snd_usb_pcm_resume(struct snd_usb_stream *as)
{
int ret;
ret = snd_usb_pcm_change_state(&as->substream[0], UAC3_PD_STATE_D1);
if (ret < 0)
return ret;
ret = snd_usb_pcm_change_state(&as->substream[1], UAC3_PD_STATE_D1);
if (ret < 0)
return ret;
return 0;
}
static void close_endpoints(struct snd_usb_audio *chip,
struct snd_usb_substream *subs)
{
if (subs->data_endpoint) {
snd_usb_endpoint_set_sync(chip, subs->data_endpoint, NULL);
snd_usb_endpoint_close(chip, subs->data_endpoint);
subs->data_endpoint = NULL;
}
if (subs->sync_endpoint) {
snd_usb_endpoint_close(chip, subs->sync_endpoint);
subs->sync_endpoint = NULL;
}
}
static int snd_usb_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
struct snd_usb_audio *chip = subs->stream->chip;
const struct audioformat *fmt;
const struct audioformat *sync_fmt;
bool fixed_rate, sync_fixed_rate;
int ret;
ret = snd_media_start_pipeline(subs);
if (ret)
return ret;
fixed_rate = snd_usb_pcm_has_fixed_rate(subs);
fmt = find_substream_format(subs, hw_params);
if (!fmt) {
usb_audio_dbg(chip,
"cannot find format: format=%s, rate=%d, channels=%d\n",
snd_pcm_format_name(params_format(hw_params)),
params_rate(hw_params), params_channels(hw_params));
ret = -EINVAL;
goto stop_pipeline;
}
if (fmt->implicit_fb) {
sync_fmt = snd_usb_find_implicit_fb_sync_format(chip, fmt,
hw_params,
!substream->stream,
&sync_fixed_rate);
if (!sync_fmt) {
usb_audio_dbg(chip,
"cannot find sync format: ep=0x%x, iface=%d:%d, format=%s, rate=%d, channels=%d\n",
fmt->sync_ep, fmt->sync_iface,
fmt->sync_altsetting,
snd_pcm_format_name(params_format(hw_params)),
params_rate(hw_params), params_channels(hw_params));
ret = -EINVAL;
goto stop_pipeline;
}
} else {
sync_fmt = fmt;
sync_fixed_rate = fixed_rate;
}
ret = snd_usb_lock_shutdown(chip);
if (ret < 0)
goto stop_pipeline;
ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0);
if (ret < 0)
goto unlock;
if (subs->data_endpoint) {
if (snd_usb_endpoint_compatible(chip, subs->data_endpoint,
fmt, hw_params))
goto unlock;
if (stop_endpoints(subs, false))
sync_pending_stops(subs);
close_endpoints(chip, subs);
}
subs->data_endpoint = snd_usb_endpoint_open(chip, fmt, hw_params, false, fixed_rate);
if (!subs->data_endpoint) {
ret = -EINVAL;
goto unlock;
}
if (fmt->sync_ep) {
subs->sync_endpoint = snd_usb_endpoint_open(chip, sync_fmt,
hw_params,
fmt == sync_fmt,
sync_fixed_rate);
if (!subs->sync_endpoint) {
ret = -EINVAL;
goto unlock;
}
snd_usb_endpoint_set_sync(chip, subs->data_endpoint,
subs->sync_endpoint);
}
mutex_lock(&chip->mutex);
subs->cur_audiofmt = fmt;
mutex_unlock(&chip->mutex);
if (!subs->data_endpoint->need_setup)
goto unlock;
if (subs->sync_endpoint) {
ret = snd_usb_endpoint_set_params(chip, subs->sync_endpoint);
if (ret < 0)
goto unlock;
}
ret = snd_usb_endpoint_set_params(chip, subs->data_endpoint);
unlock:
if (ret < 0)
close_endpoints(chip, subs);
snd_usb_unlock_shutdown(chip);
stop_pipeline:
if (ret < 0)
snd_media_stop_pipeline(subs);
return ret;
}
static int snd_usb_hw_free(struct snd_pcm_substream *substream)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
struct snd_usb_audio *chip = subs->stream->chip;
snd_media_stop_pipeline(subs);
mutex_lock(&chip->mutex);
subs->cur_audiofmt = NULL;
mutex_unlock(&chip->mutex);
if (!snd_usb_lock_shutdown(chip)) {
if (stop_endpoints(subs, false))
sync_pending_stops(subs);
close_endpoints(chip, subs);
snd_usb_unlock_shutdown(chip);
}
return 0;
}
static int in_free_wheeling_mode(struct snd_pcm_runtime *runtime)
{
return runtime->stop_threshold > runtime->buffer_size;
}
static int lowlatency_playback_available(struct snd_pcm_runtime *runtime,
struct snd_usb_substream *subs)
{
struct snd_usb_audio *chip = subs->stream->chip;
if (subs->direction == SNDRV_PCM_STREAM_CAPTURE)
return false;
if (!chip->lowlatency)
return false;
if (in_free_wheeling_mode(runtime))
return false;
if (snd_usb_endpoint_implicit_feedback_sink(subs->data_endpoint))
return false;
return true;
}
static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usb_substream *subs = runtime->private_data;
struct snd_usb_audio *chip = subs->stream->chip;
int retry = 0;
int ret;
ret = snd_usb_lock_shutdown(chip);
if (ret < 0)
return ret;
if (snd_BUG_ON(!subs->data_endpoint)) {
ret = -EIO;
goto unlock;
}
ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0);
if (ret < 0)
goto unlock;
again:
if (subs->sync_endpoint) {
ret = snd_usb_endpoint_prepare(chip, subs->sync_endpoint);
if (ret < 0)
goto unlock;
}
ret = snd_usb_endpoint_prepare(chip, subs->data_endpoint);
if (ret < 0)
goto unlock;
else if (ret > 0)
snd_usb_set_format_quirk(subs, subs->cur_audiofmt);
ret = 0;
subs->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size);
subs->inflight_bytes = 0;
subs->hwptr_done = 0;
subs->transfer_done = 0;
subs->last_frame_number = 0;
subs->period_elapsed_pending = 0;
runtime->delay = 0;
subs->lowlatency_playback = lowlatency_playback_available(runtime, subs);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
!subs->lowlatency_playback) {
ret = start_endpoints(subs);
if (ret == -EPIPE && !retry++) {
sync_pending_stops(subs);
goto again;
}
}
unlock:
snd_usb_unlock_shutdown(chip);
return ret;
}
#ifdef HW_CONST_DEBUG
#define hwc_debug(fmt, args...) pr_debug(fmt, ##args)
#else
#define hwc_debug(fmt, args...) do { } while(0)
#endif
static const struct snd_pcm_hardware snd_usb_hardware =
{
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_PAUSE,
.channels_min = 1,
.channels_max = 256,
.buffer_bytes_max = INT_MAX,
.period_bytes_min = 64,
.period_bytes_max = INT_MAX,
.periods_min = 2,
.periods_max = 1024,
};
static int hw_check_valid_format(struct snd_usb_substream *subs,
struct snd_pcm_hw_params *params,
const struct audioformat *fp)
{
struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
struct snd_mask check_fmts;
unsigned int ptime;
snd_mask_none(&check_fmts);
check_fmts.bits[0] = (u32)fp->formats;
check_fmts.bits[1] = (u32)(fp->formats >> 32);
snd_mask_intersect(&check_fmts, fmts);
if (snd_mask_empty(&check_fmts)) {
hwc_debug(" > check: no supported format 0x%llx\n", fp->formats);
return 0;
}
if (fp->channels < ct->min || fp->channels > ct->max) {
hwc_debug(" > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max);
return 0;
}
if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) {
hwc_debug(" > check: rate_min %d > max %d\n", fp->rate_min, it->max);
return 0;
}
if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) {
hwc_debug(" > check: rate_max %d < min %d\n", fp->rate_max, it->min);
return 0;
}
if (subs->speed != USB_SPEED_FULL) {
ptime = 125 * (1 << fp->datainterval);
if (ptime > pt->max || (ptime == pt->max && pt->openmax)) {
hwc_debug(" > check: ptime %u > max %u\n", ptime, pt->max);
return 0;
}
}
return 1;
}
static int apply_hw_params_minmax(struct snd_interval *it, unsigned int rmin,
unsigned int rmax)
{
int changed;
if (rmin > rmax) {
hwc_debug(" --> get empty\n");
it->empty = 1;
return -EINVAL;
}
changed = 0;
if (it->min < rmin) {
it->min = rmin;
it->openmin = 0;
changed = 1;
}
if (it->max > rmax) {
it->max = rmax;
it->openmax = 0;
changed = 1;
}
if (snd_interval_checkempty(it)) {
it->empty = 1;
return -EINVAL;
}
hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed);
return changed;
}
static const struct snd_usb_endpoint *
get_endpoint_in_use(struct snd_usb_audio *chip, int endpoint,
const struct snd_usb_endpoint *ref_ep)
{
const struct snd_usb_endpoint *ep;
ep = snd_usb_get_endpoint(chip, endpoint);
if (ep && ep->cur_audiofmt && (ep != ref_ep || ep->opened > 1))
return ep;
return NULL;
}
static int hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_usb_substream *subs = rule->private;
struct snd_usb_audio *chip = subs->stream->chip;
const struct snd_usb_endpoint *ep;
const struct audioformat *fp;
struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
unsigned int rmin, rmax, r;
int i;
hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max);
rmin = UINT_MAX;
rmax = 0;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (!hw_check_valid_format(subs, params, fp))
continue;
ep = get_endpoint_in_use(chip, fp->endpoint,
subs->data_endpoint);
if (ep) {
hwc_debug("rate limit %d for ep#%x\n",
ep->cur_rate, fp->endpoint);
rmin = min(rmin, ep->cur_rate);
rmax = max(rmax, ep->cur_rate);
continue;
}
if (fp->implicit_fb) {
ep = get_endpoint_in_use(chip, fp->sync_ep,
subs->sync_endpoint);
if (ep) {
hwc_debug("rate limit %d for sync_ep#%x\n",
ep->cur_rate, fp->sync_ep);
rmin = min(rmin, ep->cur_rate);
rmax = max(rmax, ep->cur_rate);
continue;
}
}
r = snd_usb_endpoint_get_clock_rate(chip, fp->clock);
if (r > 0) {
if (!snd_interval_test(it, r))
continue;
rmin = min(rmin, r);
rmax = max(rmax, r);
continue;
}
if (fp->rate_table && fp->nr_rates) {
for (i = 0; i < fp->nr_rates; i++) {
r = fp->rate_table[i];
if (!snd_interval_test(it, r))
continue;
rmin = min(rmin, r);
rmax = max(rmax, r);
}
} else {
rmin = min(rmin, fp->rate_min);
rmax = max(rmax, fp->rate_max);
}
}
return apply_hw_params_minmax(it, rmin, rmax);
}
static int hw_rule_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_usb_substream *subs = rule->private;
const struct audioformat *fp;
struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
unsigned int rmin, rmax;
hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max);
rmin = UINT_MAX;
rmax = 0;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (!hw_check_valid_format(subs, params, fp))
continue;
rmin = min(rmin, fp->channels);
rmax = max(rmax, fp->channels);
}
return apply_hw_params_minmax(it, rmin, rmax);
}
static int apply_hw_params_format_bits(struct snd_mask *fmt, u64 fbits)
{
u32 oldbits[2];
int changed;
oldbits[0] = fmt->bits[0];
oldbits[1] = fmt->bits[1];
fmt->bits[0] &= (u32)fbits;
fmt->bits[1] &= (u32)(fbits >> 32);
if (!fmt->bits[0] && !fmt->bits[1]) {
hwc_debug(" --> get empty\n");
return -EINVAL;
}
changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]);
hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed);
return changed;
}
static int hw_rule_format(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_usb_substream *subs = rule->private;
struct snd_usb_audio *chip = subs->stream->chip;
const struct snd_usb_endpoint *ep;
const struct audioformat *fp;
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
u64 fbits;
hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]);
fbits = 0;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (!hw_check_valid_format(subs, params, fp))
continue;
ep = get_endpoint_in_use(chip, fp->endpoint,
subs->data_endpoint);
if (ep) {
hwc_debug("format limit %d for ep#%x\n",
ep->cur_format, fp->endpoint);
fbits |= pcm_format_to_bits(ep->cur_format);
continue;
}
if (fp->implicit_fb) {
ep = get_endpoint_in_use(chip, fp->sync_ep,
subs->sync_endpoint);
if (ep) {
hwc_debug("format limit %d for sync_ep#%x\n",
ep->cur_format, fp->sync_ep);
fbits |= pcm_format_to_bits(ep->cur_format);
continue;
}
}
fbits |= fp->formats;
}
return apply_hw_params_format_bits(fmt, fbits);
}
static int hw_rule_period_time(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_usb_substream *subs = rule->private;
const struct audioformat *fp;
struct snd_interval *it;
unsigned char min_datainterval;
unsigned int pmin;
it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max);
min_datainterval = 0xff;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (!hw_check_valid_format(subs, params, fp))
continue;
min_datainterval = min(min_datainterval, fp->datainterval);
}
if (min_datainterval == 0xff) {
hwc_debug(" --> get empty\n");
it->empty = 1;
return -EINVAL;
}
pmin = 125 * (1 << min_datainterval);
return apply_hw_params_minmax(it, pmin, UINT_MAX);
}
static int hw_rule_period_size_implicit_fb(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_usb_substream *subs = rule->private;
struct snd_usb_audio *chip = subs->stream->chip;
const struct audioformat *fp;
const struct snd_usb_endpoint *ep;
struct snd_interval *it;
unsigned int rmin, rmax;
it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
hwc_debug("hw_rule_period_size: (%u,%u)\n", it->min, it->max);
rmin = UINT_MAX;
rmax = 0;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (!hw_check_valid_format(subs, params, fp))
continue;
ep = get_endpoint_in_use(chip, fp->endpoint,
subs->data_endpoint);
if (ep) {
hwc_debug("period size limit %d for ep#%x\n",
ep->cur_period_frames, fp->endpoint);
rmin = min(rmin, ep->cur_period_frames);
rmax = max(rmax, ep->cur_period_frames);
continue;
}
if (fp->implicit_fb) {
ep = get_endpoint_in_use(chip, fp->sync_ep,
subs->sync_endpoint);
if (ep) {
hwc_debug("period size limit %d for sync_ep#%x\n",
ep->cur_period_frames, fp->sync_ep);
rmin = min(rmin, ep->cur_period_frames);
rmax = max(rmax, ep->cur_period_frames);
continue;
}
}
}
if (!rmax)
return 0;
return apply_hw_params_minmax(it, rmin, rmax);
}
static int hw_rule_periods_implicit_fb(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_usb_substream *subs = rule->private;
struct snd_usb_audio *chip = subs->stream->chip;
const struct audioformat *fp;
const struct snd_usb_endpoint *ep;
struct snd_interval *it;
unsigned int rmin, rmax;
it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIODS);
hwc_debug("hw_rule_periods: (%u,%u)\n", it->min, it->max);
rmin = UINT_MAX;
rmax = 0;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (!hw_check_valid_format(subs, params, fp))
continue;
ep = get_endpoint_in_use(chip, fp->endpoint,
subs->data_endpoint);
if (ep) {
hwc_debug("periods limit %d for ep#%x\n",
ep->cur_buffer_periods, fp->endpoint);
rmin = min(rmin, ep->cur_buffer_periods);
rmax = max(rmax, ep->cur_buffer_periods);
continue;
}
if (fp->implicit_fb) {
ep = get_endpoint_in_use(chip, fp->sync_ep,
subs->sync_endpoint);
if (ep) {
hwc_debug("periods limit %d for sync_ep#%x\n",
ep->cur_buffer_periods, fp->sync_ep);
rmin = min(rmin, ep->cur_buffer_periods);
rmax = max(rmax, ep->cur_buffer_periods);
continue;
}
}
}
if (!rmax)
return 0;
return apply_hw_params_minmax(it, rmin, rmax);
}
static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs)
{
const struct audioformat *fp;
unsigned int pt, ptmin;
int param_period_time_if_needed = -1;
int err;
runtime->hw.formats = subs->formats;
runtime->hw.rate_min = 0x7fffffff;
runtime->hw.rate_max = 0;
runtime->hw.channels_min = 256;
runtime->hw.channels_max = 0;
runtime->hw.rates = 0;
ptmin = UINT_MAX;
list_for_each_entry(fp, &subs->fmt_list, list) {
runtime->hw.rates |= fp->rates;
if (runtime->hw.rate_min > fp->rate_min)
runtime->hw.rate_min = fp->rate_min;
if (runtime->hw.rate_max < fp->rate_max)
runtime->hw.rate_max = fp->rate_max;
if (runtime->hw.channels_min > fp->channels)
runtime->hw.channels_min = fp->channels;
if (runtime->hw.channels_max < fp->channels)
runtime->hw.channels_max = fp->channels;
if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) {
runtime->hw.period_bytes_min = runtime->hw.period_bytes_max =
fp->frame_size;
}
pt = 125 * (1 << fp->datainterval);
ptmin = min(ptmin, pt);
}
param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME;
if (subs->speed == USB_SPEED_FULL)
ptmin = 1000;
if (ptmin == 1000)
param_period_time_if_needed = -1;
err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
ptmin, UINT_MAX);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
hw_rule_rate, subs,
SNDRV_PCM_HW_PARAM_RATE,
SNDRV_PCM_HW_PARAM_FORMAT,
SNDRV_PCM_HW_PARAM_CHANNELS,
param_period_time_if_needed,
-1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, subs,
SNDRV_PCM_HW_PARAM_CHANNELS,
SNDRV_PCM_HW_PARAM_FORMAT,
SNDRV_PCM_HW_PARAM_RATE,
param_period_time_if_needed,
-1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
hw_rule_format, subs,
SNDRV_PCM_HW_PARAM_FORMAT,
SNDRV_PCM_HW_PARAM_RATE,
SNDRV_PCM_HW_PARAM_CHANNELS,
param_period_time_if_needed,
-1);
if (err < 0)
return err;
if (param_period_time_if_needed >= 0) {
err = snd_pcm_hw_rule_add(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
hw_rule_period_time, subs,
SNDRV_PCM_HW_PARAM_FORMAT,
SNDRV_PCM_HW_PARAM_CHANNELS,
SNDRV_PCM_HW_PARAM_RATE,
-1);
if (err < 0)
return err;
}
err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
0, 1000000);
if (err < 0)
return err;
err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_BUFFER_TIME,
0, 2000000);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
hw_rule_period_size_implicit_fb, subs,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS,
hw_rule_periods_implicit_fb, subs,
SNDRV_PCM_HW_PARAM_PERIODS, -1);
if (err < 0)
return err;
list_for_each_entry(fp, &subs->fmt_list, list) {
if (fp->implicit_fb) {
runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX;
break;
}
}
return 0;
}
static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
{
int direction = substream->stream;
struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usb_substream *subs = &as->substream[direction];
int ret;
runtime->hw = snd_usb_hardware;
if (direction == SNDRV_PCM_STREAM_PLAYBACK &&
as->chip->lowlatency)
runtime->hw.info |= SNDRV_PCM_INFO_SYNC_APPLPTR;
runtime->private_data = subs;
subs->pcm_substream = substream;
subs->dsd_dop.byte_idx = 0;
subs->dsd_dop.channel = 0;
subs->dsd_dop.marker = 1;
ret = setup_hw_info(runtime, subs);
if (ret < 0)
return ret;
ret = snd_usb_autoresume(subs->stream->chip);
if (ret < 0)
return ret;
ret = snd_media_stream_init(subs, as->pcm, direction);
if (ret < 0)
snd_usb_autosuspend(subs->stream->chip);
return ret;
}
static int snd_usb_pcm_close(struct snd_pcm_substream *substream)
{
int direction = substream->stream;
struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
struct snd_usb_substream *subs = &as->substream[direction];
int ret;
snd_media_stop_pipeline(subs);
if (!snd_usb_lock_shutdown(subs->stream->chip)) {
ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D1);
snd_usb_unlock_shutdown(subs->stream->chip);
if (ret < 0)
return ret;
}
subs->pcm_substream = NULL;
snd_usb_autosuspend(subs->stream->chip);
return 0;
}
static void retire_capture_urb(struct snd_usb_substream *subs,
struct urb *urb)
{
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
unsigned int stride, frames, bytes, oldptr;
int i, period_elapsed = 0;
unsigned long flags;
unsigned char *cp;
int current_frame_number;
current_frame_number = usb_get_current_frame_number(subs->dev);
stride = runtime->frame_bits >> 3;
for (i = 0; i < urb->number_of_packets; i++) {
cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset + subs->pkt_offset_adj;
if (urb->iso_frame_desc[i].status && printk_ratelimit()) {
dev_dbg(&subs->dev->dev, "frame %d active: %d\n",
i, urb->iso_frame_desc[i].status);
}
bytes = urb->iso_frame_desc[i].actual_length;
if (subs->stream_offset_adj > 0) {
unsigned int adj = min(subs->stream_offset_adj, bytes);
cp += adj;
bytes -= adj;
subs->stream_offset_adj -= adj;
}
frames = bytes / stride;
if (!subs->txfr_quirk)
bytes = frames * stride;
if (bytes % (runtime->sample_bits >> 3) != 0) {
int oldbytes = bytes;
bytes = frames * stride;
dev_warn_ratelimited(&subs->dev->dev,
"Corrected urb data len. %d->%d\n",
oldbytes, bytes);
}
spin_lock_irqsave(&subs->lock, flags);
oldptr = subs->hwptr_done;
subs->hwptr_done += bytes;
if (subs->hwptr_done >= subs->buffer_bytes)
subs->hwptr_done -= subs->buffer_bytes;
frames = (bytes + (oldptr % stride)) / stride;
subs->transfer_done += frames;
if (subs->transfer_done >= runtime->period_size) {
subs->transfer_done -= runtime->period_size;
period_elapsed = 1;
}
subs->last_frame_number = current_frame_number;
spin_unlock_irqrestore(&subs->lock, flags);
if (oldptr + bytes > subs->buffer_bytes) {
unsigned int bytes1 = subs->buffer_bytes - oldptr;
memcpy(runtime->dma_area + oldptr, cp, bytes1);
memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1);
} else {
memcpy(runtime->dma_area + oldptr, cp, bytes);
}
}
if (period_elapsed)
snd_pcm_period_elapsed(subs->pcm_substream);
}
static void urb_ctx_queue_advance(struct snd_usb_substream *subs,
struct urb *urb, unsigned int bytes)
{
struct snd_urb_ctx *ctx = urb->context;
ctx->queued += bytes;
subs->inflight_bytes += bytes;
subs->hwptr_done += bytes;
if (subs->hwptr_done >= subs->buffer_bytes)
subs->hwptr_done -= subs->buffer_bytes;
}
static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs,
struct urb *urb, unsigned int bytes)
{
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
unsigned int dst_idx = 0;
unsigned int src_idx = subs->hwptr_done;
unsigned int wrap = subs->buffer_bytes;
u8 *dst = urb->transfer_buffer;
u8 *src = runtime->dma_area;
static const u8 marker[] = { 0x05, 0xfa };
unsigned int queued = 0;
while (bytes--) {
if (++subs->dsd_dop.byte_idx == 3) {
dst[dst_idx++] = marker[subs->dsd_dop.marker];
src_idx += 2;
subs->dsd_dop.byte_idx = 0;
if (++subs->dsd_dop.channel % runtime->channels == 0) {
subs->dsd_dop.marker++;
subs->dsd_dop.marker %= ARRAY_SIZE(marker);
subs->dsd_dop.channel = 0;
}
} else {
int idx = (src_idx + subs->dsd_dop.byte_idx - 1) % wrap;
if (subs->cur_audiofmt->dsd_bitrev)
dst[dst_idx++] = bitrev8(src[idx]);
else
dst[dst_idx++] = src[idx];
queued++;
}
}
urb_ctx_queue_advance(subs, urb, queued);
}
static void fill_playback_urb_dsd_bitrev(struct snd_usb_substream *subs,
struct urb *urb, unsigned int bytes)
{
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
const u8 *src = runtime->dma_area;
u8 *buf = urb->transfer_buffer;
int i, ofs = subs->hwptr_done;
for (i = 0; i < bytes; i++) {
*buf++ = bitrev8(src[ofs]);
if (++ofs >= subs->buffer_bytes)
ofs = 0;
}
urb_ctx_queue_advance(subs, urb, bytes);
}
static void copy_to_urb(struct snd_usb_substream *subs, struct urb *urb,
int offset, int stride, unsigned int bytes)
{
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
if (subs->hwptr_done + bytes > subs->buffer_bytes) {
unsigned int bytes1 = subs->buffer_bytes - subs->hwptr_done;
memcpy(urb->transfer_buffer + offset,
runtime->dma_area + subs->hwptr_done, bytes1);
memcpy(urb->transfer_buffer + offset + bytes1,
runtime->dma_area, bytes - bytes1);
} else {
memcpy(urb->transfer_buffer + offset,
runtime->dma_area + subs->hwptr_done, bytes);
}
urb_ctx_queue_advance(subs, urb, bytes);
}
static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs,
struct urb *urb, int stride,
unsigned int bytes)
{
__le32 packet_length;
int i;
for (i = 0; i < urb->number_of_packets; i++) {
unsigned int length = urb->iso_frame_desc[i].length;
unsigned int offset = urb->iso_frame_desc[i].offset;
packet_length = cpu_to_le32(length);
offset += i * sizeof(packet_length);
urb->iso_frame_desc[i].offset = offset;
urb->iso_frame_desc[i].length += sizeof(packet_length);
memcpy(urb->transfer_buffer + offset,
&packet_length, sizeof(packet_length));
copy_to_urb(subs, urb, offset + sizeof(packet_length),
stride, length);
}
bytes += urb->number_of_packets * sizeof(packet_length);
return bytes;
}
static int prepare_playback_urb(struct snd_usb_substream *subs,
struct urb *urb,
bool in_stream_lock)
{
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
struct snd_usb_endpoint *ep = subs->data_endpoint;
struct snd_urb_ctx *ctx = urb->context;
unsigned int frames, bytes;
int counts;
unsigned int transfer_done, frame_limit, avail = 0;
int i, stride, period_elapsed = 0;
unsigned long flags;
int err = 0;
stride = ep->stride;
frames = 0;
ctx->queued = 0;
urb->number_of_packets = 0;
spin_lock_irqsave(&subs->lock, flags);
frame_limit = subs->frame_limit + ep->max_urb_frames;
transfer_done = subs->transfer_done;
if (subs->lowlatency_playback &&
runtime->state != SNDRV_PCM_STATE_DRAINING) {
unsigned int hwptr = subs->hwptr_done / stride;
avail = (runtime->control->appl_ptr - runtime->hw_ptr_base)
% runtime->buffer_size;
if (avail <= hwptr)
avail += runtime->buffer_size;
avail -= hwptr;
}
for (i = 0; i < ctx->packets; i++) {
counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail);
if (counts < 0)
break;
urb->iso_frame_desc[i].offset = frames * stride;
urb->iso_frame_desc[i].length = counts * stride;
frames += counts;
avail -= counts;
urb->number_of_packets++;
transfer_done += counts;
if (transfer_done >= runtime->period_size) {
transfer_done -= runtime->period_size;
frame_limit = 0;
period_elapsed = 1;
if (subs->fmt_type == UAC_FORMAT_TYPE_II) {
if (transfer_done > 0) {
frames -= transfer_done;
counts -= transfer_done;
urb->iso_frame_desc[i].length =
counts * stride;
transfer_done = 0;
}
i++;
if (i < ctx->packets) {
urb->iso_frame_desc[i].offset =
frames * stride;
urb->iso_frame_desc[i].length = 0;
urb->number_of_packets++;
}
break;
}
}
if ((period_elapsed || transfer_done >= frame_limit) &&
!snd_usb_endpoint_implicit_feedback_sink(ep))
break;
}
if (!frames) {
err = -EAGAIN;
goto unlock;
}
bytes = frames * stride;
subs->transfer_done = transfer_done;
subs->frame_limit = frame_limit;
if (unlikely(ep->cur_format == SNDRV_PCM_FORMAT_DSD_U16_LE &&
subs->cur_audiofmt->dsd_dop)) {
fill_playback_urb_dsd_dop(subs, urb, bytes);
} else if (unlikely(ep->cur_format == SNDRV_PCM_FORMAT_DSD_U8 &&
subs->cur_audiofmt->dsd_bitrev)) {
fill_playback_urb_dsd_bitrev(subs, urb, bytes);
} else {
if (!subs->tx_length_quirk)
copy_to_urb(subs, urb, 0, stride, bytes);
else
bytes = copy_to_urb_quirk(subs, urb, stride, bytes);
}
subs->last_frame_number = usb_get_current_frame_number(subs->dev);
if (subs->trigger_tstamp_pending_update) {
snd_pcm_gettime(runtime, &runtime->trigger_tstamp);
subs->trigger_tstamp_pending_update = false;
}
if (period_elapsed && !subs->running && subs->lowlatency_playback) {
subs->period_elapsed_pending = 1;
period_elapsed = 0;
}
unlock:
spin_unlock_irqrestore(&subs->lock, flags);
if (err < 0)
return err;
urb->transfer_buffer_length = bytes;
if (period_elapsed) {
if (in_stream_lock)
snd_pcm_period_elapsed_under_stream_lock(subs->pcm_substream);
else
snd_pcm_period_elapsed(subs->pcm_substream);
}
return 0;
}
static void retire_playback_urb(struct snd_usb_substream *subs,
struct urb *urb)
{
unsigned long flags;
struct snd_urb_ctx *ctx = urb->context;
bool period_elapsed = false;
spin_lock_irqsave(&subs->lock, flags);
if (ctx->queued) {
if (subs->inflight_bytes >= ctx->queued)
subs->inflight_bytes -= ctx->queued;
else
subs->inflight_bytes = 0;
}
subs->last_frame_number = usb_get_current_frame_number(subs->dev);
if (subs->running) {
period_elapsed = subs->period_elapsed_pending;
subs->period_elapsed_pending = 0;
}
spin_unlock_irqrestore(&subs->lock, flags);
if (period_elapsed)
snd_pcm_period_elapsed(subs->pcm_substream);
}
static int snd_usb_pcm_playback_ack(struct snd_pcm_substream *substream)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
struct snd_usb_endpoint *ep;
if (!subs->lowlatency_playback || !subs->running)
return 0;
ep = subs->data_endpoint;
if (!ep)
return 0;
if (!ep->active_mask)
return snd_usb_queue_pending_output_urbs(ep, true);
return 0;
}
static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
int err;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
subs->trigger_tstamp_pending_update = true;
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_usb_endpoint_set_callback(subs->data_endpoint,
prepare_playback_urb,
retire_playback_urb,
subs);
if (subs->lowlatency_playback &&
cmd == SNDRV_PCM_TRIGGER_START) {
if (in_free_wheeling_mode(substream->runtime))
subs->lowlatency_playback = false;
err = start_endpoints(subs);
if (err < 0) {
snd_usb_endpoint_set_callback(subs->data_endpoint,
NULL, NULL, NULL);
return err;
}
}
subs->running = 1;
dev_dbg(&subs->dev->dev, "%d:%d Start Playback PCM\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->altsetting);
return 0;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
stop_endpoints(subs, substream->runtime->state == SNDRV_PCM_STATE_DRAINING);
snd_usb_endpoint_set_callback(subs->data_endpoint,
NULL, NULL, NULL);
subs->running = 0;
dev_dbg(&subs->dev->dev, "%d:%d Stop Playback PCM\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->altsetting);
return 0;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
snd_usb_endpoint_set_callback(subs->data_endpoint,
NULL,
retire_playback_urb,
subs);
subs->running = 0;
dev_dbg(&subs->dev->dev, "%d:%d Pause Playback PCM\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->altsetting);
return 0;
}
return -EINVAL;
}
static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream,
int cmd)
{
int err;
struct snd_usb_substream *subs = substream->runtime->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
err = start_endpoints(subs);
if (err < 0)
return err;
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_usb_endpoint_set_callback(subs->data_endpoint,
NULL, retire_capture_urb,
subs);
subs->last_frame_number = usb_get_current_frame_number(subs->dev);
subs->running = 1;
dev_dbg(&subs->dev->dev, "%d:%d Start Capture PCM\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->altsetting);
return 0;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
stop_endpoints(subs, false);
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
snd_usb_endpoint_set_callback(subs->data_endpoint,
NULL, NULL, NULL);
subs->running = 0;
dev_dbg(&subs->dev->dev, "%d:%d Stop Capture PCM\n",
subs->cur_audiofmt->iface,
subs->cur_audiofmt->altsetting);
return 0;
}
return -EINVAL;
}
static const struct snd_pcm_ops snd_usb_playback_ops = {
.open = snd_usb_pcm_open,
.close = snd_usb_pcm_close,
.hw_params = snd_usb_hw_params,
.hw_free = snd_usb_hw_free,
.prepare = snd_usb_pcm_prepare,
.trigger = snd_usb_substream_playback_trigger,
.sync_stop = snd_usb_pcm_sync_stop,
.pointer = snd_usb_pcm_pointer,
.ack = snd_usb_pcm_playback_ack,
};
static const struct snd_pcm_ops snd_usb_capture_ops = {
.open = snd_usb_pcm_open,
.close = snd_usb_pcm_close,
.hw_params = snd_usb_hw_params,
.hw_free = snd_usb_hw_free,
.prepare = snd_usb_pcm_prepare,
.trigger = snd_usb_substream_capture_trigger,
.sync_stop = snd_usb_pcm_sync_stop,
.pointer = snd_usb_pcm_pointer,
};
void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
{
const struct snd_pcm_ops *ops;
ops = stream == SNDRV_PCM_STREAM_PLAYBACK ?
&snd_usb_playback_ops : &snd_usb_capture_ops;
snd_pcm_set_ops(pcm, stream, ops);
}
void snd_usb_preallocate_buffer(struct snd_usb_substream *subs)
{
struct snd_pcm *pcm = subs->stream->pcm;
struct snd_pcm_substream *s = pcm->streams[subs->direction].substream;
struct device *dev = subs->dev->bus->sysdev;
if (snd_usb_use_vmalloc)
snd_pcm_set_managed_buffer(s, SNDRV_DMA_TYPE_VMALLOC,
NULL, 0, 0);
else
snd_pcm_set_managed_buffer(s, SNDRV_DMA_TYPE_DEV_SG,
dev, 64*1024, 512*1024);
}