Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 36a1a1f6 authored by Krishnankutty Kolathappilly's avatar Krishnankutty Kolathappilly
Browse files

ASoC: msm: qdsp6v2: Wait for stream to be available in gapless playback



Compressed driver operates on two streams in gapless mode and switches
between the two streams. If compressed playback operates on larger
buffers and the clip being played is small enough to fit in one
buffer, the driver could run into a scenario where three streams
need to be handled. Three streams results in no audio because stream
switching logic is corrupted. Fix is to block the next track call if
stream one has not completed playback.

CRs-Fixed: 594977
Change-Id: I6c83e859fa38f177ec7f4fa4ea92937d34df933a
Signed-off-by: default avatarKrishnankutty Kolathappilly <kkolat@codeaurora.org>
parent 2d37de8c
Loading
Loading
Loading
Loading
+112 −18
Original line number Diff line number Diff line
@@ -112,6 +112,8 @@ struct msm_compr_audio {
	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;

@@ -127,6 +129,7 @@ struct msm_compr_audio {
	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;
};
@@ -332,6 +335,8 @@ static void compr_event_handler(uint32_t opcode,
			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:
@@ -376,6 +381,17 @@ static void compr_event_handler(uint32_t opcode,
			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;
@@ -626,6 +642,7 @@ static int msm_compr_open(struct snd_compr_stream *cstream)
	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
@@ -649,6 +666,7 @@ static int msm_compr_open(struct snd_compr_stream *cstream)
	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);
@@ -679,19 +697,6 @@ static int msm_compr_free(struct snd_compr_stream *cstream)
	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,
@@ -712,16 +717,30 @@ static int msm_compr_free(struct snd_compr_stream *cstream)
	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);
@@ -857,6 +876,45 @@ static int msm_compr_drain_buffer(struct msm_compr_audio *prtd,
	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;
@@ -902,6 +960,13 @@ static int msm_compr_trigger(struct snd_compr_stream *cstream, int cmd)
					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;
@@ -1163,12 +1228,43 @@ static int msm_compr_trigger(struct snd_compr_stream *cstream, int cmd)
		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");
			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,
@@ -1337,8 +1433,6 @@ static int msm_compr_copy(struct snd_compr_stream *cstream,
	 * 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)) {