From 7e80535774663ed75a57c99320ab52909fbc88f5 Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Thu, 7 Sep 2017 15:49:13 +0800 Subject: [PATCH] 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 Signed-off-by: Fred Oh Signed-off-by: Banajit Goswami Signed-off-by: Meng Wang --- include/sound/compress_driver.h | 3 + include/sound/soc-dpcm.h | 15 ++ include/sound/soc.h | 23 +++- sound/soc/soc-compress.c | 157 +++++++++++++++++++++ sound/soc/soc-pcm.c | 236 +++++++++++++++++++++++++++++++- 5 files changed, 432 insertions(+), 2 deletions(-) diff --git a/include/sound/compress_driver.h b/include/sound/compress_driver.h index 631e8fd60a3f..e9a34ca60703 100644 --- a/include/sound/compress_driver.h +++ b/include/sound/compress_driver.h @@ -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 }; /** diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index e55aeb00ce2d..f37f84466361 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -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); diff --git a/include/sound/soc.h b/include/sound/soc.h index 8921ec5d0c5a..23aedb1eb187 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -19,6 +19,9 @@ #include #include #include +#ifdef CONFIG_AUDIO_QGKI +#include +#endif #include #include #include @@ -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; diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index c5376a82a2e9..f82a41aea16f 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -450,6 +450,39 @@ out: 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; diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 42f72c9b78ba..b18fc68e11ac 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -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 @@ out: 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,7 +2627,50 @@ 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; @@ -2448,7 +2682,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) 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;