Merge remote-tracking branch 'asoc/topic/compress' into asoc-next
[firefly-linux-kernel-4.4.55.git] / sound / soc / soc-pcm.c
index 64bf3f827aac2f8bbe0f331b8657167d5051c78e..47e1ce771e65e0403e1ab743c677083c98452a8c 100644 (file)
@@ -84,35 +84,117 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream,
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        int ret;
 
-       if (!soc_dai->driver->symmetric_rates &&
-           !rtd->dai_link->symmetric_rates)
-               return 0;
+       if (soc_dai->rate && (soc_dai->driver->symmetric_rates ||
+                               rtd->dai_link->symmetric_rates)) {
+               dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n",
+                               soc_dai->rate);
+
+               ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+                                               SNDRV_PCM_HW_PARAM_RATE,
+                                               soc_dai->rate, soc_dai->rate);
+               if (ret < 0) {
+                       dev_err(soc_dai->dev,
+                               "ASoC: Unable to apply rate constraint: %d\n",
+                               ret);
+                       return ret;
+               }
+       }
 
-       /* This can happen if multiple streams are starting simultaneously -
-        * the second can need to get its constraints before the first has
-        * picked a rate.  Complain and allow the application to carry on.
-        */
-       if (!soc_dai->rate) {
-               dev_warn(soc_dai->dev,
-                        "ASoC: Not enforcing symmetric_rates due to race\n");
-               return 0;
+       if (soc_dai->channels && (soc_dai->driver->symmetric_channels ||
+                               rtd->dai_link->symmetric_channels)) {
+               dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d channel(s)\n",
+                               soc_dai->channels);
+
+               ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+                                               SNDRV_PCM_HW_PARAM_CHANNELS,
+                                               soc_dai->channels,
+                                               soc_dai->channels);
+               if (ret < 0) {
+                       dev_err(soc_dai->dev,
+                               "ASoC: Unable to apply channel symmetry constraint: %d\n",
+                               ret);
+                       return ret;
+               }
        }
 
-       dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n", soc_dai->rate);
+       if (soc_dai->sample_bits && (soc_dai->driver->symmetric_samplebits ||
+                               rtd->dai_link->symmetric_samplebits)) {
+               dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d sample bits\n",
+                               soc_dai->sample_bits);
 
-       ret = snd_pcm_hw_constraint_minmax(substream->runtime,
-                                          SNDRV_PCM_HW_PARAM_RATE,
-                                          soc_dai->rate, soc_dai->rate);
-       if (ret < 0) {
-               dev_err(soc_dai->dev,
-                       "ASoC: Unable to apply rate symmetry constraint: %d\n",
-                       ret);
-               return ret;
+               ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+                                               SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+                                               soc_dai->sample_bits,
+                                               soc_dai->sample_bits);
+               if (ret < 0) {
+                       dev_err(soc_dai->dev,
+                               "ASoC: Unable to apply sample bits symmetry constraint: %d\n",
+                               ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       unsigned int rate, channels, sample_bits, symmetry;
+
+       rate = params_rate(params);
+       channels = params_channels(params);
+       sample_bits = snd_pcm_format_physical_width(params_format(params));
+
+       /* reject unmatched parameters when applying symmetry */
+       symmetry = cpu_dai->driver->symmetric_rates ||
+               codec_dai->driver->symmetric_rates ||
+               rtd->dai_link->symmetric_rates;
+       if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) {
+               dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n",
+                               cpu_dai->rate, rate);
+               return -EINVAL;
+       }
+
+       symmetry = cpu_dai->driver->symmetric_channels ||
+               codec_dai->driver->symmetric_channels ||
+               rtd->dai_link->symmetric_channels;
+       if (symmetry && cpu_dai->channels && cpu_dai->channels != channels) {
+               dev_err(rtd->dev, "ASoC: unmatched channel symmetry: %d - %d\n",
+                               cpu_dai->channels, channels);
+               return -EINVAL;
+       }
+
+       symmetry = cpu_dai->driver->symmetric_samplebits ||
+               codec_dai->driver->symmetric_samplebits ||
+               rtd->dai_link->symmetric_samplebits;
+       if (symmetry && cpu_dai->sample_bits && cpu_dai->sample_bits != sample_bits) {
+               dev_err(rtd->dev, "ASoC: unmatched sample bits symmetry: %d - %d\n",
+                               cpu_dai->sample_bits, sample_bits);
+               return -EINVAL;
        }
 
        return 0;
 }
 
