[alsa-devel] [PATCH] ASoC: wm8985: rework and fix the clock calculation

Petr Kulhavy petr at barix.com
Thu May 12 08:48:54 CEST 2016


The clock calculation has several issues:
1) if PLL is used in master mode the BCLK output runs at double the speed
2) de-facto only 44.1kHz and 48kHz sampling rates are supported, other
   rates like 8kHz, 12kHz, 24kHz fail to find the proper BCLK divider
3) the wm8985->sysclk variable has a misleading name and is used wrongly
   in the clock calculation in wm8985_hw_params() which is the root cause
   for (1)
4) wm8985->bclk is used only in wm8985_hw_params() and therefore no
   need to store it in the wm8985_priv structure

Therefore the clock calculation is rewritten in more clean and proper way:
- move wm8985_priv->bclk as a local variable into mw8985_hw_params()
- new variable wm8985_priv->pllout holds the actual frequency that is input
  to the MCLKDIV post-divider
- move wm8985_priv->sysclk as a local variable into mw8985_hw_params()
- sysclk is now always calculated as 256 * fs
- the MCLKDIV is looked up as pllout/sysclk
- fs_ratios[] is replaced by simpler mclk_divs[] lookup table

With this patch all rates: 8, 11.025, 12, 16, 22.05, 24, 32, 44.1 and 48kHz
work properly and generate the correct BCLK.

Signed-off-by: Petr Kulhavy <petr at barix.com>
---
 sound/soc/codecs/wm8985.c | 59 ++++++++++++++++++++++++-----------------------
 1 file changed, 30 insertions(+), 29 deletions(-)

diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c
index 18f2babe1090..628127aa3c96 100644
--- a/sound/soc/codecs/wm8985.c
+++ b/sound/soc/codecs/wm8985.c
@@ -181,22 +181,11 @@ static const int volume_update_regs[] = {
 struct wm8985_priv {
 	struct regmap *regmap;
 	struct regulator_bulk_data supplies[WM8985_NUM_SUPPLIES];
-	unsigned int sysclk;
-	unsigned int bclk;
+	unsigned int pllout;	/* input rate to the MCLKDIV divider */
 };
 
-static const struct {
-	int div;
-	int ratio;
-} fs_ratios[] = {
-	{ 10, 128 },
-	{ 15, 192 },
-	{ 20, 256 },
-	{ 30, 384 },
-	{ 40, 512 },
-	{ 60, 768 },
-	{ 80, 1024 },
-	{ 120, 1536 }
+static const int mclk_divs[] = {
+	10, 15, 20, 30, 40, 60, 80, 120
 };
 
 static const int srates[] = { 48000, 32000, 24000, 16000, 12000, 8000 };
@@ -693,15 +682,18 @@ static int wm8985_hw_params(struct snd_pcm_substream *substream,
 	struct snd_soc_codec *codec;
 	struct wm8985_priv *wm8985;
 	u16 blen, srate_idx;
-	unsigned int tmp;
 	int srate_best;
+	unsigned int bclk;	/* bit clock matching the current params */
+	unsigned int sysclk;	/* the actual sysclk after post division */
 
 	codec = dai->codec;
 	wm8985 = snd_soc_codec_get_drvdata(codec);
 
-	wm8985->bclk = snd_soc_params_to_bclk(params);
-	if ((int)wm8985->bclk < 0)
-		return wm8985->bclk;
+	/* always use 256 * fs in order to get best filter quality */
+	sysclk = 256 * params_rate(params);
+	bclk = snd_soc_params_to_bclk(params);
+	if ((int)bclk < 0)
+		return bclk;
 
 	switch (params_format(params)) {
 	case SNDRV_PCM_FORMAT_S16_LE:
@@ -742,29 +734,28 @@ static int wm8985_hw_params(struct snd_pcm_substream *substream,
 	snd_soc_update_bits(codec, WM8985_ADDITIONAL_CONTROL,
 			    WM8985_SR_MASK, srate_idx << WM8985_SR_SHIFT);
 
-	dev_dbg(dai->dev, "Target BCLK = %uHz\n", wm8985->bclk);
-	dev_dbg(dai->dev, "SYSCLK = %uHz\n", wm8985->sysclk);
+	dev_dbg(dai->dev, "Target BCLK = %uHz\n", bclk);
+	dev_dbg(dai->dev, "SYSCLK = %uHz\n", sysclk);
 
-	for (i = 0; i < ARRAY_SIZE(fs_ratios); ++i) {
-		if (wm8985->sysclk / params_rate(params)
-				== fs_ratios[i].ratio)
+	/* select the appropriate mclk divider */
+	for (i = 0; i < ARRAY_SIZE(mclk_divs); ++i) {
+		if (wm8985->pllout / mclk_divs[i] * 10
+				== sysclk)
 			break;
 	}
 
-	if (i == ARRAY_SIZE(fs_ratios)) {
+	if (i == ARRAY_SIZE(mclk_divs)) {
 		dev_err(dai->dev, "Unable to configure MCLK ratio %u/%u\n",
-			wm8985->sysclk, params_rate(params));
+			wm8985->pllout, sysclk);
 		return -EINVAL;
 	}
 
-	dev_dbg(dai->dev, "MCLK ratio = %dfs\n", fs_ratios[i].ratio);
 	snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
 			    WM8985_MCLKDIV_MASK, i << WM8985_MCLKDIV_SHIFT);
 
 	/* select the appropriate bclk divider */
-	tmp = (wm8985->sysclk / fs_ratios[i].div) * 10;
 	for (i = 0; i < ARRAY_SIZE(bclk_divs); ++i) {
-		if (wm8985->bclk == tmp / bclk_divs[i])
+		if (bclk == sysclk / bclk_divs[i])
 			break;
 	}
 
@@ -786,6 +777,10 @@ struct pll_div {
 };
 
 #define FIXED_PLL_SIZE ((1ULL << 24) * 10)
+/*
+ * source = MCLK input of the chip
+ * target = the f2 coming out of the PLL before /4 divider
+ */
 static int pll_factors(struct pll_div *pll_div, unsigned int target,
 		       unsigned int source)
 {
@@ -872,17 +867,23 @@ static int wm8985_set_sysclk(struct snd_soc_dai *dai,
 				    WM8985_CLKSEL_MASK, 0);
 		snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
 				    WM8985_PLLEN_MASK, 0);
+		wm8985->pllout = freq;
 		break;
 	case WM8985_CLKSRC_PLL:
 		snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
 				    WM8985_CLKSEL_MASK, WM8985_CLKSEL);
+		/*
+		 * in order to run the PLL within the recommended 90MHz
+		 * operating range the wm8985_set_pll() configures the PLL
+		 * to output double the required frequency
+		 */
+		wm8985->pllout = 2 * freq;
 		break;
 	default:
 		dev_err(dai->dev, "Unknown clock source %d\n", clk_id);
 		return -EINVAL;
 	}
 
-	wm8985->sysclk = freq;
 	return 0;
 }
 
-- 
1.9.1



More information about the Alsa-devel mailing list