Merge branch 'asoc-4.18' into asoc-4.19 wmadsp dep
[sfrench/cifs-2.6.git] / sound / soc / soc-pcm.c
index 45b52f7b9690714c8c39ead8a844e1f98ac58e34..e8b98bfd4cf13bcb56d118a4c87a6e08ce5139e2 100644 (file)
@@ -1,20 +1,14 @@
-/*
- * soc-pcm.c  --  ALSA SoC PCM
- *
- * Copyright 2005 Wolfson Microelectronics PLC.
- * Copyright 2005 Openedhand Ltd.
- * Copyright (C) 2010 Slimlogic Ltd.
- * Copyright (C) 2010 Texas Instruments Inc.
- *
- * Authors: Liam Girdwood <lrg@ti.com>
- *          Mark Brown <broonie@opensource.wolfsonmicro.com>
- *
- *  This program is free software; you can redistribute  it and/or modify it
- *  under  the terms of  the GNU General  Public License as published by the
- *  Free Software Foundation;  either version 2 of the  License, or (at your
- *  option) any later version.
- *
- */
+// SPDX-License-Identifier: GPL-2.0+
+//
+// soc-pcm.c  --  ALSA SoC PCM
+//
+// Copyright 2005 Wolfson Microelectronics PLC.
+// Copyright 2005 Openedhand Ltd.
+// Copyright (C) 2010 Slimlogic Ltd.
+// Copyright (C) 2010 Texas Instruments Inc.
+//
+// Authors: Liam Girdwood <lrg@ti.com>
+//          Mark Brown <broonie@opensource.wolfsonmicro.com>
 
 #include <linux/kernel.h>
 #include <linux/init.h>
@@ -865,8 +859,20 @@ int soc_dai_hw_params(struct snd_pcm_substream *substream,
                      struct snd_pcm_hw_params *params,
                      struct snd_soc_dai *dai)
 {
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
        int ret;
 
+       /* perform any topology hw_params fixups before DAI  */
+       if (rtd->dai_link->be_hw_params_fixup) {
+               ret = rtd->dai_link->be_hw_params_fixup(rtd, params);
+               if (ret < 0) {
+                       dev_err(rtd->dev,
+                               "ASoC: hw_params topology fixup failed %d\n",
+                               ret);
+                       return ret;
+               }
+       }
+
        if (dai->driver->ops->hw_params) {
                ret = dai->driver->ops->hw_params(substream, params, dai);
                if (ret < 0) {
@@ -1173,6 +1179,9 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
        snd_pcm_sframes_t codec_delay = 0;
        int i;
 
+       /* clearing the previous total delay */
+       runtime->delay = 0;
+
        for_each_rtdcom(rtd, rtdcom) {
                component = rtdcom->component;
 
@@ -1184,6 +1193,8 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
                offset = component->driver->ops->pointer(substream);
                break;
        }
+       /* base delay if assigned in pointer callback */
+       delay = runtime->delay;
 
        if (cpu_dai->driver->ops->delay)
                delay += cpu_dai->driver->ops->delay(substream, cpu_dai);
@@ -1666,29 +1677,28 @@ unwind:
 }
 
 static void dpcm_init_runtime_hw(struct snd_pcm_runtime *runtime,
-                                struct snd_soc_pcm_stream *stream,
-                                u64 formats)
+                                struct snd_soc_pcm_stream *stream)
 {
        runtime->hw.rate_min = stream->rate_min;
        runtime->hw.rate_max = stream->rate_max;
        runtime->hw.channels_min = stream->channels_min;
        runtime->hw.channels_max = stream->channels_max;
        if (runtime->hw.formats)
-               runtime->hw.formats &= formats & stream->formats;
+               runtime->hw.formats &= stream->formats;
        else
-               runtime->hw.formats = formats & stream->formats;
+               runtime->hw.formats = stream->formats;
        runtime->hw.rates = stream->rates;
 }
 
-static u64 dpcm_runtime_base_format(struct snd_pcm_substream *substream)
+static void dpcm_runtime_merge_format(struct snd_pcm_substream *substream,
+                                     u64 *formats)
 {
        struct snd_soc_pcm_runtime *fe = substream->private_data;
        struct snd_soc_dpcm *dpcm;
-       u64 formats = ULLONG_MAX;
        int stream = substream->stream;
 
        if (!fe->dai_link->dpcm_merged_format)
-               return formats;
+               return;
 
        /*
         * It returns merged BE codec format
@@ -1702,17 +1712,132 @@ static u64 dpcm_runtime_base_format(struct snd_pcm_substream *substream)
                int i;
 
                for (i = 0; i < be->num_codecs; i++) {
+                       /*
+                        * Skip CODECs which don't support the current stream
+                        * type. See soc_pcm_init_runtime_hw() for more details
+                        */
+                       if (!snd_soc_dai_stream_valid(be->codec_dais[i],
+                                                     stream))
+                               continue;
+
                        codec_dai_drv = be->codec_dais[i]->driver;
                        if (stream == SNDRV_PCM_STREAM_PLAYBACK)
                                codec_stream = &codec_dai_drv->playback;
                        else
                                codec_stream = &codec_dai_drv->capture;
 
-                       formats &= codec_stream->formats;
+                       *formats &= codec_stream->formats;
                }
        }
