Ejemplo n.º 1
0
static int pcm_out_disable(struct pcm *pcm)
{
	int rc = 0;

	if (atomic_read(&pcm->out_opened)) {
		atomic_set(&pcm->out_enabled, 0);
		atomic_set(&pcm->out_opened, 0);
		rc = q6asm_cmd(pcm->ac, CMD_CLOSE);

		atomic_set(&pcm->out_stopped, 1);
		wake_up(&pcm->write_wait);
	}
	return rc;
}
static int audio_in_flush(struct q6audio_in  *audio)
{
	int rc;

	pr_debug("%s:session id %d: flush\n", __func__, audio->ac->session);
	/* Flush if session running */
	if (audio->enabled) {
		/* Implicitly issue a pause to the encoder before flushing */
		rc = audio_in_pause(audio);
		if (rc < 0) {
			pr_err("%s:session id %d: pause cmd failed rc=%d\n",
				 __func__, audio->ac->session, rc);
			return rc;
		}

		rc = q6asm_cmd(audio->ac, CMD_FLUSH);
		if (rc < 0) {
			pr_err("%s:session id %d: flush cmd failed rc=%d\n",
				__func__, audio->ac->session, rc);
			return rc;
		}
		/* 2nd arg: 0 -> run immediately
		   3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */
		q6asm_run(audio->ac, 0x00, 0x00, 0x00);
		pr_debug("Rerun the session\n");
	}
	audio->rflush = 1;
	audio->wflush = 1;
	memset(audio->out_frame_info, 0, sizeof(audio->out_frame_info));
	wake_up(&audio->read_wait);
	/* get read_lock to ensure no more waiting read thread */
	mutex_lock(&audio->read_lock);
	audio->rflush = 0;
	mutex_unlock(&audio->read_lock);
	wake_up(&audio->write_wait);
	/* get write_lock to ensure no more waiting write thread */
	mutex_lock(&audio->write_lock);
	audio->wflush = 0;
	mutex_unlock(&audio->write_lock);
	pr_debug("%s:session id %d: in_bytes %d\n", __func__,
			audio->ac->session, atomic_read(&audio->in_bytes));
	pr_debug("%s:session id %d: in_samples %d\n", __func__,
			audio->ac->session, atomic_read(&audio->in_samples));
	atomic_set(&audio->in_bytes, 0);
	atomic_set(&audio->in_samples, 0);
	atomic_set(&audio->out_count, 0);
	return 0;
}
Ejemplo n.º 3
0
static int pcm_in_disable(struct pcm *pcm)
{
	int rc = 0;

	if (atomic_read(&pcm->in_opened)) {
		atomic_set(&pcm->in_enabled, 0);
		atomic_set(&pcm->in_opened, 0);
		rc = q6asm_cmd(pcm->ac, CMD_CLOSE);

		atomic_set(&pcm->in_stopped, 1);
		memset(pcm->in_frame_info, 0,
				sizeof(char) * pcm->buffer_count * 2);
		wake_up(&pcm->wait);
	}
	return rc;
}
Ejemplo n.º 4
0
static int msm_compr_playback_close(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
	struct compr_audio *compr = runtime->private_data;
	struct msm_audio *prtd = &compr->prtd;
	int dir = 0;
	int rc = 0;
	pr_debug("[AUD] %s +++\n", __func__);

	/*
	If routing is still enabled, we need to issue EOS to
	the DSP
	To issue EOS to dsp, we need to be run state otherwise
	EOS is not honored.
	*/
	if (msm_routing_check_backend_enabled(soc_prtd->dai_link->be_id)) {
		rc = q6asm_run(prtd->audio_client,0,0,0);
		atomic_set(&prtd->pending_buffer, 0);
		prtd->cmd_ack = 0;
		q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
		pr_debug("%s ++\n", __func__);
		rc = wait_event_timeout(the_locks.eos_wait,
			prtd->cmd_ack, 3 * HZ);
		pr_debug("%s --\n", __func__);
		if (rc <= 0)
			pr_err("EOS cmd timeout\n");
		prtd->pcm_irq_pos = 0;
	}


	dir = IN;
	atomic_set(&prtd->pending_buffer, 0);
	q6asm_cmd(prtd->audio_client, CMD_CLOSE);
	q6asm_audio_client_buf_free_contiguous(dir,
				prtd->audio_client);

	msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id,
	SNDRV_PCM_STREAM_PLAYBACK);
	q6asm_audio_client_free(prtd->audio_client);
	kfree(prtd);
	pr_debug("[AUD] %s ---\n", __func__);
	return 0;
}
static int msm_compr_playback_close(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
	struct compr_audio *compr = runtime->private_data;
	struct msm_audio *prtd = &compr->prtd;
	int dir = 0;

	pr_debug("%s\n", __func__);

	dir = IN;
	atomic_set(&prtd->pending_buffer, 0);
	q6asm_cmd(prtd->audio_client, CMD_CLOSE);
	q6asm_audio_client_buf_free_contiguous(dir,
				prtd->audio_client);

	msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id,
	SNDRV_PCM_STREAM_PLAYBACK);
	q6asm_audio_client_free(prtd->audio_client);
	kfree(prtd);
	return 0;
}
ssize_t audio_in_write(struct file *file,
		const char __user *buf,
		size_t count, loff_t *pos)
{
	struct q6audio_in *audio = file->private_data;
	const char __user *start = buf;
	size_t xfer = 0;
	char *cpy_ptr;
	int rc = 0;
	unsigned char *data;
	uint32_t size = 0;
	uint32_t idx = 0;
	uint32_t nflags = 0;
	unsigned long msw_ts = 0;
	unsigned long lsw_ts = 0;
	uint32_t mfield_size = (audio->buf_cfg.meta_info_enable == 0) ? 0 :
			sizeof(struct meta_in);

	pr_debug("%s:session id %d: to write[%d]\n", __func__,
			audio->ac->session, count);
	if (!audio->enabled)
		return -EFAULT;
	mutex_lock(&audio->write_lock);

	while (count > 0) {
		rc = wait_event_interruptible(audio->write_wait,
				     ((atomic_read(&audio->in_count) > 0) ||
				      (audio->stopped) ||
				      (audio->wflush) || (audio->event_abort)));

		if (audio->event_abort) {
			rc = -EIO;
			break;
		}

		if (rc < 0)
			break;
		if (audio->stopped || audio->wflush) {
			pr_debug("%s: session id %d: stop or flush\n", __func__,
					audio->ac->session);
			rc = -EBUSY;
			break;
		}
		/* if no PCM data, might have only eos buffer
		   such case do not hold cpu buffer */
		if ((buf == start) && (count == mfield_size)) {
			char eos_buf[sizeof(struct meta_in)];
			/* Processing begining of user buffer */
			if (copy_from_user(eos_buf, buf, mfield_size)) {
				rc = -EFAULT;
				break;
			}
			/* Check if EOS flag is set and buffer has
			 * contains just meta field
			 */
			extract_meta_info(eos_buf, &msw_ts, &lsw_ts,
						&nflags);
			buf += mfield_size;
			/* send the EOS and return */
			pr_debug("%s:session id %d: send EOS 0x%8x\n",
				__func__,
				audio->ac->session, nflags);
			break;
		}
		data = (unsigned char *)q6asm_is_cpu_buf_avail(IN, audio->ac,
						&size, &idx);
		if (!data) {
			pr_debug("%s:session id %d: No buf available\n",
				__func__, audio->ac->session);
			continue;
		}
		cpy_ptr = data;
		if (audio->buf_cfg.meta_info_enable) {
			if (buf == start) {
				/* Processing beginning of user buffer */
				if (copy_from_user(cpy_ptr, buf, mfield_size)) {
					rc = -EFAULT;
					break;
				}
				/* Check if EOS flag is set and buffer has
				* contains just meta field
				*/
				extract_meta_info(cpy_ptr, &msw_ts, &lsw_ts,
						&nflags);
				buf += mfield_size;
				count -= mfield_size;
			} else {
				pr_debug("%s:session id %d: continuous buffer\n",
						__func__, audio->ac->session);
			}
		}
		xfer = (count > (audio->pcm_cfg.buffer_size)) ?
				(audio->pcm_cfg.buffer_size) : count;

		if (copy_from_user(cpy_ptr, buf, xfer)) {
			rc = -EFAULT;
			break;
		}
		rc = q6asm_write(audio->ac, xfer, msw_ts, lsw_ts, 0x00);
		if (rc < 0) {
			rc = -EFAULT;
			break;
		}
		atomic_dec(&audio->in_count);
		count -= xfer;
		buf += xfer;
	}
	mutex_unlock(&audio->write_lock);
	pr_debug("%s:session id %d: eos_condition 0x%8x buf[0x%x] start[0x%x]\n",
				__func__, audio->ac->session,
				nflags,	(int) buf, (int) start);
	if (nflags & AUD_EOS_SET) {
		rc = q6asm_cmd(audio->ac, CMD_EOS);
		pr_info("%s:session id %d: eos %d at input\n", __func__,
				audio->ac->session, audio->eos_rsp);
	}
	pr_debug("%s:session id %d: Written %d Avail Buf[%d]", __func__,
			audio->ac->session, (buf - start - mfield_size),
			atomic_read(&audio->in_count));
	if (!rc) {
		if (buf > start)
			return buf - start;
	}
	return rc;
}
static int msm_compr_trigger(struct snd_compr_stream *cstream, int cmd)
{
	struct snd_compr_runtime *runtime = cstream->runtime;
	struct msm_compr_audio *prtd = runtime->private_data;
	struct snd_soc_pcm_runtime *rtd = cstream->private_data;
	struct msm_compr_pdata *pdata =
			snd_soc_platform_get_drvdata(rtd->platform);
	uint32_t *volume = pdata->volume[rtd->dai_link->be_id];
	struct audio_client *ac = prtd->audio_client;
	int rc = 0;
	int bytes_to_write;
	unsigned long flags;
	int stream_id;

	if (cstream->direction != SND_COMPRESS_PLAYBACK) {
		pr_err("%s: Unsupported stream type\n", __func__);
		return -EINVAL;
	}

	spin_lock_irqsave(&prtd->lock, flags);
	if (atomic_read(&prtd->error)) {
		pr_err("%s Got RESET EVENTS notification, return immediately", __func__);
		spin_unlock_irqrestore(&prtd->lock, flags);
		return 0;
	}
	spin_unlock_irqrestore(&prtd->lock, flags);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		pr_debug("%s: SNDRV_PCM_TRIGGER_START\n", __func__);
		atomic_set(&prtd->start, 1);
		q6asm_run_nowait(prtd->audio_client, 0, 0, 0);

		msm_compr_set_volume(cstream, volume[0], volume[1]);
		if (rc)
			pr_err("%s : Set Volume failed : %d\n",
				__func__, rc);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		spin_lock_irqsave(&prtd->lock, flags);
		pr_debug("%s: SNDRV_PCM_TRIGGER_STOP transition %d\n", __func__,
					prtd->gapless_state.gapless_transition);
		stream_id = ac->stream_id;
		atomic_set(&prtd->start, 0);
		if (atomic_read(&prtd->eos)) {
			pr_debug("%s: interrupt eos wait queues", __func__);
			prtd->cmd_interrupt = 1;
			wake_up(&prtd->eos_wait);
			atomic_set(&prtd->eos, 0);
		}
		if (atomic_read(&prtd->drain)) {
			pr_debug("%s: interrupt drain wait queues", __func__);
			prtd->cmd_interrupt = 1;
			prtd->drain_ready = 1;
			wake_up(&prtd->drain_wait);
			atomic_set(&prtd->drain, 0);
		}
		prtd->last_buffer = 0;
		pr_debug("issue CMD_FLUSH\n");
		prtd->cmd_ack = 0;
		if (!prtd->gapless_state.gapless_transition) {
			spin_unlock_irqrestore(&prtd->lock, flags);
			rc = q6asm_stream_cmd(
				prtd->audio_client, CMD_FLUSH, stream_id);
			if (rc < 0) {
				pr_err("%s: flush cmd failed rc=%d\n",
							__func__, rc);
				return rc;
			}
			rc = wait_event_timeout(prtd->flush_wait,
					prtd->cmd_ack, 1 * HZ);
			if (!rc) {
				rc = -ETIMEDOUT;
				pr_err("Flush cmd timeout\n");
			} else {
				rc = 0; /* prtd->cmd_status == OK? 0 : -EPERM*/
			}
			spin_lock_irqsave(&prtd->lock, flags);
		} else {
			prtd->first_buffer = 0;
		}
		/* FIXME. only reset if flush was successful */
		prtd->byte_offset  = 0;
		prtd->copied_total = 0;
		prtd->app_pointer  = 0;
		prtd->bytes_received = 0;
		atomic_set(&prtd->xrun, 0);
		spin_unlock_irqrestore(&prtd->lock, flags);
		break;
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		pr_debug("SNDRV_PCM_TRIGGER_PAUSE_PUSH transition %d\n",
				prtd->gapless_state.gapless_transition);
		if (!prtd->gapless_state.gapless_transition) {
			q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);
			atomic_set(&prtd->start, 0);
		}
		break;
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		pr_debug("SNDRV_PCM_TRIGGER_PAUSE_RELEASE transition %d\n",
				   prtd->gapless_state.gapless_transition);
		if (!prtd->gapless_state.gapless_transition) {
			atomic_set(&prtd->start, 1);
			q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
		}
		break;
	case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
		pr_debug("%s: SND_COMPR_TRIGGER_PARTIAL_DRAIN\n", __func__);
		if (!prtd->gapless_state.use_dsp_gapless_mode) {
			pr_debug("%s: set partial drain as drain\n", __func__);
			cmd = SND_COMPR_TRIGGER_DRAIN;
		}
	case SND_COMPR_TRIGGER_DRAIN:
		pr_debug("%s: SNDRV_COMPRESS_DRAIN\n", __func__);
		/* Make sure all the data is sent to DSP before sending EOS */
		spin_lock_irqsave(&prtd->lock, flags);

		if (!atomic_read(&prtd->start)) {
			pr_err("%s: stream is not in started state\n",
				__func__);
			rc = -EPERM;
			spin_unlock_irqrestore(&prtd->lock, flags);
			break;
		}
		if (prtd->bytes_received > prtd->copied_total) {
			pr_debug("%s: wait till all the data is sent to dsp\n",
				__func__);
			rc = msm_compr_drain_buffer(prtd, &flags);
			if (rc || !atomic_read(&prtd->start)) {
				rc = -EINTR;
				spin_unlock_irqrestore(&prtd->lock, flags);
				break;
			}
			/*
			 * FIXME: Bug.
			 * Write(32767)
			 * Start
			 * Drain <- Indefinite wait
			 * sol1 : if (prtd->copied_total) then wait?
			 * sol2 : prtd->cmd_interrupt || prtd->drain_ready || atomic_read(xrun)
			 */
			bytes_to_write = prtd->bytes_received - prtd->copied_total;
			WARN(bytes_to_write > runtime->fragment_size,
			     "last write %d cannot be > than fragment_size",
			     bytes_to_write);

			if (bytes_to_write > 0) {
				pr_debug("%s: send %d partial bytes at the end",
				       __func__, bytes_to_write);
				atomic_set(&prtd->xrun, 0);
				prtd->last_buffer = 1;
				msm_compr_send_buffer(prtd);
			}
		}

		if ((cmd == SND_COMPR_TRIGGER_PARTIAL_DRAIN) &&
		    (prtd->gapless_state.set_next_stream_id)) {
			/* wait for the last buffer to be returned */

			if (prtd->last_buffer) {
				pr_debug("%s: last buffer drain\n", __func__);
				rc = msm_compr_drain_buffer(prtd, &flags);
				if (rc) {
					spin_unlock_irqrestore(&prtd->lock, flags);
					break;
				}
			}

			/* send EOS */
			prtd->cmd_ack = 0;
			q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
			pr_info("PARTIAL DRAIN, do not wait for EOS ack\n");

			/* send a zero length buffer */
			atomic_set(&prtd->xrun, 0);
			msm_compr_send_buffer(prtd);

			/* wait for the zero length buffer to be returned */
			pr_debug("%s: zero length buffer drain\n", __func__);
			rc = msm_compr_drain_buffer(prtd, &flags);
			if (rc) {
				spin_unlock_irqrestore(&prtd->lock, flags);
				break;
			}

			/* sleep for additional duration partial drain */
			atomic_set(&prtd->drain, 1);
			prtd->drain_ready = 0;
			pr_debug("%s, additional sleep: %d\n", __func__,
				 prtd->partial_drain_delay);
			spin_unlock_irqrestore(&prtd->lock, flags);
			rc = wait_event_timeout(prtd->drain_wait,
				prtd->drain_ready || prtd->cmd_interrupt,
				msecs_to_jiffies(prtd->partial_drain_delay));
			pr_debug("%s: out of additional wait for low sample rate\n",
				 __func__);
			spin_lock_irqsave(&prtd->lock, flags);
			if (prtd->cmd_interrupt) {
				pr_debug("%s: additional wait interrupted by flush)\n",
					 __func__);
				rc = -EINTR;
				prtd->cmd_interrupt = 0;
				spin_unlock_irqrestore(&prtd->lock, flags);
				break;
			}

			/* move to next stream and reset vars */
			pr_debug("%s: Moving to next stream in gapless\n", __func__);
			ac->stream_id ^= 1;
			prtd->byte_offset = 0;
			prtd->app_pointer  = 0;
			prtd->first_buffer = 1;
			prtd->last_buffer = 0;
			prtd->gapless_state.gapless_transition = 1;
			/*
			Don't reset these as these vars map to
			total_bytes_transferred and total_bytes_available
			directly, only total_bytes_transferred will be updated
			in the next avail() ioctl
				prtd->copied_total = 0;
				prtd->bytes_received = 0;
			*/
			atomic_set(&prtd->drain, 0);
			atomic_set(&prtd->xrun, 1);
			pr_debug("%s: issue CMD_RUN", __func__);
			q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
			spin_unlock_irqrestore(&prtd->lock, flags);
			break;
		}
		/*
		   moving to next stream failed, so reset the gapless state
		   set next stream id for the same session so that the same
		   stream can be used for gapless playback
		*/
		prtd->gapless_state.set_next_stream_id = false;
		pr_debug("%s: CMD_EOS\n", __func__);

		prtd->cmd_ack = 0;
		atomic_set(&prtd->eos, 1);
		q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);

		spin_unlock_irqrestore(&prtd->lock, flags);


		/* Wait indefinitely for  DRAIN. Flush can also signal this*/
		rc = wait_event_interruptible(prtd->eos_wait,
					      (prtd->cmd_ack || prtd->cmd_interrupt));

		if (rc < 0)
			pr_err("%s: EOS wait failed\n", __func__);

		pr_debug("%s: SNDRV_COMPRESS_DRAIN  out of wait for EOS\n", __func__);

		if (prtd->cmd_interrupt)
			rc = -EINTR;

		/*FIXME : what if a flush comes while PC is here */
		if (rc == 0) {
			/*
			 * Failed to open second stream in DSP for gapless
			 * so prepare the current stream in session for gapless playback
			 */
			spin_lock_irqsave(&prtd->lock, flags);
			pr_debug("%s: issue CMD_PAUSE ", __func__);
			q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);
			prtd->cmd_ack = 0;
			spin_unlock_irqrestore(&prtd->lock, flags);
			pr_debug("%s: issue CMD_FLUSH", __func__);
			q6asm_cmd(prtd->audio_client, CMD_FLUSH);
			wait_event_timeout(prtd->flush_wait,
					   prtd->cmd_ack, 1 * HZ / 4);

			spin_lock_irqsave(&prtd->lock, flags);
			/*
			Don't reset these as these vars map to
			total_bytes_transferred and total_bytes_available
			directly, only total_bytes_transferred will be updated
			in the next avail() ioctl
			prtd->copied_total = 0;
			prtd->bytes_received = 0;
			*/
			prtd->byte_offset = 0;
			prtd->app_pointer  = 0;
			prtd->first_buffer = 1;
			prtd->last_buffer = 0;
			atomic_set(&prtd->drain, 0);
			atomic_set(&prtd->xrun, 1);
			q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
			spin_unlock_irqrestore(&prtd->lock, flags);
		}
		prtd->cmd_interrupt = 0;
		break;
	case SND_COMPR_TRIGGER_NEXT_TRACK:
		if (!prtd->gapless_state.use_dsp_gapless_mode) {
			pr_debug("%s: ignore trigger next track\n", __func__);
			rc = 0;
			break;
		}
		pr_debug("%s: SND_COMPR_TRIGGER_NEXT_TRACK\n", __func__);
		spin_lock_irqsave(&prtd->lock, flags);
		rc = 0;
		stream_id = ac->stream_id^1; /*next stream in gapless*/
		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;
		}
		spin_unlock_irqrestore(&prtd->lock, flags);
		rc = q6asm_stream_open_write_v2(prtd->audio_client,
				prtd->codec, 16,
				stream_id,
				prtd->gapless_state.use_dsp_gapless_mode);
		if (rc < 0) {
			pr_err("%s: Session out open failed for gapless\n",
				 __func__);
			break;
		}
		rc = msm_compr_send_media_format_block(cstream, stream_id);
		if (rc < 0) {
			 pr_err("%s, failed to send media format block\n",
				__func__);
			break;
		}
		spin_lock_irqsave(&prtd->lock, flags);
		prtd->gapless_state.stream_opened[stream_id] = 1;
		prtd->gapless_state.set_next_stream_id = true;
		spin_unlock_irqrestore(&prtd->lock, flags);
		break;
	}

	return rc;
}