summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWalter Yang <yandongy@codeaurora.org>2015-03-05 14:14:42 +0800
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 20:11:09 -0700
commit110ad9d27a96477f5fb9940a3b17e89a6d3b1336 (patch)
treeb7167c867b849536513486e1d99c9da0995dad5f
parente9c2d518bee63b4cca6c31b0d38faab4fcf9265f (diff)
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: Walter Yang <yandongy@codeaurora.org> Signed-off-by: Fred Oh <fred@codeaurora.org> Conflicts: include/sound/soc.h sound/soc/soc-compress.c sound/soc/soc-pcm.c
-rw-r--r--include/sound/compress_driver.h1
-rw-r--r--include/sound/soc-dpcm.h7
-rw-r--r--include/sound/soc.h14
-rw-r--r--sound/soc/soc-compress.c154
-rw-r--r--sound/soc/soc-pcm.c230
5 files changed, 373 insertions, 33 deletions
diff --git a/include/sound/compress_driver.h b/include/sound/compress_driver.h
index 5311d98ccee1..99bc1058cdd7 100644
--- a/include/sound/compress_driver.h
+++ b/include/sound/compress_driver.h
@@ -82,6 +82,7 @@ struct snd_compr_stream {
bool metadata_set;
bool next_track;
void *private_data;
+ struct snd_soc_pcm_runtime *be;
};
/**
diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h
index 806059052bfc..2ed3a25233c1 100644
--- a/include/sound/soc-dpcm.h
+++ b/include/sound/soc-dpcm.h
@@ -17,6 +17,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.
@@ -86,6 +87,7 @@ struct snd_soc_dpcm {
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_state;
#endif
+ int stream;
};
/*
@@ -148,8 +150,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);
diff --git a/include/sound/soc.h b/include/sound/soc.h
index c51647e34221..8f57adfae53d 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -22,6 +22,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>
@@ -975,6 +976,14 @@ struct snd_soc_platform {
struct snd_soc_component component;
};
+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 */
@@ -1066,6 +1075,9 @@ struct snd_soc_dai_link {
/* pmdown_time is ignored at stop */
unsigned int ignore_pmdown_time:1;
+
+ /* this value determines what all ops can be started asynchronously */
+ enum snd_soc_async_ops async_ops;
};
struct snd_soc_codec_conf {
@@ -1214,6 +1226,8 @@ struct snd_soc_pcm_runtime {
long pmdown_time;
unsigned char pop_wait:1;
+ /* err in case of ops failed */
+ int err_ops;
/* runtime devices */
struct snd_pcm *pcm;
struct snd_compr *compr;
diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c
index 4180aa36c788..c64f230db4e1 100644
--- a/sound/soc/soc-compress.c
+++ b/sound/soc/soc-compress.c
@@ -408,13 +408,48 @@ err:
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_fe(struct snd_compr_stream *cstream,
struct snd_compr_params *params)
{
struct snd_soc_pcm_runtime *fe = cstream->private_data;
struct snd_pcm_substream *fe_substream = fe->pcm->streams[0].substream;
struct snd_soc_platform *platform = fe->platform;
- int ret = 0, stream;
+ struct snd_soc_pcm_runtime *be_list[DPCM_MAX_BE_USERS];
+ struct snd_soc_dpcm *dpcm;
+ int ret = 0, stream, i, j = 0;
+
+ ASYNC_DOMAIN_EXCLUSIVE(async_domain);
if (cstream->direction == SND_COMPRESS_PLAYBACK)
stream = SNDRV_PCM_STREAM_PLAYBACK;
@@ -423,36 +458,107 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
- if (platform->driver->compr_ops && platform->driver->compr_ops->set_params) {
- ret = platform->driver->compr_ops->set_params(cstream, params);
+ 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
+ */
+ if (platform->driver->compr_ops &&
+ platform->driver->compr_ops->set_params) {
+ ret = platform->driver->compr_ops->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;
- }
- if (fe->dai_link->compr_ops && fe->dai_link->compr_ops->set_params) {
- ret = fe->dai_link->compr_ops->set_params(cstream);
+ ret = dpcm_be_dai_prepare(fe, stream);
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;
+ } 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);
+ }
+ /* 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
+ */
+ if (platform->driver->compr_ops &&
+ platform->driver->compr_ops->set_params) {
+ ret = platform->driver->compr_ops->set_params(cstream,
+ params);
+ if (ret < 0)
+ goto exit;
+ }
- ret = dpcm_be_dai_prepare(fe, stream);
- if (ret < 0)
- goto out;
+ 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;
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index 2373c065fa0c..6bc2c887de4a 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -1878,6 +1878,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;
@@ -2178,6 +2253,35 @@ out:
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;
@@ -2229,13 +2333,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);
@@ -2248,16 +2429,47 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
goto out;
}
- ret = dpcm_be_dai_prepare(fe, substream->stream);
- if (ret < 0)
- 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) {
- dev_err(fe->dev,"ASoC: prepare FE %s failed\n",
- fe->dai_link->name);
- goto out;
+ /* 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 */