summaryrefslogtreecommitdiff
path: root/sound/usb/line6/pcm.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/usb/line6/pcm.c')
-rw-r--r--sound/usb/line6/pcm.c333
1 files changed, 187 insertions, 146 deletions
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c
index dedbe24cbf60..470fc1049d54 100644
--- a/sound/usb/line6/pcm.c
+++ b/sound/usb/line6/pcm.c
@@ -51,9 +51,9 @@ static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
line6pcm->impulse_volume = value;
if (value > 0)
- line6_pcm_acquire(line6pcm, LINE6_BITS_PCM_IMPULSE);
+ line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE);
else
- line6_pcm_release(line6pcm, LINE6_BITS_PCM_IMPULSE);
+ line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE);
return 1;
}
@@ -132,176 +132,226 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
"timeout: still %d active urbs..\n", alive);
}
-static int line6_alloc_stream_buffer(struct snd_line6_pcm *line6pcm,
- struct line6_pcm_stream *pcms)
+static inline struct line6_pcm_stream *
+get_stream(struct snd_line6_pcm *line6pcm, int direction)
{
- /* Invoked multiple times in a row so allocate once only */
- if (pcms->buffer)
- return 0;
-
- pcms->buffer = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS *
- line6pcm->max_packet_size, GFP_KERNEL);
- if (!pcms->buffer)
- return -ENOMEM;
- return 0;
+ return (direction == SNDRV_PCM_STREAM_PLAYBACK) ?
+ &line6pcm->out : &line6pcm->in;
}
-static void line6_free_stream_buffer(struct snd_line6_pcm *line6pcm,
- struct line6_pcm_stream *pcms)
+/* allocate a buffer if not opened yet;
+ * call this in line6pcm.state_change mutex
+ */
+static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pstr, int type)
{
- kfree(pcms->buffer);
- pcms->buffer = NULL;
+ /* Invoked multiple times in a row so allocate once only */
+ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) {
+ pstr->buffer = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS *
+ line6pcm->max_packet_size, GFP_KERNEL);
+ if (!pstr->buffer)
+ return -ENOMEM;
+ }
+ return 0;
}
-static bool test_flags(unsigned long flags0, unsigned long flags1,
- unsigned long mask)
+/* free a buffer if all streams are closed;
+ * call this in line6pcm.state_change mutex
+ */
+static void line6_buffer_release(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pstr, int type)
{
- return ((flags0 & mask) == 0) && ((flags1 & mask) != 0);
+
+ clear_bit(type, &pstr->opened);
+ if (!pstr->opened) {
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
+ kfree(pstr->buffer);
+ pstr->buffer = NULL;
+ }
}
-int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int channels)
+/* start a PCM stream */
+static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction,
+ int type)
{
- unsigned long flags_old, flags_new, flags_final;
- int err;
-
- do {
- flags_old = ACCESS_ONCE(line6pcm->flags);
- flags_new = flags_old | channels;
- } while (cmpxchg(&line6pcm->flags, flags_old, flags_new) != flags_old);
-
- flags_final = 0;
+ unsigned long flags;
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+ int ret = 0;
+
+ spin_lock_irqsave(&pstr->lock, flags);
+ if (!test_and_set_bit(type, &pstr->running)) {
+ if (pstr->active_urbs || pstr->unlink_urbs) {
+ ret = -EBUSY;
+ goto error;
+ }
- if (test_flags(flags_old, flags_new, LINE6_BITS_CAPTURE_BUFFER)) {
- err = line6_alloc_stream_buffer(line6pcm, &line6pcm->in);
- if (err < 0)
- goto pcm_acquire_error;
- flags_final |= channels & LINE6_BITS_CAPTURE_BUFFER;
+ pstr->count = 0;
+ /* Submit all currently available URBs */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = line6_submit_audio_out_all_urbs(line6pcm);
+ else
+ ret = line6_submit_audio_in_all_urbs(line6pcm);
}
+ error:
+ if (ret < 0)
+ clear_bit(type, &pstr->running);
+ spin_unlock_irqrestore(&pstr->lock, flags);
+ return ret;
+}
- if (test_flags(flags_old, flags_new, LINE6_BITS_CAPTURE_STREAM)) {
- /*
- Waiting for completion of active URBs in the stop handler is
- a bug, we therefore report an error if capturing is restarted
- too soon.
- */
- if (line6pcm->in.active_urbs || line6pcm->in.unlink_urbs) {
- dev_err(line6pcm->line6->ifcdev, "Device not yet ready\n");
- err = -EBUSY;
- goto pcm_acquire_error;
+/* stop a PCM stream; this doesn't sync with the unlinked URBs */
+static void line6_stream_stop(struct snd_line6_pcm *line6pcm, int direction,
+ int type)
+{
+ unsigned long flags;
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+
+ spin_lock_irqsave(&pstr->lock, flags);
+ clear_bit(type, &pstr->running);
+ if (!pstr->running) {
+ line6_unlink_audio_urbs(line6pcm, pstr);
+ if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+ line6pcm->prev_fbuf = NULL;
+ line6pcm->prev_fsize = 0;
}
+ }
+ spin_unlock_irqrestore(&pstr->lock, flags);
+}
- line6pcm->in.count = 0;
- err = line6_submit_audio_in_all_urbs(line6pcm);
+/* common PCM trigger callback */
+int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct snd_pcm_substream *s;
+ int err;
- if (err < 0)
- goto pcm_acquire_error;
+ clear_bit(LINE6_FLAG_PREPARED, &line6pcm->flags);
- flags_final |= channels & LINE6_BITS_CAPTURE_STREAM;
- }
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
- if (test_flags(flags_old, flags_new, LINE6_BITS_PLAYBACK_BUFFER)) {
- err = line6_alloc_stream_buffer(line6pcm, &line6pcm->out);
- if (err < 0)
- goto pcm_acquire_error;
- flags_final |= channels & LINE6_BITS_PLAYBACK_BUFFER;
- }
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ err = line6_stream_start(line6pcm, s->stream,
+ LINE6_STREAM_PCM);
+ if (err < 0)
+ return err;
+ break;
- if (test_flags(flags_old, flags_new, LINE6_BITS_PLAYBACK_STREAM)) {
- /*
- See comment above regarding PCM restart.
- */
- if (line6pcm->out.active_urbs || line6pcm->out.unlink_urbs) {
- dev_err(line6pcm->line6->ifcdev, "Device not yet ready\n");
- return -EBUSY;
- }
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ line6_stream_stop(line6pcm, s->stream,
+ LINE6_STREAM_PCM);
+ break;
- line6pcm->out.count = 0;
- err = line6_submit_audio_out_all_urbs(line6pcm);
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+ set_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+ break;
- if (err < 0)
- goto pcm_acquire_error;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+ clear_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+ break;
- flags_final |= channels & LINE6_BITS_PLAYBACK_STREAM;
+ default:
+ return -EINVAL;
+ }
}
return 0;
-
-pcm_acquire_error:
- /*
- If not all requested resources/streams could be obtained, release
- those which were successfully obtained (if any).
- */
- line6_pcm_release(line6pcm, flags_final);
- return err;
}
-EXPORT_SYMBOL_GPL(line6_pcm_acquire);
-int line6_pcm_release(struct snd_line6_pcm *line6pcm, int channels)
+/* Acquire and start duplex streams:
+ * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR
+ */
+int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type)
{
- unsigned long flags_old, flags_new;
-
- do {
- flags_old = ACCESS_ONCE(line6pcm->flags);
- flags_new = flags_old & ~channels;
- } while (cmpxchg(&line6pcm->flags, flags_old, flags_new) != flags_old);
-
- if (test_flags(flags_new, flags_old, LINE6_BITS_CAPTURE_STREAM)) {
- line6_unlink_audio_urbs(line6pcm, &line6pcm->in);
- line6pcm->prev_fbuf = NULL;
- line6pcm->prev_fsize = 0;
+ struct line6_pcm_stream *pstr;
+ int ret = 0, dir;
+
+ mutex_lock(&line6pcm->state_mutex);
+ for (dir = 0; dir < 2; dir++) {
+ pstr = get_stream(line6pcm, dir);
+ ret = line6_buffer_acquire(line6pcm, pstr, type);
+ if (ret < 0)
+ goto error;
+ if (!pstr->running)
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
}
-
- if (test_flags(flags_new, flags_old, LINE6_BITS_CAPTURE_BUFFER)) {
- line6_wait_clear_audio_urbs(line6pcm, &line6pcm->in);
- line6_free_stream_buffer(line6pcm, &line6pcm->in);
+ for (dir = 0; dir < 2; dir++) {
+ ret = line6_stream_start(line6pcm, dir, type);
+ if (ret < 0)
+ goto error;
}
+ error:
+ mutex_unlock(&line6pcm->state_mutex);
+ if (ret < 0)
+ line6_pcm_release(line6pcm, type);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(line6_pcm_acquire);
- if (test_flags(flags_new, flags_old, LINE6_BITS_PLAYBACK_STREAM))
- line6_unlink_audio_urbs(line6pcm, &line6pcm->out);
-
- if (test_flags(flags_new, flags_old, LINE6_BITS_PLAYBACK_BUFFER)) {
- line6_wait_clear_audio_urbs(line6pcm, &line6pcm->out);
- line6_free_stream_buffer(line6pcm, &line6pcm->out);
+/* Stop and release duplex streams */
+void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type)
+{
+ struct line6_pcm_stream *pstr;
+ int dir;
+
+ mutex_lock(&line6pcm->state_mutex);
+ for (dir = 0; dir < 2; dir++)
+ line6_stream_stop(line6pcm, dir, type);
+ for (dir = 0; dir < 2; dir++) {
+ pstr = get_stream(line6pcm, dir);
+ line6_buffer_release(line6pcm, pstr, type);
}
-
- return 0;
+ mutex_unlock(&line6pcm->state_mutex);
}
EXPORT_SYMBOL_GPL(line6_pcm_release);
-/* trigger callback */
-int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
+/* common PCM hw_params callback */
+int snd_line6_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
{
+ int ret;
struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
- struct snd_pcm_substream *s;
- int err = 0;
-
- clear_bit(LINE6_INDEX_PREPARED, &line6pcm->flags);
-
- snd_pcm_group_for_each_entry(s, substream) {
- if (s->pcm->card != substream->pcm->card)
- continue;
- switch (s->stream) {
- case SNDRV_PCM_STREAM_PLAYBACK:
- err = snd_line6_playback_trigger(line6pcm, cmd);
- break;
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ mutex_lock(&line6pcm->state_mutex);
+ ret = line6_buffer_acquire(line6pcm, pstr, LINE6_STREAM_PCM);
+ if (ret < 0)
+ goto error;
+
+ ret = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (ret < 0) {
+ line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
+ goto error;
+ }
- case SNDRV_PCM_STREAM_CAPTURE:
- err = snd_line6_capture_trigger(line6pcm, cmd);
- break;
+ pstr->period = params_period_bytes(hw_params);
+ error:
+ mutex_unlock(&line6pcm->state_mutex);
+ return ret;
+}
- default:
- dev_err(line6pcm->line6->ifcdev,
- "Unknown stream direction %d\n", s->stream);
- err = -EINVAL;
- break;
- }
- if (err < 0)
- break;
- }
+/* common PCM hw_free callback */
+int snd_line6_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
- return err;
+ mutex_lock(&line6pcm->state_mutex);
+ line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
+ mutex_unlock(&line6pcm->state_mutex);
+ return snd_pcm_lib_free_pages(substream);
}
+
/* control info callback */
static int snd_line6_control_playback_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
@@ -454,6 +504,7 @@ int line6_init_pcm(struct usb_line6 *line6,
if (!line6pcm)
return -ENOMEM;
+ mutex_init(&line6pcm->state_mutex);
line6pcm->pcm = pcm;
line6pcm->properties = properties;
line6pcm->volume_playback[0] = line6pcm->volume_playback[1] = 255;
@@ -500,24 +551,13 @@ EXPORT_SYMBOL_GPL(line6_init_pcm);
int snd_line6_prepare(struct snd_pcm_substream *substream)
{
struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
- switch (substream->stream) {
- case SNDRV_PCM_STREAM_PLAYBACK:
- if ((line6pcm->flags & LINE6_BITS_PLAYBACK_STREAM) == 0) {
- line6_unlink_audio_urbs(line6pcm, &line6pcm->out);
- line6_wait_clear_audio_urbs(line6pcm, &line6pcm->out);
- }
- break;
-
- case SNDRV_PCM_STREAM_CAPTURE:
- if ((line6pcm->flags & LINE6_BITS_CAPTURE_STREAM) == 0) {
- line6_unlink_audio_urbs(line6pcm, &line6pcm->in);
- line6_wait_clear_audio_urbs(line6pcm, &line6pcm->in);
- }
- break;
- }
+ mutex_lock(&line6pcm->state_mutex);
+ if (!pstr->running)
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
- if (!test_and_set_bit(LINE6_INDEX_PREPARED, &line6pcm->flags)) {
+ if (!test_and_set_bit(LINE6_FLAG_PREPARED, &line6pcm->flags)) {
line6pcm->out.count = 0;
line6pcm->out.pos = 0;
line6pcm->out.pos_done = 0;
@@ -527,5 +567,6 @@ int snd_line6_prepare(struct snd_pcm_substream *substream)
line6pcm->in.bytes = 0;
}
+ mutex_unlock(&line6pcm->state_mutex);
return 0;
}