ASoC: dpcm: Add runtime dynamic route update
authorLiam Girdwood <lrg@ti.com>
Wed, 25 Apr 2012 11:12:51 +0000 (12:12 +0100)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Thu, 26 Apr 2012 16:48:19 +0000 (17:48 +0100)
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>
include/sound/soc-dpcm.h
sound/soc/soc-dapm.c
sound/soc/soc-pcm.c

index e307fa30231add22c8c49e03693e4f48f02e9906..d1a4b50018d24e94409ea24bc4af3eeeb810b66e 100644 (file)
@@ -132,5 +132,6 @@ void snd_soc_dpcm_be_set_state(struct snd_soc_pcm_runtime *be, int stream,
 /* internal use only */
 int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute);
 int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd);
+int soc_dpcm_runtime_update(struct snd_soc_dapm_widget *);
 
 #endif
index 214323f539561cb0f09b779ec5733ee16246b0c4..02db2c09076b84eab8ea0b09e3bad30646eefc4f 100644 (file)
@@ -1846,7 +1846,7 @@ static int soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
                dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
        }
 
-       return 0;
+       return found;
 }
 
 int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
@@ -1858,6 +1858,8 @@ int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
        mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
        ret = soc_dapm_mux_update_power(widget, kcontrol, mux, e);
        mutex_unlock(&card->dapm_mutex);
+       if (ret > 0)
+               soc_dpcm_runtime_update(widget);
        return ret;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_mux_update_power);
@@ -1890,7 +1892,7 @@ static int soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
                dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
        }
 
-       return 0;
+       return found;
 }
 
 int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
@@ -1902,6 +1904,8 @@ int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
        mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
        ret = soc_dapm_mixer_update_power(widget, kcontrol, connect);
        mutex_unlock(&card->dapm_mutex);
+       if (ret > 0)
+               soc_dpcm_runtime_update(widget);
        return ret;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power);
index a1d4426a99d90575c74e2d4cf91c748575699e90..ca36fd6746fc6493f6562eeb31c9246ea9736bf4 100644 (file)
@@ -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;