diff options
author | Liam Girdwood <lrg@ti.com> | 2012-04-25 12:12:51 +0100 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-04-26 17:48:19 +0100 |
commit | 618dae11f809aaccd05710aa8cee8c46a9cef1a7 (patch) | |
tree | 99c882f9657b492bc2b721e36e82831efb1b66ca /sound/soc/soc-pcm.c | |
parent | f86dcef87b771935c223334324a93c1d71f7a84c (diff) | |
download | linux-618dae11f809aaccd05710aa8cee8c46a9cef1a7.tar.bz2 |
ASoC: dpcm: Add runtime dynamic route update
This patch allows DPCM to dynamically alter the FE to BE PCM links
at runtime based on mixer setting updates. DAPM is looked up after
every mixer update and we perform a DPCM runtime update if the
mixer has a change of value.
This patchs adds/changes the following :-
o Adds DPCM runtime update core.
o Changes soc_dapm_mixer_update_power() and soc_dapm_mux_update_power()
to return if a change has occured rather than 0. No other users check
atm.
Signed-off-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/soc-pcm.c')
-rw-r--r-- | sound/soc/soc-pcm.c | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index a1d4426a99d9..ca36fd6746fc 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1608,7 +1608,228 @@ out: return ret; } +static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +{ + int err; + + dev_dbg(fe->dev, "runtime %s close on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + err = dpcm_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); + if (err < 0) + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", err); + + err = dpcm_be_dai_hw_free(fe, stream); + if (err < 0) + dev_err(fe->dev,"dpcm: hw_free FE failed %d\n", err); + + err = dpcm_be_dai_shutdown(fe, stream); + if (err < 0) + dev_err(fe->dev,"dpcm: shutdown FE failed %d\n", err); + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); + + return 0; +} + +static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + int ret; + + dev_dbg(fe->dev, "runtime %s open on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + /* Only start the BE if the FE is ready */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_FREE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_CLOSE) + return -EINVAL; + + /* startup must always be called for new BEs */ + ret = dpcm_be_dai_startup(fe, stream); + if (ret < 0) { + goto disconnect; + return ret; + } + + /* keep going if FE state is > open */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_OPEN) + return 0; + ret = dpcm_be_dai_hw_params(fe, stream); + if (ret < 0) { + goto close; + return ret; + } + + /* keep going if FE state is > hw_params */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_PARAMS) + return 0; + + + ret = dpcm_be_dai_prepare(fe, stream); + if (ret < 0) { + goto hw_free; + return ret; + } + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); + + /* keep going if FE state is > prepare */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_PREPARE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_STOP) + return 0; + + dev_dbg(fe->dev, "dpcm: trigger FE %s cmd start\n", + fe->dai_link->name); + + ret = dpcm_be_dai_trigger(fe, stream, + SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto hw_free; + } + + return 0; + +hw_free: + dpcm_be_dai_hw_free(fe, stream); +close: + dpcm_be_dai_shutdown(fe, stream); +disconnect: + /* disconnect any non started BEs */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + } + + return ret; +} + +static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + int ret; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + ret = dpcm_run_update_startup(fe, stream); + if (ret < 0) + dev_err(fe->dev, "failed to startup some BEs\n"); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + return ret; +} + +static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + int ret; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + ret = dpcm_run_update_shutdown(fe, stream); + if (ret < 0) + dev_err(fe->dev, "failed to shutdown some BEs\n"); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + return ret; +} + +/* Called by DAPM mixer/mux changes to update audio routing between PCMs and + * any DAI links. + */ +int soc_dpcm_runtime_update(struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_card *card; + int i, old, new, paths; + + if (widget->codec) + card = widget->codec->card; + else if (widget->platform) + card = widget->platform->card; + else + return -EINVAL; + + mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dapm_widget_list *list; + struct snd_soc_pcm_runtime *fe = &card->rtd[i]; + + /* make sure link is FE */ + if (!fe->dai_link->dynamic) + continue; + + /* only check active links */ + if (!fe->cpu_dai->active) + continue; + + /* DAPM sync will call this to update DSP paths */ + dev_dbg(fe->dev, "DPCM runtime update for FE %s\n", + fe->dai_link->name); + + /* skip if FE doesn't have playback capability */ + if (!fe->cpu_dai->driver->playback.channels_min) + goto capture; + + paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list); + if (paths < 0) { + dev_warn(fe->dev, "%s no valid %s path\n", + fe->dai_link->name, "playback"); + mutex_unlock(&card->mutex); + return paths; + } + + /* update any new playback paths */ + new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 1); + if (new) { + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); + } + + /* update any old playback paths */ + old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 0); + if (old) { + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); + } + +capture: + /* skip if FE doesn't have capture capability */ + if (!fe->cpu_dai->driver->capture.channels_min) + continue; + + paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list); + if (paths < 0) { + dev_warn(fe->dev, "%s no valid %s path\n", + fe->dai_link->name, "capture"); + mutex_unlock(&card->mutex); + return paths; + } + + /* update any new capture paths */ + new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 1); + if (new) { + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); + } + + /* update any old capture paths */ + old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 0); + if (old) { + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); + } + + dpcm_path_put(&list); + } + + mutex_unlock(&card->mutex); + return 0; +} int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) { struct snd_soc_dpcm *dpcm; |