+static bool soc_pcm_has_symmetry(struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai_driver *cpu_driver = rtd->cpu_dai->driver;
+       struct snd_soc_dai_driver *codec_driver = rtd->codec_dai->driver;
+       struct snd_soc_dai_link *link = rtd->dai_link;
+
+       return cpu_driver->symmetric_rates || codec_driver->symmetric_rates ||
+               link->symmetric_rates || cpu_driver->symmetric_channels ||
+               codec_driver->symmetric_channels || link->symmetric_channels ||
+               cpu_driver->symmetric_samplebits ||
+               codec_driver->symmetric_samplebits ||
+               link->symmetric_samplebits;
+}
+
 /*
  * List of sample sizes that might go over the bus for parameter
  * application.  There ought to be a wildcard sample size for things
@@ -148,24 +230,32 @@ static void soc_pcm_apply_msb(struct snd_pcm_substream *substream,
        }
 }
 
-static void soc_pcm_init_runtime_hw(struct snd_pcm_hardware *hw,
+static void soc_pcm_init_runtime_hw(struct snd_pcm_runtime *runtime,
        struct snd_soc_pcm_stream *codec_stream,
        struct snd_soc_pcm_stream *cpu_stream)
 {
-       hw->rate_min = max(codec_stream->rate_min, cpu_stream->rate_min);
-       hw->rate_max = max(codec_stream->rate_max, cpu_stream->rate_max);
+       struct snd_pcm_hardware *hw = &runtime->hw;
+
        hw->channels_min = max(codec_stream->channels_min,
                cpu_stream->channels_min);
        hw->channels_max = min(codec_stream->channels_max,
                cpu_stream->channels_max);
-       hw->formats = codec_stream->formats & cpu_stream->formats;
-       hw->rates = codec_stream->rates & cpu_stream->rates;
-       if (codec_stream->rates
-               & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
-               hw->rates |= cpu_stream->rates;
-       if (cpu_stream->rates
-               & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
-               hw->rates |= codec_stream->rates;
+       if (hw->formats)
+               hw->formats &= codec_stream->formats & cpu_stream->formats;
+       else
+               hw->formats = codec_stream->formats & cpu_stream->formats;
+       hw->rates = snd_pcm_rate_mask_intersect(codec_stream->rates,
+               cpu_stream->rates);
+
+       hw->rate_min = 0;
+       hw->rate_max = UINT_MAX;
+
+       snd_pcm_limit_hw_rates(runtime);
+
+       hw->rate_min = max(hw->rate_min, cpu_stream->rate_min);
+       hw->rate_min = max(hw->rate_min, codec_stream->rate_min);
+       hw->rate_max = min_not_zero(hw->rate_max, cpu_stream->rate_max);
+       hw->rate_max = min_not_zero(hw->rate_max, codec_stream->rate_max);
 }
 
 /*
@@ -235,15 +325,17 @@ static int soc_pcm_open(struct snd_pcm_substream *substream)
 
        /* Check that the codec and cpu DAIs are compatible */
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-               soc_pcm_init_runtime_hw(&runtime->hw, &codec_dai_drv->playback,
+               soc_pcm_init_runtime_hw(runtime, &codec_dai_drv->playback,
                        &cpu_dai_drv->playback);
        } else {
-               soc_pcm_init_runtime_hw(&runtime->hw, &codec_dai_drv->capture,
+               soc_pcm_init_runtime_hw(runtime, &codec_dai_drv->capture,
                        &cpu_dai_drv->capture);
        }
 
+       if (soc_pcm_has_symmetry(substream))
+               runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+
        ret = -EINVAL;
-       snd_pcm_limit_hw_rates(runtime);
        if (!runtime->hw.rates) {
                printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n",
                        codec_dai->name, cpu_dai->name);
@@ -390,11 +482,6 @@ static int soc_pcm_close(struct snd_pcm_substream *substream)
        if (!codec_dai->active)
                codec_dai->rate = 0;
 
-       /* Muting the DAC suppresses artifacts caused during digital
-        * shutdown, for example from stopping clocks.
-        */
-       snd_soc_dai_digital_mute(codec_dai, 1, substream->stream);
-
        if (cpu_dai->driver->ops->shutdown)
                cpu_dai->driver->ops->shutdown(substream, cpu_dai);
 
@@ -525,6 +612,10 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
 
        mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
 
+       ret = soc_pcm_params_symmetry(substream, params);
+       if (ret)
+               goto out;
+
        if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
                ret = rtd->dai_link->ops->hw_params(substream, params);
                if (ret < 0) {
@@ -561,9 +652,16 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
                }
        }
 
