Merge "ASoC: msm: qdsp6v2: Wait for stream to be available in gapless playback"
diff --git a/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c
index 499313d..d2352ff 100644
--- a/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c
+++ b/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c
@@ -111,6 +111,8 @@
uint32_t cmd_ack;
uint32_t cmd_interrupt;
uint32_t drain_ready;
+ uint32_t stream_available;
+ uint32_t next_stream;
struct msm_compr_gapless_state gapless_state;
@@ -126,6 +128,7 @@
wait_queue_head_t drain_wait;
wait_queue_head_t flush_wait;
wait_queue_head_t close_wait;
+ wait_queue_head_t wait_for_stream_avail;
spinlock_t lock;
};
@@ -329,6 +332,8 @@
prtd->gapless_state.stream_opened[stream_id] = 0;
prtd->gapless_state.set_next_stream_id = false;
}
+ if (prtd->gapless_state.gapless_transition)
+ prtd->gapless_state.gapless_transition = 0;
spin_unlock(&prtd->lock);
break;
case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY:
@@ -373,6 +378,17 @@
break;
case ASM_STREAM_CMD_CLOSE:
pr_debug("ASM_DATA_CMD_CLOSE\n");
+ /*
+ * wakeup wait for stream avail on stream 3
+ * after stream 1 ends.
+ */
+ if (prtd->next_stream) {
+ pr_debug("%s:CLOSE:wakeup wait for stream\n",
+ __func__);
+ prtd->stream_available = 1;
+ wake_up(&prtd->wait_for_stream_avail);
+ prtd->next_stream = 0;
+ }
if (atomic_read(&prtd->close) &&
atomic_read(&prtd->wait_on_close)) {
prtd->cmd_ack = 1;
@@ -599,6 +615,7 @@
prtd->last_buffer = 0;
prtd->first_buffer = 1;
prtd->partial_drain_delay = 0;
+ prtd->next_stream = 0;
memset(&prtd->gapless_state, 0, sizeof(struct msm_compr_gapless_state));
/*
* Update the use_dsp_gapless_mode from gapless struture with the value
@@ -622,6 +639,7 @@
init_waitqueue_head(&prtd->drain_wait);
init_waitqueue_head(&prtd->flush_wait);
init_waitqueue_head(&prtd->close_wait);
+ init_waitqueue_head(&prtd->wait_for_stream_avail);
runtime->private_data = prtd;
populate_codec_list(prtd);
@@ -652,19 +670,6 @@
unsigned long flags;
pr_debug("%s\n", __func__);
- pdata->cstream[soc_prtd->dai_link->be_id] = NULL;
- if (cstream->direction == SND_COMPRESS_PLAYBACK) {
- if (atomic_read(&pdata->audio_ocmem_req) > 1)
- atomic_dec(&pdata->audio_ocmem_req);
- else if (atomic_cmpxchg(&pdata->audio_ocmem_req, 1, 0))
- audio_ocmem_process_req(AUDIO, false);
-
- msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id,
- SNDRV_PCM_STREAM_PLAYBACK);
- }
-
- pr_debug("%s: ocmem_req: %d\n", __func__,
- atomic_read(&pdata->audio_ocmem_req));
if (atomic_read(&prtd->eos)) {
ret = wait_event_timeout(prtd->eos_wait,
@@ -685,16 +690,30 @@
stream_id = ac->stream_id;
if (prtd->gapless_state.stream_opened[stream_id^1]) {
spin_unlock_irqrestore(&prtd->lock, flags);
+ pr_debug(" close stream %d", stream_id^1);
q6asm_stream_cmd(ac, CMD_CLOSE, stream_id^1);
spin_lock_irqsave(&prtd->lock, flags);
}
if (prtd->gapless_state.stream_opened[stream_id]) {
spin_unlock_irqrestore(&prtd->lock, flags);
+ pr_debug("close stream %d", stream_id);
q6asm_stream_cmd(ac, CMD_CLOSE, stream_id);
spin_lock_irqsave(&prtd->lock, flags);
}
spin_unlock_irqrestore(&prtd->lock, flags);
+ pdata->cstream[soc_prtd->dai_link->be_id] = NULL;
+ if (cstream->direction == SND_COMPRESS_PLAYBACK) {
+ if (atomic_read(&pdata->audio_ocmem_req) > 1)
+ atomic_dec(&pdata->audio_ocmem_req);
+ else if (atomic_cmpxchg(&pdata->audio_ocmem_req, 1, 0))
+ audio_ocmem_process_req(AUDIO, false);
+ msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+
+ pr_debug("%s: ocmem_req: %d\n", __func__,
+ atomic_read(&pdata->audio_ocmem_req));
/* client buf alloc was with stream id 0, so free with the same */
ac->stream_id = 0;
q6asm_audio_client_buf_free_contiguous(dir, ac);
@@ -816,6 +835,45 @@
return rc;
}
+static int msm_compr_wait_for_stream_avail(struct msm_compr_audio *prtd,
+ unsigned long *flags)
+{
+ int rc = 0;
+ pr_debug("next session is already in opened state\n");
+ prtd->next_stream = 1;
+ prtd->cmd_interrupt = 0;
+ spin_unlock_irqrestore(&prtd->lock, *flags);
+ /*
+ * Wait for stream to be available, or the wait to be interrupted by
+ * commands like flush or till a timeout of one second.
+ */
+ rc = wait_event_timeout(prtd->wait_for_stream_avail,
+ prtd->stream_available || prtd->cmd_interrupt, 1 * HZ);
+ pr_err("%s:prtd->stream_available %d, prtd->cmd_interrupt %d rc %d\n",
+ __func__, prtd->stream_available, prtd->cmd_interrupt, rc);
+
+ spin_lock_irqsave(&prtd->lock, *flags);
+ if (rc == 0) {
+ pr_err("%s: wait_for_stream_avail timed out\n",
+ __func__);
+ rc = -ETIMEDOUT;
+ } else if (prtd->cmd_interrupt == 1) {
+ /*
+ * This scenario might not happen as we do not allow
+ * flush in transition state.
+ */
+ pr_debug("%s: wait_for_stream_avail interrupted\n", __func__);
+ prtd->cmd_interrupt = 0;
+ prtd->stream_available = 0;
+ rc = -EINTR;
+ } else {
+ prtd->stream_available = 0;
+ rc = 0;
+ }
+ pr_debug("%s : rc = %d", __func__, rc);
+ return rc;
+}
+
static int msm_compr_trigger(struct snd_compr_stream *cstream, int cmd)
{
struct snd_compr_runtime *runtime = cstream->runtime;
@@ -860,6 +918,13 @@
prtd->gapless_state.gapless_transition);
stream_id = ac->stream_id;
atomic_set(&prtd->start, 0);
+ if (prtd->next_stream) {
+ pr_debug("%s: interrupt next track wait queues\n",
+ __func__);
+ prtd->cmd_interrupt = 1;
+ wake_up(&prtd->wait_for_stream_avail);
+ prtd->next_stream = 0;
+ }
if (atomic_read(&prtd->eos)) {
pr_debug("%s: interrupt eos wait queues", __func__);
prtd->cmd_interrupt = 1;
@@ -1117,12 +1182,43 @@
spin_lock_irqsave(&prtd->lock, flags);
rc = 0;
stream_id = ac->stream_id^1; /*next stream in gapless*/
+ /*
+ * Wait if stream 1 has not completed before honoring next
+ * track for stream 3. Scenario happens if second clip is
+ * small and fills in one buffer so next track will be
+ * called immediately.
+ */
if (prtd->gapless_state.stream_opened[stream_id]) {
- pr_debug("next session is already in opened state\n");
- spin_unlock_irqrestore(&prtd->lock, flags);
- break;
+ if (prtd->gapless_state.gapless_transition) {
+ rc = msm_compr_wait_for_stream_avail(prtd,
+ &flags);
+ } else {
+ /*
+ * If session is already opened break out if
+ * the state is not gapless transition. This
+ * is when seek happens after the last buffer
+ * is sent to the driver. Next track would be
+ * called again after last buffer is sent.
+ */
+ pr_debug("next session is in opened state\n");
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ break;
+ }
}
spin_unlock_irqrestore(&prtd->lock, flags);
+ if (rc < 0) {
+ /*
+ * if return type EINTR then reset to zero. Tiny
+ * compress treats EINTR as error and prevents PARTIAL
+ * DRAIN. EINTR is not an error. wait for stream avail
+ * is interrupted by some other command like FLUSH.
+ */
+ if (rc == -EINTR) {
+ pr_debug("%s: EINTR reset rc to 0\n", __func__);
+ rc = 0;
+ }
+ break;
+ }
rc = q6asm_stream_open_write_v2(prtd->audio_client,
prtd->codec, 16,
stream_id,
@@ -1290,8 +1386,6 @@
* since the available bytes fits fragment_size, copy the data right away
*/
spin_lock_irqsave(&prtd->lock, flags);
- if (prtd->gapless_state.gapless_transition)
- prtd->gapless_state.gapless_transition = 0;
prtd->bytes_received += count;
if (atomic_read(&prtd->start)) {
if (atomic_read(&prtd->xrun)) {