Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7e805357 authored by Meng Wang's avatar Meng Wang Committed by Meng Wang
Browse files

ASoC: Cold start latency reduction



This patch parallelizes FE (front end) and BE (back end) when
playback/capture path is set up. FE's and BE's which can be
parallelized should be indicated in corresponding machine file.
All the operations can be started asynchronously provided the usecase
supports asynchronous operation.
Parallelizing reduces the startup latency as both FE and BE operations
run in parallel.

Change-Id: I17e7e1c3d406713cc728ec262cfe388f7251f7de
Signed-off-by: default avatarWalter Yang <yandongy@codeaurora.org>
Signed-off-by: default avatarFred Oh <fred@codeaurora.org>
Signed-off-by: default avatarBanajit Goswami <bgoswami@codeaurora.org>
Signed-off-by: default avatarMeng Wang <mwang@codeaurora.org>
parent 567264c6
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -71,6 +71,9 @@ struct snd_compr_stream {
	bool metadata_set;
	bool next_track;
	void *private_data;
#ifdef CONFIG_AUDIO_QGKI
	struct snd_soc_pcm_runtime *be;
#endif
};

/**
+15 −0
Original line number Diff line number Diff line
@@ -14,6 +14,9 @@

struct snd_soc_pcm_runtime;

#ifdef CONFIG_AUDIO_QGKI
#define DPCM_MAX_BE_USERS   8
#endif
/*
 * Types of runtime_update to perform. e.g. originated from FE PCM ops
 * or audio route changes triggered by muxes/mixers.
@@ -83,6 +86,9 @@ struct snd_soc_dpcm {
#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs_state;
#endif
#ifdef CONFIG_AUDIO_QGKI
	int stream;
#endif
};

/*
@@ -162,8 +168,17 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream);
void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream);
int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream);
int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int tream);
#ifdef CONFIG_AUDIO_QGKI
int dpcm_fe_dai_hw_params_be(struct snd_soc_pcm_runtime *fe,
	struct snd_soc_pcm_runtime *be, struct snd_pcm_hw_params *hw_params,
							    int stream);
#endif
int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, int cmd);
int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream);
#ifdef CONFIG_AUDIO_QGKI
int dpcm_fe_dai_prepare_be(struct snd_soc_pcm_runtime *fe,
	struct snd_soc_pcm_runtime *be, int stream);
#endif
int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir,
	int event);

+22 −1
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@
#include <linux/kernel.h>
#include <linux/regmap.h>
#include <linux/log2.h>
#ifdef CONFIG_AUDIO_QGKI
#include <linux/async.h>
#endif
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/compress_driver.h>
@@ -765,6 +768,16 @@ struct snd_soc_dai_link_component {
	const char *dai_name;
};

#ifdef CONFIG_AUDIO_QGKI
enum snd_soc_async_ops {
	ASYNC_DPCM_SND_SOC_OPEN = 1 << 0,
	ASYNC_DPCM_SND_SOC_CLOSE = 1 << 1,
	ASYNC_DPCM_SND_SOC_PREPARE = 1 << 2,
	ASYNC_DPCM_SND_SOC_HW_PARAMS = 1 << 3,
	ASYNC_DPCM_SND_SOC_FREE = 1 << 4,
};
#endif

struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
@@ -865,6 +878,11 @@ struct snd_soc_dai_link {

	struct list_head list; /* DAI link list of the soc card */
	struct snd_soc_dobj dobj; /* For topology */

#ifdef CONFIG_AUDIO_QGKI
	/* this value determines what all ops can be started asynchronously */
	enum snd_soc_async_ops async_ops;
#endif
};
#define for_each_link_codecs(link, i, codec)				\
	for ((i) = 0;							\
@@ -1147,7 +1165,10 @@ struct snd_soc_pcm_runtime {
	struct snd_soc_dpcm_runtime dpcm[2];

	long pmdown_time;

#ifdef CONFIG_AUDIO_QGKI
	/* err in case of ops failed */
	int err_ops;
#endif
	/* runtime devices */
	struct snd_pcm *pcm;
	struct snd_compr *compr;
+157 −0
Original line number Diff line number Diff line
@@ -450,6 +450,39 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd)
	return ret;
}

#ifdef CONFIG_AUDIO_QGKI
static void dpcm_be_hw_params_prepare(void *data)
{
	struct snd_compr_stream *cstream = data;
	struct snd_soc_pcm_runtime *fe = cstream->private_data;
	struct snd_soc_pcm_runtime *be = cstream->be;
	int stream, ret;

	if (cstream->direction == SND_COMPRESS_PLAYBACK)
		stream = SNDRV_PCM_STREAM_PLAYBACK;
	else
		stream = SNDRV_PCM_STREAM_CAPTURE;

	ret = dpcm_fe_dai_hw_params_be(fe, be,
			&fe->dpcm[stream].hw_params, stream);
	if (ret < 0) {
		fe->err_ops = ret;
		return;
	}

	ret = dpcm_fe_dai_prepare_be(fe, be, stream);
	if (ret < 0) {
		fe->err_ops = ret;
		return;
	}
}

static void dpcm_be_hw_params_prepare_async(void *data, async_cookie_t cookie)
{
	dpcm_be_hw_params_prepare(data);
}
#endif

static int soc_compr_components_set_params(struct snd_compr_stream *cstream,
					   struct snd_compr_params *params)
{
@@ -532,7 +565,17 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
	struct snd_pcm_substream *fe_substream =
		 fe->pcm->streams[cstream->direction].substream;
	struct snd_soc_dai *cpu_dai = fe->cpu_dai;
#ifdef CONFIG_AUDIO_QGKI
	struct snd_soc_component *component;
	struct snd_soc_rtdcom_list *rtdcom;
	struct snd_soc_pcm_runtime *be_list[DPCM_MAX_BE_USERS];
	struct snd_soc_dpcm *dpcm;
	int ret, __ret, stream, i, j = 0;

	ASYNC_DOMAIN_EXCLUSIVE(async_domain);
#else
	int ret, stream;
#endif

	if (cstream->direction == SND_COMPRESS_PLAYBACK)
		stream = SNDRV_PCM_STREAM_PLAYBACK;
@@ -564,7 +607,120 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
		if (ret < 0)
			goto out;
	}
#ifdef CONFIG_AUDIO_QGKI
	if (!(fe->dai_link->async_ops & ASYNC_DPCM_SND_SOC_HW_PARAMS)) {
		/* first we call set_params for the platform driver
		 * this should configure the soc side
		 * if the machine has compressed ops then we call that as well
		 * expectation is that platform and machine will configure
		 * everything for this compress path, like configuring pcm
		 * port for codec
		 */
		ret = soc_compr_components_set_params(cstream, params);
		if (ret < 0)
			goto out;

		if (fe->dai_link->compr_ops &&
				fe->dai_link->compr_ops->set_params) {
			ret = fe->dai_link->compr_ops->set_params(cstream);
			if (ret < 0)
				goto out;
		}
		/*
		 * Create an empty hw_params for the BE as the machine
		 * driver must fix this up to match DSP decoder and
		 * ASRC configuration.
		 * I.e. machine driver fixup for compressed BE is
		 * mandatory.
		 */
		memset(&fe->dpcm[fe_substream->stream].hw_params, 0,
				sizeof(struct snd_pcm_hw_params));

		fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

		ret = dpcm_be_dai_hw_params(fe, stream);
		if (ret < 0)
			goto out;

		ret = dpcm_be_dai_prepare(fe, stream);
		if (ret < 0)
			goto out;
	} else {
		/*
		 * Create an empty hw_params for the BE as the machine
		 * driver must fix this up to match DSP decoder and
		 * ASRC configuration.
		 * I.e. machine driver fixup for compressed BE is
		 * mandatory.
		 */
		memset(&fe->dpcm[fe_substream->stream].hw_params, 0,
				sizeof(struct snd_pcm_hw_params));

		fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;

		list_for_each_entry(dpcm,
				&fe->dpcm[stream].be_clients, list_be) {
			struct snd_soc_pcm_runtime *be = dpcm->be;

			if (be->dai_link->async_ops &
				ASYNC_DPCM_SND_SOC_HW_PARAMS) {
				cstream->be = be;
				async_schedule_domain(
				dpcm_be_hw_params_prepare_async,
				cstream, &async_domain);
			} else {
				be_list[j++] = be;
			}
		}
		for (i = 0; i < j; i++) {
			cstream->be = be_list[i];
			dpcm_be_hw_params_prepare(cstream);
		}

		if (cpu_dai->driver->cops &&
			cpu_dai->driver->cops->set_params) {
			ret = cpu_dai->driver->cops->set_params(
					cstream, params, cpu_dai);
			if (ret < 0)
				goto exit;
		}

		/* first we call set_params for the platform driver
		 * this should configure the soc side
		 * if the machine has compressed ops then we call that as well
		 * expectation is that platform and machine will configure
		 * everything this compress path, like configuring pcm port
		 * for codec
		 */
		for_each_rtdcom(fe, rtdcom) {
			component = rtdcom->component;

			if (!component->driver->compr_ops ||
			    !component->driver->compr_ops->set_params)
				continue;

			__ret = component->driver->compr_ops->set_params(
					cstream, params);
			if (__ret < 0)
				ret = __ret;
		}
		if (ret < 0)
			goto exit;

		dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START);

		if (fe->dai_link->compr_ops &&
				fe->dai_link->compr_ops->set_params) {
			ret = fe->dai_link->compr_ops->set_params(cstream);
			if (ret < 0)
				goto exit;
		}
