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

Charles Keepax ckeepax at opensource.wolfsonmicro.com
Thu May 12 14:15:58 CEST 2016


On Thu, May 12, 2016 at 08:48:54AM +0200, Petr Kulhavy wrote:
> 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

These certainly seem like things that need fixed :-)

> 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);

Seems quite bold to assume we always want to use 256*fs, I am not
super familiar with the part itself but are there good reasons to
do that? What if someone wanted to use say a direct MCLK and a
lower multiple?

The intent presumably here is that set_sysclk is where we set the
target SYSCLK and set_pll should set the PLL output then here we
probably should work out the correct MCLKDIV to get those.

> +	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;

Were does this *2 come from? Is this the PLLPRESCALE?

>  		break;
>  	default:
>  		dev_err(dai->dev, "Unknown clock source %d\n", clk_id);
>  		return -EINVAL;
>  	}
>  
> -	wm8985->sysclk = freq;
>  	return 0;
>  }
>  
> -- 
> 1.9.1

Thanks,
Charles


More information about the Alsa-devel mailing list