On Fri, Dec 17, 2010 at 11:06:18PM +0000, Mark Brown wrote: > I'll make sure the FLL is supported as soon as possible, and if we can > get these drivers working on Harmoney we could also do the machine > driver updates there. The below *completely untested* patch should get the FLL going, though I really must emphasise the *completely untested* part - the patch needed a bit of rework for 2.6.36 and I spent longer than intended fighting with the Harmony rather than setting up my normal test system as the Harmony setup had looked like it'd work. To use this you'll need to have your machine driver set up the FLL in hw_params. If the CODEC is master then feed it whatever you like on MCLK (it's pretty flexible) and then do something like: snd_soc_dai_set_fll(codec_dai, 0, WM8903_FLL_SRC_MCLK, input, params_rate(params) * 256); snd_soc_dai_set_sysclk(codec_dai, WM8903_SYSCLK_FLL, params_rate(params) * 256); where input is your input frequency on MCLK. If the CODEC is slave then use something like this: snd_soc_dai_set_fll(codec_dai, 0, WM8903_FLL_SRC_BCLK, bclk_rate, params_rate(params) * 256); snd_soc_dai_set_sysclk(codec_dai, WM8903_SYSCLK_FLL, params_rate(params) * 256); where bclk_rate is whatever BCLK is running at - probably some multiple of params_rate() (again, the device is pretty flexible so should be able to consume anything that looks even roughly like an audio frequency). Ideally the FLL should be stopped when the DAI is not in use: snd_soc_dai_set_fll(codec_dai, 0, 0, 0, 0); (but first set sysclk back to MCLK if it's actually still in use, eg for jack detection). The patch also removes an old change to set up the default state of headphone outputs on early silicon revs which interferes with the new FLL stuff. Once more, this is *completely untested* diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index bf08282..0f227de 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -217,6 +217,7 @@ struct wm8903_priv { u16 reg_cache[ARRAY_SIZE(wm8903_reg_defaults)]; int sysclk; + int sysclk_src; /* Reference counts */ int class_w_users; @@ -231,6 +232,10 @@ struct wm8903_priv { int mic_last_report; int mic_delay; + int fll_src; + int fll_fref; + int fll_fout; + struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; }; @@ -936,7 +941,7 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { struct i2c_client *i2c = codec->control_data; - u16 reg, reg2; + u16 reg; switch (level) { case SND_SOC_BIAS_ON: @@ -960,22 +965,12 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, wm8903_run_sequence(codec, 0); wm8903_sync_reg_cache(codec, codec->reg_cache); - /* Enable low impedence charge pump output */ - reg = snd_soc_read(codec, - WM8903_CONTROL_INTERFACE_TEST_1); - snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, - reg | WM8903_TEST_KEY); - reg2 = snd_soc_read(codec, WM8903_CHARGE_PUMP_TEST_1); - snd_soc_write(codec, WM8903_CHARGE_PUMP_TEST_1, - reg2 | WM8903_CP_SW_KELVIN_MODE_MASK); - snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, - reg); - /* By default no bypass paths are enabled so * enable Class W support. */ dev_dbg(&i2c->dev, "Enabling Class W\n"); - snd_soc_write(codec, WM8903_CLASS_W_0, reg | + snd_soc_update_bits(codec, WM8903_CLASS_W_0, + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V, WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); } @@ -1004,7 +999,17 @@ static int wm8903_set_dai_sysclk(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - wm8903->sysclk = freq; + switch (clk_id) { + case WM8903_SYSCLK_MCLK: + wm8903->sysclk = freq; + break; + case WM8903_SYSCLK_FLL: + break; + default: + return -EINVAL; + } + + wm8903->sysclk_src = clk_id; return 0; } @@ -1366,10 +1371,13 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - dev_dbg(&i2c->dev, "MCLK = %dHz, target sample rate = %dHz\n", + if (wm8903->sysclk_src == WM8903_SYSCLK_FLL) + wm8903->sysclk = wm8903->fll_fout; + + dev_dbg(&i2c->dev, "SYSCLK = %dHz, target sample rate = %dHz\n", wm8903->sysclk, fs); - /* We may not have an MCLK which allows us to generate exactly + /* We may not have a SYSCLK which allows us to generate exactly * the clock we want, particularly with USB derived inputs, so * approximate. */ @@ -1445,6 +1453,169 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, return 0; } +struct fll_config { + int clk_ref_div; + int outdiv; + int fratio; + int n; + int k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + int fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int wm8903_fll_config(struct fll_config *fll, int Fref, int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + int i; + + memset(fll, 0, sizeof(*fll)); + + /* Fref must be <=13.5MHz */ + div = 1; + while ((Fref / div) > 13500000) { + div *= 2; + fll->clk_ref_div++; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 2; + while (Fout * div < 90000000) { + fll->outdiv++; + div *= 2; + if (div > 256) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * div; + + pr_debug("Fvco=%dHz\n", target); + + /* Find an appropraite FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll->fratio = fll_fratios[i].fll_fratio; + target /= fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + fll->n = Ndiv; + Nmod = target % Fref; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll->k = K / 10; + + pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", + fll->n, fll->k, fll->fratio, fll->outdiv, fll->clk_ref_div); + + return 0; +} + +static int wm8903_set_fll(struct snd_soc_dai *dai, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + struct fll_config fll; + int ret; + + if (Fout) { + switch (source) { + case WM8903_FLL_SRC_MCLK: + case WM8903_FLL_SRC_BCLK: + case WM8903_FLL_SRC_LRCLK: + break; + default: + dev_err(codec->dev, "Invalid FLL source %d\n", source); + return -EINVAL; + } + + ret = wm8903_fll_config(&fll, Fref, Fout); + if (ret != 0) + return ret; + } + + /* Disable the FLL while configuring */ + snd_soc_update_bits(codec, WM8903_FLL_CONTROL_1, + WM8903_FLL_ENA, 0); + + if (!Fout) + return 0; + + /* Set the new config */ + snd_soc_update_bits(codec, WM8903_FLL_CONTROL_2, + WM8903_FLL_CLK_SRC_MASK | + WM8903_FLL_CLK_REF_DIV_MASK | + WM8903_FLL_OUTDIV_MASK | + WM8903_FLL_FRATIO_MASK, + (source - 1) << WM8903_FLL_CLK_SRC_SHIFT | + fll.clk_ref_div << WM8903_FLL_CLK_REF_DIV_SHIFT | + fll.outdiv << WM8903_FLL_OUTDIV_SHIFT | + fll.fratio << WM8903_FLL_FRATIO_SHIFT); + snd_soc_update_bits(codec, WM8903_FLL_CONTROL_3, + WM8903_FLL_K_MASK, fll.k); + snd_soc_update_bits(codec, WM8903_FLL_CONTROL_4, + WM8903_FLL_N_MASK, fll.n); + + snd_soc_update_bits(codec, WM8903_FLL_CONTROL_1, + WM8903_FLL_ENA | WM8903_FLL_HOLD, + WM8903_FLL_ENA | WM8903_FLL_HOLD); + + wm8903->fll_src = source; + wm8903->fll_fref = Fref; + wm8903->fll_fout = Fout; + + return 0; +} + /** * wm8903_mic_detect - Enable microphone detection via the WM8903 IRQ * @@ -1584,6 +1755,7 @@ static struct snd_soc_dai_ops wm8903_dai_ops = { .digital_mute = wm8903_digital_mute, .set_fmt = wm8903_set_dai_fmt, .set_sysclk = wm8903_set_dai_sysclk, + .set_pll = wm8903_set_fll, }; struct snd_soc_dai wm8903_dai = { @@ -1612,6 +1784,8 @@ static int wm8903_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; + snd_soc_update_bits(codec, WM8903_FLL_CONTROL_1, WM8903_FLL_ENA, 0); + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -1621,6 +1795,7 @@ static int wm8903_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; int i; u16 *reg_cache = codec->reg_cache; @@ -1640,6 +1815,10 @@ static int wm8903_resume(struct platform_device *pdev) dev_err(&i2c->dev, "Failed to allocate temporary cache\n"); } + /* If the FLL was not configured this is a noop */ + wm8903_set_fll(codec->dai, 0, wm8903->fll_src, wm8903->fll_fref, + wm8903->fll_fout); + return 0; } diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h index ce384a2..3b8587b 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -22,10 +22,12 @@ extern int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int det, int shrt); -#define WM8903_MCLK_DIV_2 1 -#define WM8903_CLK_SYS 2 -#define WM8903_BCLK 3 -#define WM8903_LRCLK 4 +#define WM8903_SYSCLK_MCLK 1 +#define WM8903_SYSCLK_FLL 2 + +#define WM8903_FLL_SRC_MCLK 1 +#define WM8903_FLL_SRC_BCLK 2 +#define WM8903_FLL_SRC_LRCLK 3 /* * Register values. @@ -101,8 +103,10 @@ extern int wm8903_mic_detect(struct snd_soc_codec *codec, #define WM8903_INTERRUPT_STATUS_1_MASK 0x7A #define WM8903_INTERRUPT_POLARITY_1 0x7B #define WM8903_INTERRUPT_CONTROL 0x7E -#define WM8903_CONTROL_INTERFACE_TEST_1 0x81 -#define WM8903_CHARGE_PUMP_TEST_1 0x95 +#define WM8903_FLL_CONTROL_1 0x80 +#define WM8903_FLL_CONTROL_2 0x81 +#define WM8903_FLL_CONTROL_3 0x82 +#define WM8903_FLL_CONTROL_4 0x83 #define WM8903_CLOCK_RATE_TEST_4 0xA4 #define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC @@ -1209,23 +1213,56 @@ extern int wm8903_mic_detect(struct snd_soc_codec *codec, #define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */ /* - * R129 (0x81) - Control Interface Test 1 - */ -#define WM8903_USER_KEY 0x0002 /* USER_KEY */ -#define WM8903_USER_KEY_MASK 0x0002 /* USER_KEY */ -#define WM8903_USER_KEY_SHIFT 1 /* USER_KEY */ -#define WM8903_USER_KEY_WIDTH 1 /* USER_KEY */ -#define WM8903_TEST_KEY 0x0001 /* TEST_KEY */ -#define WM8903_TEST_KEY_MASK 0x0001 /* TEST_KEY */ -#define WM8903_TEST_KEY_SHIFT 0 /* TEST_KEY */ -#define WM8903_TEST_KEY_WIDTH 1 /* TEST_KEY */ - -/* - * R149 (0x95) - Charge Pump Test 1 - */ -#define WM8903_CP_SW_KELVIN_MODE_MASK 0x0006 /* CP_SW_KELVIN_MODE - [2:1] */ -#define WM8903_CP_SW_KELVIN_MODE_SHIFT 1 /* CP_SW_KELVIN_MODE - [2:1] */ -#define WM8903_CP_SW_KELVIN_MODE_WIDTH 2 /* CP_SW_KELVIN_MODE - [2:1] */ + * R128 (0x80) - FLL Control 1 + */ +#define WM8903_FLL_GAIN_MASK 0x00F0 /* FLL_GAIN - [7:4] */ +#define WM8903_FLL_GAIN_SHIFT 4 /* FLL_GAIN - [7:4] */ +#define WM8903_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [7:4] */ +#define WM8903_FLL_HOLD 0x0008 /* FLL_HOLD */ +#define WM8903_FLL_HOLD_MASK 0x0008 /* FLL_HOLD */ +#define WM8903_FLL_HOLD_SHIFT 3 /* FLL_HOLD */ +#define WM8903_FLL_HOLD_WIDTH 1 /* FLL_HOLD */ +#define WM8903_FLL_FRAC 0x0004 /* FLL_FRAC */ +#define WM8903_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */ +#define WM8903_FLL_FRAC_SHIFT 2 /* FLL_FRAC */ +#define WM8903_FLL_FRAC_WIDTH 1 /* FLL_FRAC */ +#define WM8903_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM8903_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM8903_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM8903_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R129 (0x81) - FLL Control 2 + */ +#define WM8903_FLL_CLK_SRC_MASK 0x1800 /* FLL_CLK_SRC - [12:11] */ +#define WM8903_FLL_CLK_SRC_SHIFT 11 /* FLL_CLK_SRC - [12:11] */ +#define WM8903_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [12:11] */ +#define WM8903_FLL_CLK_REF_DIV_MASK 0x0600 /* FLL_CLK_REF_DIV - [10:9] */ +#define WM8903_FLL_CLK_REF_DIV_SHIFT 9 /* FLL_CLK_REF_DIV - [10:9] */ +#define WM8903_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [10:9] */ +#define WM8903_FLL_CTRL_RATE_MASK 0x01C0 /* FLL_CTRL_RATE - [8:6] */ +#define WM8903_FLL_CTRL_RATE_SHIFT 6 /* FLL_CTRL_RATE - [8:6] */ +#define WM8903_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [8:6] */ +#define WM8903_FLL_OUTDIV_MASK 0x0038 /* FLL_OUTDIV - [5:3] */ +#define WM8903_FLL_OUTDIV_SHIFT 3 /* FLL_OUTDIV - [5:3] */ +#define WM8903_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [5:3] */ +#define WM8903_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM8903_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM8903_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R130 (0x82) - FLL Control 3 + */ +#define WM8903_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */ +#define WM8903_FLL_K_SHIFT 0 /* FLL_K - [15:0] */ +#define WM8903_FLL_K_WIDTH 16 /* FLL_K - [15:0] */ + +/* + * R131 (0x83) - FLL Control 4 + */ +#define WM8903_FLL_N_MASK 0x03FF /* FLL_N - [9:0] */ +#define WM8903_FLL_N_SHIFT 0 /* FLL_N - [9:0] */ +#define WM8903_FLL_N_WIDTH 10 /* FLL_N - [9:0] */ /* * R164 (0xA4) - Clock Rate Test 4 -- To unsubscribe from this list: send the line "unsubscribe linux-tegra" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html