Commit b0580913 authored by Guennadi Liakhovetski's avatar Guennadi Liakhovetski Committed by Mark Brown

ASoC: improve MCLKDIV calculation in wm8978, when OPCLK is not used

In case, if OPCLK is not used, and PLL is used for driving the codec, the
choice of PLL output frequency could result in a needlessly imprecise
system clock frequency. Use an iterative process to select a precise
configuration.
Signed-off-by: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Acked-by: default avatarLiam Girdwood <lrg@slimlogic.co.uk>
Signed-off-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
parent b2c3e923
......@@ -58,6 +58,7 @@ struct wm8978_priv {
unsigned int f_mclk;
unsigned int f_256fs;
unsigned int f_opclk;
int mclk_idx;
enum wm8978_sysclk_src sysclk;
u16 reg_cache[WM8978_CACHEREGNUM];
};
......@@ -402,6 +403,35 @@ static void pll_factors(struct wm8978_pll_div *pll_div, unsigned int target,
pll_div->k = k;
}
/* MCLK dividers */
static const int mclk_numerator[] = {1, 3, 2, 3, 4, 6, 8, 12};
static const int mclk_denominator[] = {1, 2, 1, 1, 1, 1, 1, 1};
/*
* find index >= idx, such that, for a given f_out,
* 3 * f_mclk / 4 <= f_PLLOUT < 13 * f_mclk / 4
* f_out can be f_256fs or f_opclk, currently only used for f_256fs. Can be
* generalised for f_opclk with suitable coefficient arrays, but currently
* the OPCLK divisor is calculated directly, not iteratively.
*/
static int wm8978_enum_mclk(unsigned int f_out, unsigned int f_mclk,
unsigned int *f_pllout)
{
int i;
for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
unsigned int f_pllout_x4 = 4 * f_out * mclk_numerator[i] /
mclk_denominator[i];
if (3 * f_mclk <= f_pllout_x4 && f_pllout_x4 < 13 * f_mclk) {
*f_pllout = f_pllout_x4 / 4;
return i;
}
}
return -EINVAL;
}
/*
* Calculate internal frequencies and dividers, according to Figure 40
* "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6
......@@ -412,12 +442,16 @@ static int wm8978_configure_pll(struct snd_soc_codec *codec)
struct wm8978_pll_div pll_div;
unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk,
f_256fs = wm8978->f_256fs;
unsigned int f2, opclk_div;
unsigned int f2;
if (!f_mclk)
return -EINVAL;
if (f_opclk) {
unsigned int opclk_div;
/* Cannot set up MCLK divider now, do later */
wm8978->mclk_idx = -1;
/*
* The user needs OPCLK. Choose OPCLKDIV to put
* 6 <= R = f2 / f1 < 13, 1 <= OPCLKDIV <= 4.
......@@ -444,7 +478,7 @@ static int wm8978_configure_pll(struct snd_soc_codec *codec)
wm8978->f_pllout = f_opclk * opclk_div;
} else if (f_256fs) {
/*
* Not using OPCLK, choose R:
* Not using OPCLK, but PLL is used for the codec, choose R:
* 6 <= R = f2 / f1 < 13, to put 1 <= MCLKDIV <= 12.
* f_256fs = f_mclk * prescale * R / 4 / MCLKDIV, where
* prescale = 1, or prescale = 2. Prescale is calculated inside
......@@ -453,18 +487,11 @@ static int wm8978_configure_pll(struct snd_soc_codec *codec)
* f_mclk * 3 / 48 <= f_256fs < f_mclk * 13 / 4. This means MCLK
* must be 3.781MHz <= f_MCLK <= 32.768MHz
*/
if (48 * f_256fs < 3 * f_mclk || 4 * f_256fs >= 13 * f_mclk)
return -EINVAL;
int idx = wm8978_enum_mclk(f_256fs, f_mclk, &wm8978->f_pllout);
if (idx < 0)
return idx;
/*
* MCLKDIV will be selected in .hw_params(), just choose a
* suitable f_PLLOUT
*/
if (4 * f_256fs < 3 * f_mclk)
/* Will have to use MCLKDIV */
wm8978->f_pllout = wm8978->f_mclk * 3 / 4;
else
wm8978->f_pllout = f_256fs;
wm8978->mclk_idx = idx;
/* GPIO1 into default mode as input - before configuring PLL */
snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0);
......@@ -515,6 +542,20 @@ static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
wm8978->f_opclk = div;
if (wm8978->f_mclk)
/*
* We know the MCLK frequency, the user has requested
* OPCLK, configure the PLL based on that and start it
* and OPCLK immediately. We will configure PLL to match
* user-requested OPCLK frquency as good as possible.
* In fact, it is likely, that matching the sampling
* rate, when it becomes known, is more important, and
* we will not be reconfiguring PLL then, because we
* must not interrupt OPCLK. But it should be fine,
* because typically the user will request OPCLK to run
* at 256fs or 512fs, and for these cases we will also
* find an exact MCLK divider configuration - it will
* be equal to or double the OPCLK divisor.
*/
ret = wm8978_configure_pll(codec);
break;
case WM8978_BCLKDIV:
......@@ -640,10 +681,6 @@ static int wm8978_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
return 0;
}
/* MCLK dividers */
static const int mclk_numerator[] = {1, 3, 2, 3, 4, 6, 8, 12};
static const int mclk_denominator[] = {1, 2, 1, 1, 1, 1, 1, 1};
/*
* Set PCM DAI bit size and sample rate.
*/
......@@ -709,9 +746,11 @@ static int wm8978_hw_params(struct snd_pcm_substream *substream,
wm8978->f_256fs = params_rate(params) * 256;
if (wm8978->sysclk == WM8978_MCLK) {
wm8978->mclk_idx = -1;
f_sel = wm8978->f_mclk;
} else {
if (!wm8978->f_pllout) {
/* We only enter here, if OPCLK is not used */
int ret = wm8978_configure_pll(codec);
if (ret < 0)
return ret;
......@@ -719,11 +758,8 @@ static int wm8978_hw_params(struct snd_pcm_substream *substream,
f_sel = wm8978->f_pllout;
}
/*
* In some cases it is possible to reconfigure PLL to a higher frequency
* by raising OPCLKDIV, but normally OPCLK is configured to 256 * fs or
* 512 * fs, so, we should be fine.
*/
if (wm8978->mclk_idx < 0) {
/* Either MCLK is used directly, or OPCLK is used */
if (f_sel < wm8978->f_256fs || f_sel > 12 * wm8978->f_256fs)
return -EINVAL;
......@@ -739,10 +775,15 @@ static int wm8978_hw_params(struct snd_pcm_substream *substream,
if (!diff)
break;
}
} else {
/* OPCLK not used, codec driven by PLL */
best = wm8978->mclk_idx;
diff = 0;
}
if (diff)
dev_warn(codec->dev, "Imprecise clock: %u%s\n",
f_sel * mclk_denominator[best] / mclk_numerator[best],
dev_warn(codec->dev, "Imprecise sampling rate: %uHz%s\n",
f_sel * mclk_denominator[best] / mclk_numerator[best] / 256,
wm8978->sysclk == WM8978_MCLK ?
", consider using PLL" : "");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment