From: Srinivasa Rao Mandadapu <srivasam@xxxxxxxxxxxxxx> This patch fixes PoP noise of around 15ms observed during audio capture begin. Enables BCLK and LRCLK in snd_soc_dai_ops prepare call for introducing some delay before capture start and clock enable. Co-developed-by: Judy Hsiao <judyhsiao@xxxxxxxxxxxx> Signed-off-by: Judy Hsiao <judyhsiao@xxxxxxxxxxxx> Signed-off-by: Srinivasa Rao Mandadapu <srivasam@xxxxxxxxxxxxxx> (am from https://patchwork.kernel.org/patch/12276369/) (also found at https://lore.kernel.org/r/20210524142114.18676-1-srivasam@xxxxxxxxxxxxxx) --- Chages Since V3: -- Check BCLK is off before enabling it in lpass_cpu_daiops_prepare as lpass_cpu_daiops_prepare can be called multiple times. -- Check BCLK is on before disabling it in lpass_cpu_daiops_shutdown to fix the WARN. It is because BCLK may not be enabled if lpass_cpu_daiops_prepare is not called before lpass_cpu_daiops_shutdown. -- Adds more comments. Changes Since V2: -- Updated comments as per linux style -- Removed unrelated changes. Changes Since V1: -- Enableed BCLK and LRCLK in dai ops prepare API instead of startup API -- Added comments sound/soc/qcom/lpass-cpu.c | 83 +++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index af8cb64924a0..b572d7874554 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -6,6 +6,7 @@ */ #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> @@ -93,8 +94,28 @@ static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct lpaif_i2sctl *i2sctl = drvdata->i2sctl; + unsigned int id = dai->driver->id; clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); + /* + * To ensure LRCLK disabled even in device node validation + * Will not impact if disabled in lpass_cpu_daiops_trigger() + * suspend. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_DISABLE); + else + regmap_fields_write(i2sctl->micen, id, LPAIF_I2SCTL_MICEN_DISABLE); + + /* + * BCLK may not be enabled if lpass_cpu_daiops_prepare is called before + * lpass_cpu_daiops_shutdown. It's paired with the clk_enable in + * lpass_cpu_daiops_prepare. + */ + if (__clk_is_enabled(drvdata->mi2s_bit_clk[dai->driver->id])) + clk_disable(drvdata->mi2s_bit_clk[dai->driver->id]); + clk_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]); } @@ -275,6 +296,18 @@ static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * To ensure lpass BCLK/LRCLK is enabled during + * device resume as lpass_cpu_daiops_prepare() is not called + * after the device resumes. We don't check BCLK status before + * enable/disable BCLK in trigger event because: + * 1. These trigger events are paired, so the BCLK + * enable_count is balanced. + * 2. the BCLK can be shared (ex: headset and headset mic), + * we need to increase the enable_count so that we don't + * turn off the shared BCLK while other devices are using + * it. + */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ret = regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_ENABLE); @@ -288,7 +321,8 @@ static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, ret = clk_enable(drvdata->mi2s_bit_clk[id]); if (ret) { - dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret); + dev_err(dai->dev, + "error in enabling mi2s bit clk: %d\n", ret); clk_disable(drvdata->mi2s_osr_clk[id]); return ret; } @@ -296,6 +330,10 @@ static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* + * To ensure lpass BCLK/LRCLK is disabled during + * device suspend. + */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ret = regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_DISABLE); @@ -315,12 +353,55 @@ static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, return ret; } +static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct lpaif_i2sctl *i2sctl = drvdata->i2sctl; + unsigned int id = dai->driver->id; + int ret; + /* + * To ensure lpass BCLK/LRCLK is enabled bit before playback/capture + * data flow starts. This allows other codec to have some delay before + * the data flow. + * (ex: to drop start up pop noise before capture starts). + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = regmap_fields_write(i2sctl->spken, id, + LPAIF_I2SCTL_SPKEN_ENABLE); + else + ret = regmap_fields_write(i2sctl->micen, id, + LPAIF_I2SCTL_MICEN_ENABLE); + + if (ret) { + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret); + return ret; + } + + /* + * Check BCLK is off before enabling it as lpass_cpu_daiops_prepare can + * be called multiple times. It's paired with the clk_disable in + * lpass_cpu_daiops_shutdown. + */ + if (!__clk_is_enabled(drvdata->mi2s_bit_clk[dai->driver->id])) { + ret = clk_enable(drvdata->mi2s_bit_clk[id]); + if (ret) { + dev_err(dai->dev, + "error in enabling mi2s bit clk: %d\n", ret); + clk_disable(drvdata->mi2s_osr_clk[id]); + return ret; + } + } + return 0; +} + const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops = { .set_sysclk = lpass_cpu_daiops_set_sysclk, .startup = lpass_cpu_daiops_startup, .shutdown = lpass_cpu_daiops_shutdown, .hw_params = lpass_cpu_daiops_hw_params, .trigger = lpass_cpu_daiops_trigger, + .prepare = lpass_cpu_daiops_prepare, }; EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops); -- 2.31.0