aboutsummaryrefslogtreecommitdiffstats
path: root/sound/usb/endpoint.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/usb/endpoint.c')
-rw-r--r--sound/usb/endpoint.c180
1 files changed, 146 insertions, 34 deletions
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index 743b8287cfcd..310cd6fb0038 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -32,6 +32,17 @@ struct snd_usb_iface_ref {
unsigned char iface;
bool need_setup;
int opened;
+ int altset;
+ struct list_head list;
+};
+
+/* clock refcounting */
+struct snd_usb_clock_ref {
+ unsigned char clock;
+ atomic_t locked;
+ int opened;
+ int rate;
+ bool need_setup;
struct list_head list;
};
@@ -85,12 +96,13 @@ static inline unsigned get_usb_high_speed_rate(unsigned int rate)
*/
static void release_urb_ctx(struct snd_urb_ctx *u)
{
- if (u->buffer_size)
+ if (u->urb && u->buffer_size)
usb_free_coherent(u->ep->chip->dev, u->buffer_size,
u->urb->transfer_buffer,
u->urb->transfer_dma);
usb_free_urb(u->urb);
u->urb = NULL;
+ u->buffer_size = 0;
}
static const char *usb_error_string(int err)
@@ -125,7 +137,7 @@ static inline bool ep_state_running(struct snd_usb_endpoint *ep)
static inline bool ep_state_update(struct snd_usb_endpoint *ep, int old, int new)
{
- return atomic_cmpxchg(&ep->state, old, new) == old;
+ return atomic_try_cmpxchg(&ep->state, &old, new);
}
/**
@@ -591,6 +603,25 @@ iface_ref_find(struct snd_usb_audio *chip, int iface)
return ip;
}
+/* Similarly, a refcount object for clock */
+static struct snd_usb_clock_ref *
+clock_ref_find(struct snd_usb_audio *chip, int clock)
+{
+ struct snd_usb_clock_ref *ref;
+
+ list_for_each_entry(ref, &chip->clock_ref_list, list)
+ if (ref->clock == clock)
+ return ref;
+
+ ref = kzalloc(sizeof(*ref), GFP_KERNEL);
+ if (!ref)
+ return NULL;
+ ref->clock = clock;
+ atomic_set(&ref->locked, 0);
+ list_add_tail(&ref->list, &chip->clock_ref_list);
+ return ref;
+}
+
/*
* Get the existing endpoint object corresponding EP
* Returns NULL if not present.
@@ -731,7 +762,8 @@ bool snd_usb_endpoint_compatible(struct snd_usb_audio *chip,
* The endpoint needs to be closed via snd_usb_endpoint_close() later.
*
* Note that this function doesn't configure the endpoint. The substream
- * needs to set it up later via snd_usb_endpoint_configure().
+ * needs to set it up later via snd_usb_endpoint_set_params() and
+ * snd_usb_endpoint_prepare().
*/
struct snd_usb_endpoint *
snd_usb_endpoint_open(struct snd_usb_audio *chip,
@@ -768,6 +800,15 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip,
goto unlock;
}
+ if (fp->protocol != UAC_VERSION_1) {
+ ep->clock_ref = clock_ref_find(chip, fp->clock);
+ if (!ep->clock_ref) {
+ ep = NULL;
+ goto unlock;
+ }
+ ep->clock_ref->opened++;
+ }
+
ep->cur_audiofmt = fp;
ep->cur_channels = fp->channels;
ep->cur_rate = params_rate(params);
@@ -777,13 +818,13 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip,
ep->cur_period_frames = params_period_size(params);
ep->cur_period_bytes = ep->cur_period_frames * ep->cur_frame_bytes;
ep->cur_buffer_periods = params_periods(params);
- ep->cur_clock = fp->clock;
if (ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
endpoint_set_syncinterval(chip, ep);
ep->implicit_fb_sync = fp->implicit_fb;
ep->need_setup = true;
+ ep->need_prepare = true;
usb_audio_dbg(chip, " channels=%d, rate=%d, format=%s, period_bytes=%d, periods=%d, implicit_fb=%d\n",
ep->cur_channels, ep->cur_rate,
@@ -860,6 +901,9 @@ static int endpoint_set_interface(struct snd_usb_audio *chip,
int altset = set ? ep->altsetting : 0;
int err;
+ if (ep->iface_ref->altset == altset)
+ return 0;
+
usb_audio_dbg(chip, "Setting usb interface %d:%d for EP 0x%x\n",
ep->iface, altset, ep->ep_num);
err = usb_set_interface(chip->dev, ep->iface, altset);
@@ -871,6 +915,7 @@ static int endpoint_set_interface(struct snd_usb_audio *chip,
if (chip->quirk_flags & QUIRK_FLAG_IFACE_DELAY)
msleep(50);
+ ep->iface_ref->altset = altset;
return 0;
}
@@ -886,16 +931,21 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip,
usb_audio_dbg(chip, "Closing EP 0x%x (count %d)\n",
ep->ep_num, ep->opened);
- if (!--ep->iface_ref->opened)
+ if (!--ep->iface_ref->opened &&
+ !(chip->quirk_flags & QUIRK_FLAG_IFACE_SKIP_CLOSE))
endpoint_set_interface(chip, ep, false);
if (!--ep->opened) {
+ if (ep->clock_ref) {
+ if (!--ep->clock_ref->opened)
+ ep->clock_ref->rate = 0;
+ }
ep->iface = 0;
ep->altsetting = 0;
ep->cur_audiofmt = NULL;
ep->cur_rate = 0;
- ep->cur_clock = 0;
ep->iface_ref = NULL;
+ ep->clock_ref = NULL;
usb_audio_dbg(chip, "EP 0x%x closed\n", ep->ep_num);
}
mutex_unlock(&chip->mutex);
@@ -904,9 +954,11 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip,
/* Prepare for suspening EP, called from the main suspend handler */
void snd_usb_endpoint_suspend(struct snd_usb_endpoint *ep)
{
- ep->need_setup = true;
+ ep->need_prepare = true;
if (ep->iface_ref)
ep->iface_ref->need_setup = true;
+ if (ep->clock_ref)
+ ep->clock_ref->rate = 0;
}
/*
@@ -1223,6 +1275,7 @@ static int sync_ep_set_params(struct snd_usb_endpoint *ep)
if (!ep->syncbuf)
return -ENOMEM;
+ ep->nurbs = SYNC_URBS;
for (i = 0; i < SYNC_URBS; i++) {
struct snd_urb_ctx *u = &ep->urb[i];
u->index = i;
@@ -1242,8 +1295,6 @@ static int sync_ep_set_params(struct snd_usb_endpoint *ep)
u->urb->complete = snd_complete_urb;
}
- ep->nurbs = SYNC_URBS;
-
return 0;
out_of_memory:
@@ -1251,23 +1302,51 @@ out_of_memory:
return -ENOMEM;
}
+/* update the rate of the referred clock; return the actual rate */
+static int update_clock_ref_rate(struct snd_usb_audio *chip,
+ struct snd_usb_endpoint *ep)
+{
+ struct snd_usb_clock_ref *clock = ep->clock_ref;
+ int rate = ep->cur_rate;
+
+ if (!clock || clock->rate == rate)
+ return rate;
+ if (clock->rate) {
+ if (atomic_read(&clock->locked))
+ return clock->rate;
+ if (clock->rate != rate) {
+ usb_audio_err(chip, "Mismatched sample rate %d vs %d for EP 0x%x\n",
+ clock->rate, rate, ep->ep_num);
+ return clock->rate;
+ }
+ }
+ clock->rate = rate;
+ clock->need_setup = true;
+ return rate;
+}
+
/*
* snd_usb_endpoint_set_params: configure an snd_usb_endpoint
*
+ * It's called either from hw_params callback.
* Determine the number of URBs to be used on this endpoint.
* An endpoint must be configured before it can be started.
* An endpoint that is already running can not be reconfigured.
*/
-static int snd_usb_endpoint_set_params(struct snd_usb_audio *chip,
- struct snd_usb_endpoint *ep)
+int snd_usb_endpoint_set_params(struct snd_usb_audio *chip,
+ struct snd_usb_endpoint *ep)
{
const struct audioformat *fmt = ep->cur_audiofmt;
- int err;
+ int err = 0;
+
+ mutex_lock(&chip->mutex);
+ if (!ep->need_setup)
+ goto unlock;
/* release old buffers, if any */
err = release_urbs(ep, false);
if (err < 0)
- return err;
+ goto unlock;
ep->datainterval = fmt->datainterval;
ep->maxpacksize = fmt->maxpacksize;
@@ -1305,28 +1384,60 @@ static int snd_usb_endpoint_set_params(struct snd_usb_audio *chip,
usb_audio_dbg(chip, "Set up %d URBS, ret=%d\n", ep->nurbs, err);
if (err < 0)
- return err;
+ goto unlock;
/* some unit conversions in runtime */
ep->maxframesize = ep->maxpacksize / ep->cur_frame_bytes;
ep->curframesize = ep->curpacksize / ep->cur_frame_bytes;
+ err = update_clock_ref_rate(chip, ep);
+ if (err >= 0) {
+ ep->need_setup = false;
+ err = 0;
+ }
+
+ unlock:
+ mutex_unlock(&chip->mutex);
+ return err;
+}
+
+static int init_sample_rate(struct snd_usb_audio *chip,
+ struct snd_usb_endpoint *ep)
+{
+ struct snd_usb_clock_ref *clock = ep->clock_ref;
+ int rate, err;
+
+ rate = update_clock_ref_rate(chip, ep);
+ if (rate < 0)
+ return rate;
+ if (clock && !clock->need_setup)
+ return 0;
+
+ err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt, rate);
+ if (err < 0) {
+ if (clock)
+ clock->rate = 0; /* reset rate */
+ return err;
+ }
+
+ if (clock)
+ clock->need_setup = false;
return 0;
}
/*
- * snd_usb_endpoint_configure: Configure the endpoint
+ * snd_usb_endpoint_prepare: Prepare the endpoint
*
* This function sets up the EP to be fully usable state.
- * It's called either from hw_params or prepare callback.
+ * It's called either from prepare callback.
* The function checks need_setup flag, and performs nothing unless needed,
* so it's safe to call this multiple times.
*
* This returns zero if unchanged, 1 if the configuration has changed,
* or a negative error code.
*/
-int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
- struct snd_usb_endpoint *ep)
+int snd_usb_endpoint_prepare(struct snd_usb_audio *chip,
+ struct snd_usb_endpoint *ep)
{
bool iface_first;
int err = 0;
@@ -1334,7 +1445,7 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
mutex_lock(&chip->mutex);
if (WARN_ON(!ep->iface_ref))
goto unlock;
- if (!ep->need_setup)
+ if (!ep->need_prepare)
goto unlock;
/* If the interface has been already set up, just set EP parameters */
@@ -1343,14 +1454,10 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
* to update at each EP configuration
*/
if (ep->cur_audiofmt->protocol == UAC_VERSION_1) {
- err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt,
- ep->cur_rate);
+ err = init_sample_rate(chip, ep);
if (err < 0)
goto unlock;
}
- err = snd_usb_endpoint_set_params(chip, ep);
- if (err < 0)
- goto unlock;
goto done;
}
@@ -1374,11 +1481,7 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
if (err < 0)
goto unlock;
- err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt, ep->cur_rate);
- if (err < 0)
- goto unlock;
-
- err = snd_usb_endpoint_set_params(chip, ep);
+ err = init_sample_rate(chip, ep);
if (err < 0)
goto unlock;
@@ -1396,7 +1499,7 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
ep->iface_ref->need_setup = false;
done:
- ep->need_setup = false;
+ ep->need_prepare = false;
err = 1;
unlock:
@@ -1407,15 +1510,15 @@ unlock:
/* get the current rate set to the given clock by any endpoint */
int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock)
{
- struct snd_usb_endpoint *ep;
+ struct snd_usb_clock_ref *ref;
int rate = 0;
if (!clock)
return 0;
mutex_lock(&chip->mutex);
- list_for_each_entry(ep, &chip->ep_list, list) {
- if (ep->cur_clock == clock && ep->cur_rate) {
- rate = ep->cur_rate;
+ list_for_each_entry(ref, &chip->clock_ref_list, list) {
+ if (ref->clock == clock) {
+ rate = ref->rate;
break;
}
}
@@ -1456,6 +1559,9 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
if (atomic_inc_return(&ep->running) != 1)
return 0;
+ if (ep->clock_ref)
+ atomic_inc(&ep->clock_ref->locked);
+
ep->active_mask = 0;
ep->unlink_mask = 0;
ep->phase = 0;
@@ -1565,6 +1671,8 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending)
if (ep->sync_source)
WRITE_ONCE(ep->sync_source->sync_sink, NULL);
stop_urbs(ep, false, keep_pending);
+ if (ep->clock_ref)
+ atomic_dec(&ep->clock_ref->locked);
}
}
@@ -1591,12 +1699,16 @@ void snd_usb_endpoint_free_all(struct snd_usb_audio *chip)
{
struct snd_usb_endpoint *ep, *en;
struct snd_usb_iface_ref *ip, *in;
+ struct snd_usb_clock_ref *cp, *cn;
list_for_each_entry_safe(ep, en, &chip->ep_list, list)
kfree(ep);
list_for_each_entry_safe(ip, in, &chip->iface_ref_list, list)
kfree(ip);
+
+ list_for_each_entry_safe(cp, cn, &chip->clock_ref_list, list)
+ kfree(cp);
}
/*