Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 182864
b: refs/heads/master
c: b058091
h: refs/heads/master
v: v3
  • Loading branch information
Guennadi Liakhovetski authored and Mark Brown committed Feb 1, 2010
1 parent 14d64a6 commit 889a8ab
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 38 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: b2c3e923110f6ca60ccb30cf4a6bda5211454c4f
refs/heads/master: b0580913797034a1001e867b8b492c75226bf77e
115 changes: 78 additions & 37 deletions trunk/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 889a8ab

Please sign in to comment.