ASoC: sun8i-codec: Automatically set the system sample rate
authorSamuel Holland <samuel@sholland.org>
Wed, 14 Oct 2020 06:19:34 +0000 (01:19 -0500)
committerMark Brown <broonie@kernel.org>
Mon, 26 Oct 2020 14:56:59 +0000 (14:56 +0000)
The sun8i codec has three clock/sample rate domains:
 - The AIF1 domain, with a sample rate equal to AIF1 LRCK
 - The AIF2 domain, with a sample rate equal to AIF2 LRCK
 - The SYSCLK domain, containing the ADC, DAC, and effects (AGC/DRC),
   with a sample rate given by a divisor from SYSCLK. The divisor is
   controlled by the AIF1_FS or AIF2_FS field in SYS_SR_CTRL, depending
   on if SYSCLK's source is AIF1CLK or AIF2CLK, respectively. The exact
   sample rate depends on if SYSCLK is running at 22.6 MHz or 24.6 MHz.

When an AIF (currently only AIF1) is active, the ADC and DAC should run
at that sample rate to avoid artifacting. Sample rate conversion is only
available when multiple AIFs are active and are routed to each other;
this means the sample rate conversion hardware usually cannot be used.

Only attach the event hook to the channel 0 AIF widgets, since we only
need one event when a DAI stream starts or stops. Channel 0 is always
brought up with a DAI stream, regardless of the number of channels in
the stream.

The ADC and DAC (along with their effects blocks) can be used even if
no AIFs are in use. In that case, we should select an appropriate sample
rate divisor, instead of keeping the last-used AIF sample rate.
44.1/48 kHz was chosen to balance audio quality and power consumption.

Since the sample rate is tied to active AIF paths, disabling pmdown_time
allows switching to the optimal sample rate immediately, instead of
after a 5 second delay.

Signed-off-by: Samuel Holland <samuel@sholland.org>
Acked-by: Maxime Ripard <mripard@kernel.org>
Link: https://lore.kernel.org/r/20201014061941.4306-11-samuel@sholland.org
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/sunxi/sun8i-codec.c

index 38349d8..468fa5f 100644 (file)
@@ -94,6 +94,8 @@
 #define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK  GENMASK(5, 4)
 #define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT_MASK  GENMASK(3, 2)
 
+#define SUN8I_CODEC_PASSTHROUGH_SAMPLE_RATE 48000
+
 #define SUN8I_CODEC_PCM_RATES  (SNDRV_PCM_RATE_8000_48000|\
                                 SNDRV_PCM_RATE_88200     |\
                                 SNDRV_PCM_RATE_96000     |\
@@ -107,8 +109,11 @@ enum {
 };
 
 struct sun8i_codec_aif {
+       unsigned int    sample_rate;
        unsigned int    slots;
        unsigned int    slot_width;
+       unsigned int    active_streams  : 2;
+       unsigned int    open_streams    : 2;
 };
 
 struct sun8i_codec_quirks {
@@ -149,11 +154,9 @@ static int sun8i_codec_runtime_suspend(struct device *dev)
        return 0;
 }
 
-static int sun8i_codec_get_hw_rate(struct snd_pcm_hw_params *params)
+static int sun8i_codec_get_hw_rate(unsigned int sample_rate)
 {
-       unsigned int rate = params_rate(params);
-
-       switch (rate) {
+       switch (sample_rate) {
        case 7350:
        case 8000:
                return 0x0;
@@ -186,6 +189,33 @@ static int sun8i_codec_get_hw_rate(struct snd_pcm_hw_params *params)
        }
 }
 
+static int sun8i_codec_update_sample_rate(struct sun8i_codec *scodec)
+{
+       unsigned int max_rate = 0;
+       int hw_rate, i;
+
+       for (i = SUN8I_CODEC_AIF1; i < SUN8I_CODEC_NAIFS; ++i) {
+               struct sun8i_codec_aif *aif = &scodec->aifs[i];
+
+               if (aif->active_streams)
+                       max_rate = max(max_rate, aif->sample_rate);
+       }
+
+       /* Set the sample rate for ADC->DAC passthrough when no AIF is active. */
+       if (!max_rate)
+               max_rate = SUN8I_CODEC_PASSTHROUGH_SAMPLE_RATE;
+
+       hw_rate = sun8i_codec_get_hw_rate(max_rate);
+       if (hw_rate < 0)
+               return hw_rate;
+
+       regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL,
+                          SUN8I_SYS_SR_CTRL_AIF1_FS_MASK,
+                          hw_rate << SUN8I_SYS_SR_CTRL_AIF1_FS);
+
+       return 0;
+}
+
 static int sun8i_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 {
        struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai);
@@ -355,9 +385,10 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
 {
        struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai);
        struct sun8i_codec_aif *aif = &scodec->aifs[dai->id];
+       unsigned int sample_rate = params_rate(params);
        unsigned int slots = aif->slots ?: params_channels(params);
        unsigned int slot_width = aif->slot_width ?: params_width(params);
-       int lrck_div_order, sample_rate, word_size;
+       int lrck_div_order, word_size;
        u8 bclk_div;
 
        /* word size */
