static int msm_compr_copy(struct snd_compr_stream *cstream, char __user *buf, size_t count) { struct snd_compr_runtime *runtime = cstream->runtime; struct msm_compr_audio *prtd = runtime->private_data; void *dstn; size_t copy; size_t bytes_available = 0; unsigned long flags; pr_debug("%s: count = %d\n", __func__, count); if (!prtd->buffer) { pr_err("%s: Buffer is not allocated yet ??", __func__); return 0; } dstn = prtd->buffer + prtd->app_pointer; if (count < prtd->buffer_size - prtd->app_pointer) { if (copy_from_user(dstn, buf, count)) return -EFAULT; prtd->app_pointer += count; } else { copy = prtd->buffer_size - prtd->app_pointer; if (copy_from_user(dstn, buf, copy)) return -EFAULT; if (copy_from_user(prtd->buffer, buf + copy, count - copy)) return -EFAULT; prtd->app_pointer = count - copy; } /* * If stream is started and there has been an xrun, * since the available bytes fits fragment_size, copy the data right away */ spin_lock_irqsave(&prtd->lock, flags); prtd->bytes_received += count; if (atomic_read(&prtd->start)) { if (atomic_read(&prtd->xrun)) { pr_debug("%s: in xrun, count = %d\n", __func__, count); bytes_available = prtd->bytes_received - prtd->copied_total; if (bytes_available >= runtime->fragment_size) { pr_debug("%s: handle xrun, bytes_to_write = %d\n", __func__, bytes_available); atomic_set(&prtd->xrun, 0); msm_compr_send_buffer(prtd); } /* else not sufficient data */ } /* writes will continue on the next write_done */ } spin_unlock_irqrestore(&prtd->lock, flags); return count; }
static int msm_compr_ack(struct snd_compr_stream *cstream, size_t count) { struct snd_compr_runtime *runtime = cstream->runtime; struct msm_compr_audio *prtd = runtime->private_data; void *src, *dstn; size_t copy; unsigned long flags; WARN(1, "This path is untested"); return -EINVAL; pr_debug("%s: count = %d\n", __func__, count); if (!prtd->buffer) { pr_err("%s: Buffer is not allocated yet ??\n", __func__); return -EINVAL; } src = runtime->buffer + prtd->app_pointer; dstn = prtd->buffer + prtd->app_pointer; if (count < prtd->buffer_size - prtd->app_pointer) { memcpy(dstn, src, count); prtd->app_pointer += count; } else { copy = prtd->buffer_size - prtd->app_pointer; memcpy(dstn, src, copy); memcpy(prtd->buffer, runtime->buffer, count - copy); prtd->app_pointer = count - copy; } /* * If the stream is started and all the bytes received were * copied to DSP, the newly received bytes should be * sent right away */ spin_lock_irqsave(&prtd->lock, flags); if (atomic_read(&prtd->start) && prtd->bytes_received == prtd->copied_total) { prtd->bytes_received += count; msm_compr_send_buffer(prtd); } else prtd->bytes_received += count; spin_unlock_irqrestore(&prtd->lock, flags); return 0; }
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; }
static void compr_event_handler(uint32_t opcode, uint32_t token, uint32_t *payload, void *priv) { struct msm_compr_audio *prtd = priv; struct snd_compr_stream *cstream = prtd->cstream; struct audio_client *ac = prtd->audio_client; uint32_t chan_mode = 0; uint32_t sample_rate = 0; int bytes_available, stream_id; pr_debug("%s opcode =%08x\n", __func__, opcode); switch (opcode) { case ASM_DATA_EVENT_WRITE_DONE_V2: spin_lock(&prtd->lock); if (payload[3]) { pr_err("WRITE FAILED w/ err 0x%x !, paddr 0x%x" " byte_offset = %d, copied_total = %d, token = %d\n", payload[3], payload[0], prtd->byte_offset, prtd->copied_total, token); atomic_set(&prtd->start, 0); } else { pr_debug("ASM_DATA_EVENT_WRITE_DONE_V2 offset %d, length %d\n", prtd->byte_offset, token); } prtd->byte_offset += token; prtd->copied_total += token; if (prtd->byte_offset >= prtd->buffer_size) prtd->byte_offset -= prtd->buffer_size; snd_compr_fragment_elapsed(cstream); if (!atomic_read(&prtd->start)) { /* Writes must be restarted from _copy() */ pr_debug("write_done received while not started, treat as xrun"); atomic_set(&prtd->xrun, 1); spin_unlock(&prtd->lock); break; } bytes_available = prtd->bytes_received - prtd->copied_total; if (bytes_available < cstream->runtime->fragment_size) { pr_debug("WRITE_DONE Insufficient data to send. break out\n"); atomic_set(&prtd->xrun, 1); if (prtd->last_buffer) prtd->last_buffer = 0; if (atomic_read(&prtd->drain)) { pr_debug("wake up on drain\n"); prtd->drain_ready = 1; wake_up(&prtd->drain_wait); atomic_set(&prtd->drain, 0); } } else if ((bytes_available == cstream->runtime->fragment_size) && atomic_read(&prtd->drain)) { prtd->last_buffer = 1; msm_compr_send_buffer(prtd); prtd->last_buffer = 0; } else msm_compr_send_buffer(prtd); spin_unlock(&prtd->lock); break; case ASM_DATA_EVENT_RENDERED_EOS: pr_debug("ASM_DATA_CMDRSP_EOS\n"); spin_lock(&prtd->lock); if (atomic_read(&prtd->eos) && !prtd->gapless_state.set_next_stream_id) { pr_debug("ASM_DATA_CMDRSP_EOS wake up\n"); prtd->cmd_ack = 1; wake_up(&prtd->eos_wait); } atomic_set(&prtd->eos, 0); stream_id = ac->stream_id^1; /*prev stream */ if (prtd->gapless_state.set_next_stream_id && prtd->gapless_state.stream_opened[stream_id]) { q6asm_stream_cmd_nowait(prtd->audio_client, CMD_CLOSE, stream_id); atomic_set(&prtd->close, 1); prtd->gapless_state.stream_opened[stream_id] = 0; prtd->gapless_state.set_next_stream_id = false; } spin_unlock(&prtd->lock); break; case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: case ASM_DATA_EVENT_ENC_SR_CM_CHANGE_NOTIFY: { pr_debug("ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY\n"); chan_mode = payload[1] >> 16; sample_rate = payload[2] >> 16; if (prtd && (chan_mode != prtd->num_channels || sample_rate != prtd->sample_rate)) { prtd->num_channels = chan_mode; prtd->sample_rate = sample_rate; } } case APR_BASIC_RSP_RESULT: { switch (payload[0]) { case ASM_SESSION_CMD_RUN_V2: /* check if the first buffer need to be sent to DSP */ pr_debug("ASM_SESSION_CMD_RUN_V2\n"); spin_lock(&prtd->lock); /* FIXME: A state is a much better way of dealing with this */ if (!prtd->copied_total) { bytes_available = prtd->bytes_received - prtd->copied_total; if (bytes_available < cstream->runtime->fragment_size) { pr_debug("CMD_RUN_V2 Insufficient data to send. break out\n"); atomic_set(&prtd->xrun, 1); } else msm_compr_send_buffer(prtd); } spin_unlock(&prtd->lock); break; case ASM_STREAM_CMD_FLUSH: pr_debug("ASM_STREAM_CMD_FLUSH\n"); prtd->cmd_ack = 1; wake_up(&prtd->flush_wait); break; case ASM_DATA_CMD_REMOVE_INITIAL_SILENCE: pr_debug("ASM_DATA_CMD_REMOVE_INITIAL_SILENCE\n"); break; case ASM_DATA_CMD_REMOVE_TRAILING_SILENCE: pr_debug("ASM_DATA_CMD_REMOVE_TRAILING_SILENCE\n"); break; case ASM_STREAM_CMD_CLOSE: pr_debug("ASM_DATA_CMD_CLOSE\n"); if (atomic_read(&prtd->close) && atomic_read(&prtd->wait_on_close)) { prtd->cmd_ack = 1; wake_up(&prtd->close_wait); } atomic_set(&prtd->close, 0); break; default: break; } break; } case ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3: pr_debug("ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3\n"); break; case RESET_EVENTS: pr_err("Received reset events CB, move to error state"); spin_lock(&prtd->lock); snd_compr_fragment_elapsed(cstream); prtd->copied_total = prtd->bytes_received; atomic_set(&prtd->error, 1); spin_unlock(&prtd->lock); break; default: pr_debug("Not Supported Event opcode[0x%x]\n", opcode); break; } }