+}
+
+static void dpcm_runtime_merge_chan(struct snd_pcm_substream *substream,
+                                   unsigned int *channels_min,
+                                   unsigned int *channels_max)
+{
+       struct snd_soc_pcm_runtime *fe = substream->private_data;
+       struct snd_soc_dpcm *dpcm;
+       int stream = substream->stream;
+
+       if (!fe->dai_link->dpcm_merged_chan)
+               return;
+
+       /*
+        * It returns merged BE codec channel;
+        * if FE want to use it (= dpcm_merged_chan)
+        */
+
+       list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
+               struct snd_soc_pcm_runtime *be = dpcm->be;
+               struct snd_soc_dai_driver *cpu_dai_drv =  be->cpu_dai->driver;
+               struct snd_soc_dai_driver *codec_dai_drv;
+               struct snd_soc_pcm_stream *codec_stream;
+               struct snd_soc_pcm_stream *cpu_stream;
+
+               if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       cpu_stream = &cpu_dai_drv->playback;
+               else
+                       cpu_stream = &cpu_dai_drv->capture;
+
+               *channels_min = max(*channels_min, cpu_stream->channels_min);
+               *channels_max = min(*channels_max, cpu_stream->channels_max);
+
+               /*
+                * chan min/max cannot be enforced if there are multiple CODEC
+                * DAIs connected to a single CPU DAI, use CPU DAI's directly
+                */
+               if (be->num_codecs == 1) {
+                       codec_dai_drv = be->codec_dais[0]->driver;
+
+                       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+                               codec_stream = &codec_dai_drv->playback;
+                       else
+                               codec_stream = &codec_dai_drv->capture;
 
-       return formats;
+                       *channels_min = max(*channels_min,
+                                           codec_stream->channels_min);
+                       *channels_max = min(*channels_max,
+                                           codec_stream->channels_max);
+               }
+       }
+}
+
+static void dpcm_runtime_merge_rate(struct snd_pcm_substream *substream,
+                                   unsigned int *rates,
+                                   unsigned int *rate_min,
+                                   unsigned int *rate_max)
+{
+       struct snd_soc_pcm_runtime *fe = substream->private_data;
+       struct snd_soc_dpcm *dpcm;
+       int stream = substream->stream;
+
+       if (!fe->dai_link->dpcm_merged_rate)
+               return;
+
+       /*
+        * It returns merged BE codec channel;
+        * if FE want to use it (= dpcm_merged_chan)
+        */
+
+       list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
+               struct snd_soc_pcm_runtime *be = dpcm->be;
+               struct snd_soc_dai_driver *cpu_dai_drv =  be->cpu_dai->driver;
+               struct snd_soc_dai_driver *codec_dai_drv;
+               struct snd_soc_pcm_stream *codec_stream;
+               struct snd_soc_pcm_stream *cpu_stream;
+               int i;
+
+               if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       cpu_stream = &cpu_dai_drv->playback;
+               else
+                       cpu_stream = &cpu_dai_drv->capture;
+
+               *rate_min = max(*rate_min, cpu_stream->rate_min);
+               *rate_max = min_not_zero(*rate_max, cpu_stream->rate_max);
+               *rates = snd_pcm_rate_mask_intersect(*rates, cpu_stream->rates);
+
+               for (i = 0; i < be->num_codecs; i++) {
+                       /*
+                        * Skip CODECs which don't support the current stream
+                        * type. See soc_pcm_init_runtime_hw() for more details
+                        */
+                       if (!snd_soc_dai_stream_valid(be->codec_dais[i],
+                                                     stream))
+                               continue;
+
+                       codec_dai_drv = be->codec_dais[i]->driver;
+                       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+                               codec_stream = &codec_dai_drv->playback;
+                       else
+                               codec_stream = &codec_dai_drv->capture;
+
+                       *rate_min = max(*rate_min, codec_stream->rate_min);
+                       *rate_max = min_not_zero(*rate_max,
+                                                codec_stream->rate_max);
+                       *rates = snd_pcm_rate_mask_intersect(*rates,
+                                               codec_stream->rates);
+               }
+       }
 }
 
 static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
@@ -1721,12 +1846,17 @@ static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
-       u64 format = dpcm_runtime_base_format(substream);
 
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-               dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback, format);
+               dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback);
        else
-               dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture, format);
+               dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture);
+
+       dpcm_runtime_merge_format(substream, &runtime->hw.formats);
+       dpcm_runtime_merge_chan(substream, &runtime->hw.channels_min,
+                               &runtime->hw.channels_max);
+       dpcm_runtime_merge_rate(substream, &runtime->hw.rates,
+                               &runtime->hw.rate_min, &runtime->hw.rate_max);
 }
 
 static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd);
@@ -2551,106 +2681,113 @@ static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream)
        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_card *card)
