Skip to content

Commit

Permalink
ASoC: improve MCLKDIV calculation in wm8978, when OPCLK is not used
Browse files Browse the repository at this point in the history
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: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
  • Loading branch information
Guennadi Liakhovetski authored and Mark Brown committed Feb 1, 2010
1 parent b2c3e92 commit b058091
Showing 1 changed file with 78 additions and 37 deletions.
115 changes: 78 additions & 37 deletions sound/soc/codecs/wm8978.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -709,42 +746,46 @@ 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;
}
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 (f_sel < wm8978->f_256fs || f_sel > 12 * wm8978->f_256fs)
return -EINVAL;
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;

for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
diff = abs(wm8978->f_256fs * 3 -
f_sel * 3 * mclk_denominator[i] / mclk_numerator[i]);
for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
diff = abs(wm8978->f_256fs * 3 -
f_sel * 3 * mclk_denominator[i] / mclk_numerator[i]);

if (diff < diff_best) {
diff_best = diff;
best = i;
}
if (diff < diff_best) {
diff_best = diff;
best = i;
}

if (!diff)
break;
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],
wm8978->sysclk == WM8978_MCLK ?
", consider using PLL" : "");
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" : "");

dev_dbg(codec->dev, "%s: fmt %d, rate %u, MCLK divisor #%d\n", __func__,
params_format(params), params_rate(params), best);
Expand Down

0 comments on commit b058091

Please sign in to comment.