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; }
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; }
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; }