summaryrefslogtreecommitdiff
path: root/sound/soc/soc-pcm.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/soc-pcm.c')
-rw-r--r--sound/soc/soc-pcm.c230
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 */