Re: [PATCH 10/11] ASoC: tegra: Harmony machine support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [ARM Kernel]     [Linux ARM]     [Linux ARM MSM]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux