248 lines
8.7 KiB
Diff
248 lines
8.7 KiB
Diff
|
From 63ec1c904e479a7d5a0ecaf190ee1b447d760d6b Mon Sep 17 00:00:00 2001
|
||
|
From: Samuel Holland <samuel@sholland.org>
|
||
|
Date: Wed, 14 Oct 2020 01:19:34 -0500
|
||
|
Subject: [PATCH 307/351] ASoC: sun8i-codec: Automatically set the system
|
||
|
sample rate
|
||
|
|
||
|
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 | 103 +++++++++++++++++++++++++++-------
|
||
|
1 file changed, 84 insertions(+), 19 deletions(-)
|
||
|
|
||
|
diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c
|
||
|
index 38349d85fb17..468fa5f71bd3 100644
|
||
|
--- a/sound/soc/sunxi/sun8i-codec.c
|
||
|
+++ b/sound/soc/sunxi/sun8i-codec.c
|
||
|
@@ -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,
|
||
|
};
|
||
|
--
|
||
|
2.34.0
|
||
|
|