diff options
Diffstat (limited to 'sound/soc/soc-pcm.c')
-rw-r--r-- | sound/soc/soc-pcm.c | 230 |
1 files changed, 221 insertions, 9 deletions
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 */ |