exit:
		async_synchronize_full_domain(&async_domain);
		if (fe->err_ops < 0 || ret < 0)
			goto out;
	}
#else
	ret = soc_compr_components_set_params(cstream, params);
	if (ret < 0)
		goto out;
@@ -574,6 +730,7 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
		if (ret < 0)
			goto out;
	}
#endif

	dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START);
	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
+235 −1
Original line number Diff line number Diff line
@@ -2061,6 +2061,83 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream)
	return 0;
}

#ifdef CONFIG_AUDIO_QGKI
int dpcm_fe_dai_hw_params_be(struct snd_soc_pcm_runtime *fe,
	struct snd_soc_pcm_runtime *be,
	struct snd_pcm_hw_params *params, int stream)
{
	int ret;
	struct snd_soc_dpcm *dpcm;
	struct snd_pcm_substream *be_substream =
		snd_soc_dpcm_get_substream(be, stream);

	/* is this op for this BE ? */
	if (!snd_soc_dpcm_be_can_update(fe, be, stream))
		return 0;

	/* only allow hw_params() if no connected FEs are running */
	if (!snd_soc_dpcm_can_be_params(fe, be, stream))
		return 0;

	if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) &&
			(be->dpcm[stream].state !=
				SND_SOC_DPCM_STATE_HW_PARAMS) &&
			(be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE))
		return 0;

	dev_dbg(be->dev, "ASoC: hw_params BE %s\n",
			fe->dai_link->name);

	/* perform any hw_params fixups */
	if (be->dai_link->be_hw_params_fixup) {
		ret = be->dai_link->be_hw_params_fixup(be,
				params);
		if (ret < 0) {
			dev_err(be->dev,
					"ASoC: hw_params BE fixup failed %d\n",
					ret);
			goto unwind;
		}
	}

	ret = soc_pcm_hw_params(be_substream, params);
	if (ret < 0) {
		dev_err(be->dev, "ASoC: hw_params BE failed %d\n", ret);
		goto unwind;
	}

	be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS;
	return 0;

unwind:
	/* disable any enabled and non active backends */
	list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
		struct snd_soc_pcm_runtime *be = dpcm->be;
		struct snd_pcm_substream *be_substream =
			snd_soc_dpcm_get_substream(be, stream);

		if (!snd_soc_dpcm_be_can_update(fe, be, stream))
			continue;

		/* only allow hw_free() if no connected FEs are running */
		if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream))
			continue;

		if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) &&
			(be->dpcm[stream].state
				!= SND_SOC_DPCM_STATE_HW_PARAMS) &&
			(be->dpcm[stream].state
				!= SND_SOC_DPCM_STATE_HW_FREE) &&
			(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
			continue;

		soc_pcm_hw_free(be_substream);
	}

	return ret;
}
#endif

int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream)
{
	struct snd_soc_dpcm *dpcm;
@@ -2365,6 +2442,37 @@ static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd)
	return ret;
}

#ifdef CONFIG_AUDIO_QGKI
int dpcm_fe_dai_prepare_be(struct snd_soc_pcm_runtime *fe,
		struct snd_soc_pcm_runtime *be, int stream)
{
	struct snd_pcm_substream *be_substream =
		snd_soc_dpcm_get_substream(be, stream);
	int ret = 0;

	/* is this op for this BE ? */
	if (!snd_soc_dpcm_be_can_update(fe, be, stream))
		return 0;

	if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) &&
			(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
		return 0;

	dev_dbg(be->dev, "ASoC: prepare BE %s\n",
			fe->dai_link->name);

	ret = soc_pcm_prepare(be_substream);
	if (ret < 0) {
		dev_err(be->dev, "ASoC: backend prepare failed %d\n",
				ret);
		return ret;
	}

	be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
	return ret;
}
#endif

static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct snd_soc_pcm_runtime *fe = substream->private_data;
@@ -2418,12 +2526,95 @@ int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream)
	return ret;
}