-       /* store the rate for each DAIs */
+       /* store the parameters for each DAIs */
        cpu_dai->rate = params_rate(params);
+       cpu_dai->channels = params_channels(params);
+       cpu_dai->sample_bits =
+               snd_pcm_format_physical_width(params_format(params));
+
        codec_dai->rate = params_rate(params);
+       codec_dai->channels = params_channels(params);
+       codec_dai->sample_bits =
+               snd_pcm_format_physical_width(params_format(params));
 
 out:
        mutex_unlock(&rtd->pcm_mutex);
@@ -594,12 +692,26 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
        struct snd_soc_platform *platform = rtd->platform;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai *codec_dai = rtd->codec_dai;
-       struct snd_soc_codec *codec = rtd->codec;
+       bool playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
 
        mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
 
+       /* clear the corresponding DAIs parameters when going to be inactive */
+       if (cpu_dai->active == 1) {
+               cpu_dai->rate = 0;
+               cpu_dai->channels = 0;
+               cpu_dai->sample_bits = 0;
+       }
+
+       if (codec_dai->active == 1) {
+               codec_dai->rate = 0;
+               codec_dai->channels = 0;
+               codec_dai->sample_bits = 0;
+       }
+
        /* apply codec digital mute */
-       if (!codec->active)
+       if ((playback && codec_dai->playback_active == 1) ||
+           (!playback && codec_dai->capture_active == 1))
                snd_soc_dai_digital_mute(codec_dai, 1, substream->stream);
 
        /* free any machine hw params */
@@ -665,7 +777,7 @@ static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream,
                        return ret;
        }
 
-       if (platform->driver->ops && platform->driver->bespoke_trigger) {
+       if (platform->driver->bespoke_trigger) {
                ret = platform->driver->bespoke_trigger(substream, cmd);
                if (ret < 0)
                        return ret;
@@ -1119,6 +1231,20 @@ unwind:
        return err;
 }
 
+static void dpcm_init_runtime_hw(struct snd_pcm_runtime *runtime,
+       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 &= stream->formats;
+       else
+               runtime->hw.formats = stream->formats;
+       runtime->hw.rates = stream->rates;
+}
+
 static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
@@ -1126,21 +1252,10 @@ static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
 
-       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-               runtime->hw.rate_min = cpu_dai_drv->playback.rate_min;
-               runtime->hw.rate_max = cpu_dai_drv->playback.rate_max;
-               runtime->hw.channels_min = cpu_dai_drv->playback.channels_min;
-               runtime->hw.channels_max = cpu_dai_drv->playback.channels_max;
-               runtime->hw.formats &= cpu_dai_drv->playback.formats;
-               runtime->hw.rates = cpu_dai_drv->playback.rates;
-       } else {
-               runtime->hw.rate_min = cpu_dai_drv->capture.rate_min;
-               runtime->hw.rate_max = cpu_dai_drv->capture.rate_max;
-               runtime->hw.channels_min = cpu_dai_drv->capture.channels_min;
-               runtime->hw.channels_max = cpu_dai_drv->capture.channels_max;
-               runtime->hw.formats &= cpu_dai_drv->capture.formats;
-               runtime->hw.rates = cpu_dai_drv->capture.rates;
-       }
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback);
+       else
+               dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture);
 }
 
 static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
@@ -2021,10 +2136,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
        int ret = 0, playback = 0, capture = 0;
 
        if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
-               if (cpu_dai->driver->playback.channels_min)
-                       playback = 1;
-               if (cpu_dai->driver->capture.channels_min)
-                       capture = 1;
+               playback = rtd->dai_link->dpcm_playback;
+               capture = rtd->dai_link->dpcm_capture;
        } else {
                if (codec_dai->driver->playback.channels_min &&
                    cpu_dai->driver->playback.channels_min)