+static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, int new)
 {
-       struct snd_soc_pcm_runtime *fe;
-       int old, new, paths;
+       struct snd_soc_dapm_widget_list *list;
+       int count, paths;
 
-       mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
-       list_for_each_entry(fe, &card->rtd_list, list) {
-               struct snd_soc_dapm_widget_list *list;
+       if (!fe->dai_link->dynamic)
+               return 0;
 
-               /* make sure link is FE */
-               if (!fe->dai_link->dynamic)
-                       continue;
+       /* only check active links */
+       if (!fe->cpu_dai->active)
+               return 0;
 
-               /* only check active links */
-               if (!fe->cpu_dai->active)
-                       continue;
+       /* DAPM sync will call this to update DSP paths */
+       dev_dbg(fe->dev, "ASoC: DPCM %s runtime update for FE %s\n",
+               new ? "new" : "old", fe->dai_link->name);
 
-               /* DAPM sync will call this to update DSP paths */
-               dev_dbg(fe->dev, "ASoC: 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 ||
+           !fe->codec_dai->driver->playback.channels_min)
+               goto capture;
 
-               /* skip if FE doesn't have playback capability */
-               if (!fe->cpu_dai->driver->playback.channels_min
-                   || !fe->codec_dai->driver->playback.channels_min)
-                       goto capture;
-
-               /* skip if FE isn't currently playing */
-               if (!fe->cpu_dai->playback_active
-                   || !fe->codec_dai->playback_active)
-                       goto capture;
-
-               paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list);
-               if (paths < 0) {
-                       dev_warn(fe->dev, "ASoC: %s no valid %s path\n",
-                                       fe->dai_link->name,  "playback");
-                       mutex_unlock(&card->mutex);
-                       return paths;
-               }
+       /* skip if FE isn't currently playing */
+       if (!fe->cpu_dai->playback_active || !fe->codec_dai->playback_active)
+               goto capture;
 
-               /* 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);
-               }
+       paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list);
+       if (paths < 0) {
+               dev_warn(fe->dev, "ASoC: %s no valid %s path\n",
+                        fe->dai_link->name,  "playback");
+               return paths;
+       }
 
-               /* update any old playback paths */
-               old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 0);
-               if (old) {
+       /* update any playback paths */
+       count = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, new);
+       if (count) {
+               if (new)
+                       dpcm_run_new_update(fe, SNDRV_PCM_STREAM_PLAYBACK);
+               else
                        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);
-               }
 
-               dpcm_path_put(&list);
+               dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK);
+               dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK);
+       }
+
+       dpcm_path_put(&list);
+
 capture:
-               /* skip if FE doesn't have capture capability */
-               if (!fe->cpu_dai->driver->capture.channels_min
-                   || !fe->codec_dai->driver->capture.channels_min)
-                       continue;
+       /* skip if FE doesn't have capture capability */
+       if (!fe->cpu_dai->driver->capture.channels_min ||
+           !fe->codec_dai->driver->capture.channels_min)
+               return 0;
 
-               /* skip if FE isn't currently capturing */
-               if (!fe->cpu_dai->capture_active
-                   || !fe->codec_dai->capture_active)
-                       continue;
+       /* skip if FE isn't currently capturing */
+       if (!fe->cpu_dai->capture_active || !fe->codec_dai->capture_active)
+               return 0;
 
-               paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list);
-               if (paths < 0) {
-                       dev_warn(fe->dev, "ASoC: %s no valid %s path\n",
-                                       fe->dai_link->name,  "capture");
-                       mutex_unlock(&card->mutex);
-                       return paths;
-               }
+       paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list);
+       if (paths < 0) {
+               dev_warn(fe->dev, "ASoC: %s no valid %s path\n",
+                        fe->dai_link->name,  "capture");
+               return paths;
+       }
 
-               /* update any new capture paths */
-               new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 1);
-               if (new) {
+       /* update any old capture paths */
+       count = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, new);
+       if (count) {
+               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) {
+               else
                        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);
+               dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE);
+               dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE);
        }
 
-       mutex_unlock(&card->mutex);
+       dpcm_path_put(&list);
+
        return 0;
 }
+
+/* Called by DAPM mixer/mux changes to update audio routing between PCMs and
+ * any DAI links.
+ */
+int soc_dpcm_runtime_update(struct snd_soc_card *card)
+{
+       struct snd_soc_pcm_runtime *fe;
+       int ret = 0;
+
+       mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
+       /* shutdown all old paths first */
+       list_for_each_entry(fe, &card->rtd_list, list) {
+               ret = soc_dpcm_fe_runtime_update(fe, 0);
+               if (ret)
+                       goto out;
+       }
+
+       /* bring new paths up */
+       list_for_each_entry(fe, &card->rtd_list, list) {
+               ret = soc_dpcm_fe_runtime_update(fe, 1);
+               if (ret)
+                       goto out;
+       }
+
+out:
+       mutex_unlock(&card->mutex);
+       return ret;
+}
 int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute)
 {
        struct snd_soc_dpcm *dpcm;