#ifdef CONFIG_AUDIO_QGKI
static void dpcm_be_async_prepare(void *data, async_cookie_t cookie)
{
	struct snd_soc_dpcm *dpcm = data;
	struct snd_soc_pcm_runtime *be = dpcm->be;
	int stream = dpcm->stream;
	struct snd_pcm_substream *be_substream =
		snd_soc_dpcm_get_substream(be, stream);
	int ret;

	dev_dbg(be->dev, "%s ASoC: prepare BE %s\n", __func__,
					dpcm->fe->dai_link->name);
	ret = soc_pcm_prepare(be_substream);
	if (ret < 0) {
		be->err_ops = ret;
		dev_err(be->dev, "ASoC: backend prepare failed %d\n",
				ret);
		return;
	}
	be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
}

void dpcm_be_dai_prepare_async(struct snd_soc_pcm_runtime *fe, int stream,
					    struct async_domain *domain)
{
	struct snd_soc_dpcm *dpcm;
	struct snd_soc_dpcm *dpcm_async[DPCM_MAX_BE_USERS];
	int i = 0, j;

	list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
		struct snd_soc_pcm_runtime *be = dpcm->be;

		be->err_ops = 0;
		/* is this op for this BE ? */
		if (!snd_soc_dpcm_be_can_update(fe, be, stream))
			continue;

		if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) &&
			(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
			continue;

		/* does this BE support async op ?*/
		if ((fe->dai_link->async_ops & ASYNC_DPCM_SND_SOC_PREPARE) &&
		    (be->dai_link->async_ops & ASYNC_DPCM_SND_SOC_PREPARE)) {
			dpcm->stream = stream;
			async_schedule_domain(dpcm_be_async_prepare,
							    dpcm, domain);
		} else {
			dpcm_async[i++] = dpcm;
		}
	}

	for (j = 0; j < i; j++) {
		struct snd_soc_dpcm *dpcm = dpcm_async[j];
		struct snd_soc_pcm_runtime *be = dpcm->be;
		struct snd_pcm_substream *be_substream =
			snd_soc_dpcm_get_substream(be, stream);
		int ret;

		dev_dbg(be->dev, "ASoC: prepare BE %s\n",
				dpcm->fe->dai_link->name);

		ret = soc_pcm_prepare(be_substream);
		if (ret < 0) {
			dev_err(be->dev, "ASoC: backend prepare failed %d\n",
					ret);
			be->err_ops = ret;
			return;
		}

		be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
	}
}
#endif

static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *fe = substream->private_data;
	int stream = substream->stream, ret = 0;
#ifdef CONFIG_AUDIO_QGKI
	struct snd_soc_dpcm *dpcm;

	ASYNC_DOMAIN_EXCLUSIVE(async_domain);
#endif

	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
#ifdef CONFIG_AUDIO_QGKI
	fe->err_ops = 0;
#endif

	dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name);

@@ -2436,19 +2627,62 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
		ret = -EINVAL;
		goto out;
	}

#ifdef CONFIG_AUDIO_QGKI
	if (!(fe->dai_link->async_ops & ASYNC_DPCM_SND_SOC_PREPARE)) {
		ret = dpcm_be_dai_prepare(fe, substream->stream);
		if (ret < 0)
			goto out;
		/* call prepare on the frontend */
		ret = soc_pcm_prepare(substream);
		if (ret < 0) {
			dev_err(fe->dev, "ASoC: prepare FE %s failed\n",
					fe->dai_link->name);
			goto out;
		}
	} else {
		dpcm_be_dai_prepare_async(fe, substream->stream,
							&async_domain);

		/* call prepare on the frontend */
		ret = soc_pcm_prepare(substream);
		if (ret < 0) {
			fe->err_ops = ret;
			dev_err(fe->dev, "ASoC: prepare FE %s failed\n",
					fe->dai_link->name);
		}

		async_synchronize_full_domain(&async_domain);

		/* check if any BE failed */
		list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients,
							    list_be) {
			struct snd_soc_pcm_runtime *be = dpcm->be;

			if (be->err_ops < 0) {
				ret = be->err_ops;
				goto out;
			}
		}

		/* check if FE failed */
		if (fe->err_ops < 0) {
			ret = fe->err_ops;
			goto out;
		}
	}
#else
	ret = dpcm_be_dai_prepare(fe, substream->stream);
	if (ret < 0)
		goto out;

	/* call prepare on the frontend */
	ret = soc_pcm_prepare(substream);
	if (ret < 0) {
		dev_err(fe->dev,"ASoC: prepare FE %s failed\n",
			fe->dai_link->name);
		goto out;
	}
#endif
	/* run the stream event for each BE */
	dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START);
	fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;