// 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.// #include <linux/pm_runtime.h>#include <sound/pcm_params.h>#include <sound/sof.h>#include <trace/events/sof.h>#include "sof-of-dev.h"#include "sof-priv.h"#include "sof-audio.h"#include "sof-utils.h"#include "ops.h" /* * sof pcm period elapse work */static void snd_sof_pcm_period_elapsed_work(struct work_struct *work){ struct snd_sof_pcm_stream *sps = container_of(work, struct snd_sof_pcm_stream, period_elapsed_work); snd_pcm_period_elapsed(sps->substream);} void snd_sof_pcm_init_elapsed_work(struct work_struct *work){ INIT_WORK(work, snd_sof_pcm_period_elapsed_work);} /* * 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(). */ queue_work(system_highpri_wq, &spcm->stream[substream->stream].period_elapsed_work);}EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); static intsof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params, int dir){ struct snd_soc_dai *dai; int ret, j; /* query DAPM for list of connected widgets and set them up */ for_each_rtd_cpu_dais(rtd, j, dai) { struct snd_soc_dapm_widget_list *list; ret = snd_soc_dapm_dai_get_connected_widgets(dai, dir, &list, dpcm_end_walk_at_be); if (ret < 0) { spcm_err(spcm, dir, "dai %s has no valid %s path\n", dai->name, snd_pcm_direction_name(dir)); return ret; } spcm->stream[dir].list = list; ret = sof_widget_list_prepare(sdev, spcm, params, platform_params, dir); if (ret < 0) { spcm_err(spcm, dir, "widget list prepare failed\n"); spcm->stream[dir].list = NULL; snd_soc_dapm_dai_free_widgets(&list); return ret; } } return 0;} static struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, int comp_id){ struct snd_sof_widget *swidget; list_for_each_entry(swidget, &sdev->widget_list, list) { if (comp_id == swidget->comp_id) return swidget; } return NULL;} static int sof_pcm_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params){ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_platform_stream_params *platform_params; struct snd_pcm_runtime *runtime = substream->runtime; struct snd_sof_widget *host_widget; struct snd_sof_pcm *spcm; int ret; /* 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; spcm_dbg(spcm, substream->stream, "Entry: hw_params\n"); if (!sdev->dspless_mode_selected) { /* * Make sure that the DSP is booted up, which might not be the * case if the on-demand DSP boot is used */ ret = snd_sof_boot_dsp_firmware(sdev); if (ret) return ret; } /* * 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; } platform_params = &spcm->platform_params[substream->stream]; 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; } if (!sdev->dspless_mode_selected) { int host_comp_id = spcm->stream[substream->stream].comp_id; host_widget = snd_sof_find_swidget_by_comp_id(sdev, host_comp_id); if (!host_widget) { spcm_err(spcm, substream->stream, "failed to find host widget with comp_id %d\n", host_comp_id); return -EINVAL; } /* set the host DMA ID */ if (tplg_ops && tplg_ops->host_config) tplg_ops->host_config(sdev, host_widget, platform_params); } /* 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; } /* save pcm hw_params */ memcpy(&spcm->params[substream->stream], params, sizeof(*params)); return 0;} static int 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){ const struct 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; } } spcm->prepared[substream->stream] = false; spcm->pending_stop[substream->stream] = false; } /* reset the DMA */ ret = snd_sof_pcm_platform_hw_free(sdev, substream); if (ret < 0) { spcm_err(spcm, substream->stream, "platform hw free failed %d\n", ret); if (!err) err = ret; } /* free widget list */ if (free_widget_list) { ret = sof_widget_list_free(sdev, spcm, dir); if (ret < 0) { spcm_err(spcm, substream->stream, "sof_widget_list_free failed %d\n", ret); if (!err) err = ret; } } return err;} int sof_pcm_free_all_streams(struct snd_sof_dev *sdev){ struct snd_pcm_substream *substream; struct snd_sof_pcm *spcm; int dir, ret; list_for_each_entry(spcm, &sdev->pcm_list, list) { for_each_pcm_streams(dir) { substream = spcm->stream[dir].substream; if (!substream || !substream->runtime || spcm->stream[dir].suspend_ignored) continue; if (spcm->stream[dir].list) { ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true); if (ret < 0) return ret; } } } return 0;} static int sof_pcm_hw_free(struct snd_soc_component *component, struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; int ret; /* 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; spcm_dbg(spcm, substream->stream, "Entry: hw_free\n"); ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true); /* unprepare and free the list of DAPM widgets */ sof_widget_list_unprepare(sdev, spcm, substream->stream); cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); return ret;} static int sof_pcm_prepare(struct snd_soc_component *component, struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_platform_stream_params *platform_params; struct snd_soc_dapm_widget_list *list; struct snd_pcm_hw_params *params; struct snd_sof_pcm *spcm; int dir = substream->stream; int ret; /* 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; spcm_dbg(spcm, substream->stream, "Entry: prepare\n"); 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; } 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; } list = spcm->stream[dir].list; params = &spcm->params[substream->stream]; platform_params = &spcm->platform_params[substream->stream]; ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir); if (ret < 0) { dev_err(sdev->dev, "failed widget list set up for pcm %d dir %u\n", le32_to_cpu(spcm->pcm.pcm_id), dir); spcm->stream[dir].list = NULL; snd_soc_dapm_dai_free_widgets(&list); 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; return 0;} /* * FE dai link trigger actions are always executed in non-atomic context because * they involve IPC's. */static int 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); const struct 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; spcm_dbg(spcm, substream->stream, "Entry: trigger (cmd: %d)\n", cmd); spcm->pending_stop[substream->stream] = false; 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; fallthrough; case SNDRV_PCM_TRIGGER_STOP: ipc_first = true; if (pcm_ops && pcm_ops->reset_hw_params_during_stop) reset_hw_params = true; break; default: spcm_err(spcm, substream->stream, "Unhandled trigger cmd %d\n", cmd); return -EINVAL; } 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); return ret;} static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);Lines 1–500 of 870. Load more
// 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. // #include <linux/pm_runtime.h> #include <sound/pcm_params.h> #include <sound/sof.h> #include <trace/events/sof.h> #include "sof-of-dev.h" #include "sof-priv.h" #include "sof-audio.h" #include "sof-utils.h" #include "ops.h" /* * sof pcm period elapse work */ static void snd_sof_pcm_period_elapsed_work(struct work_struct *work) { struct snd_sof_pcm_stream *sps = container_of(work, struct snd_sof_pcm_stream, period_elapsed_work); snd_pcm_period_elapsed(sps->substream); } void snd_sof_pcm_init_elapsed_work(struct work_struct *work) { INIT_WORK(work, snd_sof_pcm_period_elapsed_work); } /* * 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(). */ queue_work(system_highpri_wq, &spcm->stream[substream->stream].period_elapsed_work); } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); static int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params, int dir) { struct snd_soc_dai *dai; int ret, j; /* query DAPM for list of connected widgets and set them up */ for_each_rtd_cpu_dais(rtd, j, dai) { struct snd_soc_dapm_widget_list *list; ret = snd_soc_dapm_dai_get_connected_widgets(dai, dir, &list, dpcm_end_walk_at_be); if (ret < 0) { spcm_err(spcm, dir, "dai %s has no valid %s path\n", dai->name, snd_pcm_direction_name(dir)); return ret; } spcm->stream[dir].list = list; ret = sof_widget_list_prepare(sdev, spcm, params, platform_params, dir); if (ret < 0) { spcm_err(spcm, dir, "widget list prepare failed\n"); spcm->stream[dir].list = NULL; snd_soc_dapm_dai_free_widgets(&list); return ret; } } return 0; } static struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, int comp_id) { struct snd_sof_widget *swidget; list_for_each_entry(swidget, &sdev->widget_list, list) { if (comp_id == swidget->comp_id) return swidget; } return NULL; } static int sof_pcm_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_platform_stream_params *platform_params; struct snd_pcm_runtime *runtime = substream->runtime; struct snd_sof_widget *host_widget; struct snd_sof_pcm *spcm; int ret; /* 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; spcm_dbg(spcm, substream->stream, "Entry: hw_params\n"); if (!sdev->dspless_mode_selected) { /* * Make sure that the DSP is booted up, which might not be the * case if the on-demand DSP boot is used */ ret = snd_sof_boot_dsp_firmware(sdev); if (ret) return ret; } /* * 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; } platform_params = &spcm->platform_params[substream->stream]; 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; } if (!sdev->dspless_mode_selected) { int host_comp_id = spcm->stream[substream->stream].comp_id; host_widget = snd_sof_find_swidget_by_comp_id(sdev, host_comp_id); if (!host_widget) { spcm_err(spcm, substream->stream, "failed to find host widget with comp_id %d\n", host_comp_id); return -EINVAL; } /* set the host DMA ID */ if (tplg_ops && tplg_ops->host_config) tplg_ops->host_config(sdev, host_widget, platform_params); } /* 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; } /* save pcm hw_params */ memcpy(&spcm->params[substream->stream], params, sizeof(*params)); return 0; } static int 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) { const struct 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; } } spcm->prepared[substream->stream] = false; spcm->pending_stop[substream->stream] = false; } /* reset the DMA */ ret = snd_sof_pcm_platform_hw_free(sdev, substream); if (ret < 0) { spcm_err(spcm, substream->stream, "platform hw free failed %d\n", ret); if (!err) err = ret; } /* free widget list */ if (free_widget_list) { ret = sof_widget_list_free(sdev, spcm, dir); if (ret < 0) { spcm_err(spcm, substream->stream, "sof_widget_list_free failed %d\n", ret); if (!err) err = ret; } } return err; } int sof_pcm_free_all_streams(struct snd_sof_dev *sdev) { struct snd_pcm_substream *substream; struct snd_sof_pcm *spcm; int dir, ret; list_for_each_entry(spcm, &sdev->pcm_list, list) { for_each_pcm_streams(dir) { substream = spcm->stream[dir].substream; if (!substream || !substream->runtime || spcm->stream[dir].suspend_ignored) continue; if (spcm->stream[dir].list) { ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true); if (ret < 0) return ret; } } } return 0; } static int sof_pcm_hw_free(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; int ret; /* 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; spcm_dbg(spcm, substream->stream, "Entry: hw_free\n"); ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true); /* unprepare and free the list of DAPM widgets */ sof_widget_list_unprepare(sdev, spcm, substream->stream); cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); return ret; } static int sof_pcm_prepare(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_platform_stream_params *platform_params; struct snd_soc_dapm_widget_list *list; struct snd_pcm_hw_params *params; struct snd_sof_pcm *spcm; int dir = substream->stream; int ret; /* 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; spcm_dbg(spcm, substream->stream, "Entry: prepare\n"); 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; } 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; } list = spcm->stream[dir].list; params = &spcm->params[substream->stream]; platform_params = &spcm->platform_params[substream->stream]; ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir); if (ret < 0) { dev_err(sdev->dev, "failed widget list set up for pcm %d dir %u\n", le32_to_cpu(spcm->pcm.pcm_id), dir); spcm->stream[dir].list = NULL; snd_soc_dapm_dai_free_widgets(&list); 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; return 0; } /* * FE dai link trigger actions are always executed in non-atomic context because * they involve IPC's. */ static int 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); const struct 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; spcm_dbg(spcm, substream->stream, "Entry: trigger (cmd: %d)\n", cmd); spcm->pending_stop[substream->stream] = false; 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; fallthrough; case SNDRV_PCM_TRIGGER_STOP: ipc_first = true; if (pcm_ops && pcm_ops->reset_hw_params_during_stop) reset_hw_params = true; break; default: spcm_err(spcm, substream->stream, "Unhandled trigger cmd %d\n", cmd); return -EINVAL; } 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); return ret; } static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);