870 lines · 24.4 KB · C
RawDownload
// 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.