@@ -392,19 +423,30 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
                           (lrck_div_order - 4) << SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV);
 
        /* BCLK divider (SYSCLK/BCLK ratio) */
-       bclk_div = sun8i_codec_get_bclk_div(scodec, lrck_div_order, params_rate(params));
+       bclk_div = sun8i_codec_get_bclk_div(scodec, lrck_div_order, sample_rate);
        regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
                           SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV_MASK,
                           bclk_div << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV);
 
-       sample_rate = sun8i_codec_get_hw_rate(params);
-       if (sample_rate < 0)
-               return sample_rate;
+       aif->sample_rate = sample_rate;
+       aif->open_streams |= BIT(substream->stream);
 
-       regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL,
-                          SUN8I_SYS_SR_CTRL_AIF1_FS_MASK,
-                          sample_rate << SUN8I_SYS_SR_CTRL_AIF1_FS);
+       return sun8i_codec_update_sample_rate(scodec);
+}
+
+static int sun8i_codec_hw_free(struct snd_pcm_substream *substream,
+                              struct snd_soc_dai *dai)
+{
+       struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai);
+       struct sun8i_codec_aif *aif = &scodec->aifs[dai->id];
+
+       if (aif->open_streams != BIT(substream->stream))
+               goto done;
 
+       aif->sample_rate = 0;
+
+done:
+       aif->open_streams &= ~BIT(substream->stream);
        return 0;
 }
 
@@ -412,6 +454,7 @@ static const struct snd_soc_dai_ops sun8i_codec_dai_ops = {
        .set_fmt        = sun8i_codec_set_fmt,
        .set_tdm_slot   = sun8i_codec_set_tdm_slot,
        .hw_params      = sun8i_codec_hw_params,
+       .hw_free        = sun8i_codec_hw_free,
 };
 
 static struct snd_soc_dai_driver sun8i_codec_dais[] = {
@@ -442,6 +485,22 @@ static struct snd_soc_dai_driver sun8i_codec_dais[] = {
        },
 };
 
+static int sun8i_codec_aif_event(struct snd_soc_dapm_widget *w,
+                                struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+       struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);
+       struct sun8i_codec_aif *aif = &scodec->aifs[w->sname[3] - '1'];
+       int stream = w->id == snd_soc_dapm_aif_out;
+
+       if (SND_SOC_DAPM_EVENT_ON(event))
+               aif->active_streams |= BIT(stream);
+       else
+               aif->active_streams &= ~BIT(stream);
+
+       return sun8i_codec_update_sample_rate(scodec);
+}
+
 static const char *const sun8i_aif_stereo_mux_enum_values[] = {
        "Stereo", "Reverse Stereo", "Sum Mono", "Mix Mono"
 };
@@ -544,9 +603,11 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = {
                            SUN8I_DAC_DIG_CTRL_ENDA, 0, NULL, 0),
 
        /* AIF "ADC" Outputs */
-       SND_SOC_DAPM_AIF_OUT("AIF1 AD0L", "AIF1 Capture", 0,
-                            SUN8I_AIF1_ADCDAT_CTRL,
-                            SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0L_ENA, 0),
+       SND_SOC_DAPM_AIF_OUT_E("AIF1 AD0L", "AIF1 Capture", 0,
+                              SUN8I_AIF1_ADCDAT_CTRL,
+                              SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0L_ENA, 0,
+                              sun8i_codec_aif_event,
+                              SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
        SND_SOC_DAPM_AIF_OUT("AIF1 AD0R", "AIF1 Capture", 1,
                             SUN8I_AIF1_ADCDAT_CTRL,
                             SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0R_ENA, 0),
@@ -570,9 +631,11 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = {
                         &sun8i_aif1_da0_stereo_mux_control),
 
        /* AIF "DAC" Inputs */
-       SND_SOC_DAPM_AIF_IN("AIF1 DA0L", "AIF1 Playback", 0,
-                           SUN8I_AIF1_DACDAT_CTRL,
-                           SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA, 0),
+       SND_SOC_DAPM_AIF_IN_E("AIF1 DA0L", "AIF1 Playback", 0,
+                             SUN8I_AIF1_DACDAT_CTRL,
+                             SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA, 0,
+                             sun8i_codec_aif_event,
+                             SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
        SND_SOC_DAPM_AIF_IN("AIF1 DA0R", "AIF1 Playback", 1,
                            SUN8I_AIF1_DACDAT_CTRL,
                            SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0),
@@ -727,6 +790,9 @@ static int sun8i_codec_component_probe(struct snd_soc_component *component)
                           BIT(SUN8I_SYSCLK_CTL_SYSCLK_SRC),
                           SUN8I_SYSCLK_CTL_SYSCLK_SRC_AIF1CLK);
 
+       /* Program the default sample rate. */
+       sun8i_codec_update_sample_rate(scodec);
+
        return 0;
 }
 
@@ -737,7 +803,6 @@ static const struct snd_soc_component_driver sun8i_soc_component = {
        .num_dapm_routes        = ARRAY_SIZE(sun8i_codec_dapm_routes),
        .probe                  = sun8i_codec_component_probe,
        .idle_bias_on           = 1,
-       .use_pmdown_time        = 1,
        .endianness             = 1,
        .non_legacy_dai_naming  = 1,
 };