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

Commit acee621b authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "ASoC: Cold start latency reduction"

parents b3261299 11323a08
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ struct snd_compr_stream {
	bool metadata_set;
	bool next_track;
	void *private_data;
	struct snd_soc_pcm_runtime *be;
};

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

struct snd_soc_pcm_runtime;

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

/*
@@ -145,8 +147,13 @@ 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);
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);
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);
int dpcm_fe_dai_prepare_be(struct snd_soc_pcm_runtime *fe,
	struct snd_soc_pcm_runtime *be, int stream);
int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir,
	int event);

+14 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include <linux/kernel.h>
#include <linux/regmap.h>
#include <linux/log2.h>
#include <linux/async.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/compress_driver.h>
@@ -904,6 +905,14 @@ struct snd_soc_dai_link_component {
	const char *dai_name;
};

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,
};

struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
@@ -1008,6 +1017,9 @@ struct snd_soc_dai_link {

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

	/* this value determines what all ops can be started asynchronously */
	enum snd_soc_async_ops async_ops;
};

struct snd_soc_codec_conf {
@@ -1164,6 +1176,8 @@ struct snd_soc_pcm_runtime {

	long pmdown_time;

	/* err in case of ops failed */
	int err_ops;
	/* runtime devices */
	struct snd_pcm *pcm;
	struct snd_compr *compr;
+154 −14
Original line number Diff line number Diff line
@@ -465,6 +465,37 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd)
	return ret;
}

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);
}

static int soc_compr_set_params(struct snd_compr_stream *cstream,
					struct snd_compr_params *params)
{
@@ -538,7 +569,11 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
	struct snd_soc_component *component;
	struct snd_soc_rtdcom_list *rtdcom;
	struct snd_soc_dai *cpu_dai = fe->cpu_dai;
	int ret = 0, __ret, stream;
	struct snd_soc_pcm_runtime *be_list[DPCM_MAX_BE_USERS];
	struct snd_soc_dpcm *dpcm;
	int ret = 0, __ret, stream, i, j = 0;

	ASYNC_DOMAIN_EXCLUSIVE(async_domain);

	if (cstream->direction == SND_COMPRESS_PLAYBACK)
		stream = SNDRV_PCM_STREAM_PLAYBACK;
@@ -571,6 +606,14 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
			goto out;
	}

	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
		 */
		for_each_rtdcom(fe, rtdcom) {
			component = rtdcom->component;

@@ -578,18 +621,115 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
			    !component->driver->compr_ops->set_params)
				continue;

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

	if (fe->dai_link->compr_ops && fe->dai_link->compr_ops->set_params) {
		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;
	}

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

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;
}

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

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;
}

static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct snd_soc_pcm_runtime *fe = substream->private_data;
@@ -2569,13 +2673,90 @@ int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream)
	return ret;
}

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;
	}
}

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

	mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);

	fe->err_ops = 0;

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

	dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
@@ -2588,17 +2769,48 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
		goto out;
	}

	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;
		}
	}

	/* run the stream event for each BE */
	dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START);