// SPDX-License-Identifier: GPL-2.0-or-later
/*
*/
#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
/* return the estimated delay based on USB frame counters */
snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
unsigned int rate)
{
int current_frame_number;
int frame_diff;
int est_delay;
if (!subs->last_delay)
return 0; /* short path */
current_frame_number = usb_get_current_frame_number(subs->dev);
/*
* HCD implementations use different widths, use lower 8 bits.
* The delay will be managed up to 256ms, which is more than
* enough
*/
frame_diff = (current_frame_number - subs->last_frame_number) & 0xff;
/* Approximation based on number of samples per USB frame (ms),
some truncation for 44.1 but the estimate is good enough */
est_delay = frame_diff * rate / 1000;
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
est_delay = subs->last_delay - est_delay;
else
est_delay = subs->last_delay + est_delay;
if (est_delay < 0)
est_delay = 0;
return est_delay;
}
/*
* return the current pcm pointer. just based on the hwptr_done value.
*/
static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_usb_substream *subs = substream->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;
substream->runtime->delay = snd_usb_pcm_delay(subs,
substream->runtime->rate);
spin_unlock(&subs->lock);
return hwptr_done / (substream->runtime->frame_bits >> 3);
}
/*
* find a matching audio format
*/
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;
}
/* avoid async out and adaptive in if the other method
* supports the same format.
* this is a workaround for the case like
* M-audio audiophile USB.
*/
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;
}
}
/* find the format with the largest max. packet size */
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);
}
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;
}
/*
* initialize the pitch control and sample rate
*/
int snd_usb_init_pitch(struct snd_usb_audio *chip,
const struct audioformat *fmt)
{
int err;
/* if endpoint doesn't have pitch control, bail out */
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 stopped = 0;
if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {
snd_usb_endpoint_stop(subs->sync_endpoint);
stopped = true;
}
if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) {
snd_usb_endpoint_stop(subs->data_endpoint);
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);
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);
}
/* PCM sync_stop callback */
static int snd_usb_pcm_sync_stop(struct snd_pcm_substream *substream)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
if (!snd_usb_lock_shutdown(subs->stream->chip)) {
sync_pending_stops(subs);
snd_usb_unlock_shutdown(subs->stream->chip);
}
return 0;
}
/* Set up sync endpoint */
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;
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; /* matched */
/*
* Generic sync EP handling
*/
|