// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) // // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // // Copyright(c) 2018 Intel Corporation // // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> // // PCM Layer, interface between ALSA and IPC. //
/* * sof pcm period elapse, this could be called at irq thread context.
*/ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component =
snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); struct snd_sof_pcm *spcm;
spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) {
dev_err(component->dev, "error: period elapsed for unknown stream!\n"); return;
}
/* * snd_pcm_period_elapsed() can be called in interrupt context * before IRQ_HANDLED is returned. Inside snd_pcm_period_elapsed(), * when the PCM is done draining or xrun happened, a STOP IPC will * then be sent and this IPC will hit IPC timeout. * To avoid sending IPC before the previous IPC is handled, we * schedule delayed work here to call the snd_pcm_period_elapsed().
*/
schedule_work(&spcm->stream[substream->stream].period_elapsed_work);
}
EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
/* * Handle repeated calls to hw_params() without free_pcm() in * between. At least ALSA OSS emulation depends on this.
*/ if (spcm->prepared[substream->stream] && pcm_ops && pcm_ops->hw_free) {
ret = pcm_ops->hw_free(component, substream); if (ret < 0) return ret;
spcm->prepared[substream->stream] = false;
}
ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params); if (ret < 0) {
spcm_err(spcm, substream->stream, "platform hw params failed\n"); return ret;
}
/* if this is a repeated hw_params without hw_free, skip setting up widgets */ if (!spcm->stream[substream->stream].list) {
ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, params, &platform_params,
substream->stream); if (ret < 0) return ret;
}
/* create compressed page table for audio firmware */ if (runtime->buffer_changed) { struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream);
ret = snd_sof_create_page_table(component->dev, dmab,
spcm->stream[substream->stream].page_table.area,
runtime->dma_bytes); if (ret < 0) return ret;
}
if (pcm_ops && pcm_ops->hw_params) {
ret = pcm_ops->hw_params(component, substream, params, &platform_params); if (ret < 0) return ret;
}
spcm->prepared[substream->stream] = true;
/* save pcm hw_params */
memcpy(&spcm->params[substream->stream], params, sizeof(*params));
return 0;
}
staticint sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_sof_pcm *spcm, int dir, bool free_widget_list)
{ conststruct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); int ret; int err = 0;
if (spcm->prepared[substream->stream]) { /* stop DMA first if needed */ if (pcm_ops && pcm_ops->platform_stop_during_hw_free)
snd_sof_pcm_platform_trigger(sdev, substream,
SNDRV_PCM_TRIGGER_STOP);
/* free PCM in the DSP */ if (pcm_ops && pcm_ops->hw_free) {
ret = pcm_ops->hw_free(sdev->component, substream); if (ret < 0) {
spcm_err(spcm, substream->stream, "pcm_ops->hw_free failed %d\n", ret);
err = ret;
}
}
if (spcm->prepared[substream->stream]) { if (!spcm->pending_stop[substream->stream]) return 0;
/* * this case should be reached in case of xruns where we absolutely * want to free-up and reset all PCM/DMA resources
*/
ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true); if (ret < 0) return ret;
}
/* set hw_params */
ret = sof_pcm_hw_params(component,
substream, &spcm->params[substream->stream]); if (ret < 0) {
spcm_err(spcm, substream->stream, "failed to set hw_params after resume\n"); return ret;
}
return 0;
}
/* * FE dai link trigger actions are always executed in non-atomic context because * they involve IPC's.
*/ staticint sof_pcm_trigger(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); conststruct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_pcm *spcm; bool reset_hw_params = false; bool ipc_first = false; int ret = 0;
/* nothing to do for BE */ if (rtd->dai_link->no_pcm) return 0;
spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL;
switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ipc_first = true; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (pcm_ops && pcm_ops->ipc_first_on_start)
ipc_first = true; break; case SNDRV_PCM_TRIGGER_START: if (spcm->stream[substream->stream].suspend_ignored) { /* * This case will be triggered when INFO_RESUME is * not supported, no need to re-start streams that * remained enabled in D0ix.
*/
spcm->stream[substream->stream].suspend_ignored = false; return 0;
}
if (pcm_ops && pcm_ops->ipc_first_on_start)
ipc_first = true; break; case SNDRV_PCM_TRIGGER_SUSPEND: /* * If DSP D0I3 is allowed during S0iX, set the suspend_ignored flag for * D0I3-compatible streams to keep the firmware pipeline running
*/ if (pcm_ops && pcm_ops->d0i3_supported_in_s0ix &&
sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
spcm->stream[substream->stream].d0i3_compatible) {
spcm->stream[substream->stream].suspend_ignored = true; return 0;
}
/* On suspend the DMA must be stopped in DSPless mode */ if (sdev->dspless_mode_selected)
reset_hw_params = true;
if (!ipc_first)
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
if (pcm_ops && pcm_ops->trigger)
ret = pcm_ops->trigger(component, substream, cmd);
switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_START: /* invoke platform trigger to start DMA only if pcm_ops is successful */ if (ipc_first && !ret)
snd_sof_pcm_platform_trigger(sdev, substream, cmd); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_STOP: /* invoke platform trigger to stop DMA even if pcm_ops isn't set or if it failed */ if (!pcm_ops || !pcm_ops->platform_stop_during_hw_free)
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
/* * set the pending_stop flag to indicate that pipeline stop has been delayed. * This will be used later to stop the pipelines during prepare when recovering * from xruns.
*/ if (pcm_ops && pcm_ops->platform_stop_during_hw_free &&
cmd == SNDRV_PCM_TRIGGER_STOP)
spcm->pending_stop[substream->stream] = true; break; default: break;
}
/* free PCM if reset_hw_params is set and the STOP IPC is successful */ if (!ret && reset_hw_params)
ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, false);
/* nothing to do for BE */ if (rtd->dai_link->no_pcm) return 0;
if (pcm_ops && pcm_ops->pointer)
ret = pcm_ops->pointer(component, substream, &host);
if (ret != -EOPNOTSUPP) return ret ? ret : host;
/* use dsp ops pointer callback directly if set */ if (sof_ops(sdev)->pcm_pointer) return sof_ops(sdev)->pcm_pointer(sdev, substream);
spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL;
/* read position from DSP */
host = bytes_to_frames(substream->runtime,
spcm->stream[substream->stream].posn.host_posn);
dai = bytes_to_frames(substream->runtime,
spcm->stream[substream->stream].posn.dai_posn);
/* set any runtime constraints based on topology */
runtime->hw.formats = le64_to_cpu(caps->formats);
runtime->hw.period_bytes_min = le32_to_cpu(caps->period_size_min);
runtime->hw.period_bytes_max = le32_to_cpu(caps->period_size_max);
runtime->hw.periods_min = le32_to_cpu(caps->periods_min);
runtime->hw.periods_max = le32_to_cpu(caps->periods_max);
/* * caps->buffer_size_min is not used since the * snd_pcm_hardware structure only defines buffer_bytes_max
*/
runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max);
/* set wait time - TODO: come from topology */
substream->wait_time = 500;
err = snd_sof_pcm_platform_close(sdev, substream); if (err < 0) {
spcm_err(spcm, substream->stream, "platform pcm close failed %d\n", err); /* * keep going, no point in preventing the close * from happening
*/
}
spcm->stream[substream->stream].substream = NULL;
return 0;
}
/* * Pre-allocate playback/capture audio buffer pages. * no need to explicitly release memory preallocated by sof_pcm_new in pcm_free * snd_pcm_lib_preallocate_free_for_all() is called by the core.
*/ staticint sof_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd)
{ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; struct snd_pcm *pcm = rtd->pcm; struct snd_soc_tplg_stream_caps *caps; int stream = SNDRV_PCM_STREAM_PLAYBACK;
/* find SOF PCM for this RTD */
spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) {
dev_warn(component->dev, "warn: can't find PCM with DAI ID %d\n",
rtd->dai_link->id); return 0;
}
/* fixup the BE DAI link to match any values from topology */ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params)
{ struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE); struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_soc_component *component =
snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); struct snd_sof_dai *dai =
snd_sof_find_dai(component, (char *)rtd->dai_link->name); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); conststruct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
/* no topology exists for this BE, try a common configuration */ if (!dai) {
dev_warn(component->dev, "warning: no topology found for BE DAI %s config\n",
rtd->dai_link->name);
/* set 48k, stereo, 16bits by default */
rate->min = 48000;
rate->max = 48000;
/* * make sure the device is pm_runtime_active before loading the * topology and initiating IPC or bus transactions
*/
ret = pm_runtime_resume_and_get(component->dev); if (ret < 0 && ret != -EACCES) return ret;
/* load the default topology */
sdev->component = component;
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, "%s/%s",
plat_data->tplg_filename_prefix,
plat_data->tplg_filename); if (!tplg_filename) {
ret = -ENOMEM; goto pm_error;
}
ret = snd_sof_load_topology(component, tplg_filename); if (ret < 0)
dev_err(component->dev, "error: failed to load DSP topology %d\n",
ret);
/* increment module refcount when a pcm is opened */
pd->module_get_upon_open = 1;
pd->legacy_dai_naming = 1;
/* * The fixup is only needed when the DSP is in use as with the DSPless * mode we are directly using the audio interface
*/ if (!sdev->dspless_mode_selected)
pd->be_hw_params_fixup = sof_pcm_dai_link_fixup;
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.