ASoC: nau8825: Add FLL configuration
authorBen Zhang <benzh@chromium.org>
Mon, 19 Oct 2015 23:49:05 +0000 (16:49 -0700)
committerMark Brown <broonie@kernel.org>
Thu, 22 Oct 2015 16:17:38 +0000 (01:17 +0900)
snd_soc_codec_driver.set_pll is implemented to configure the FLL.
The codec internal SYSCLK can be from either the MCLK pin directly,
or the FLL. This is configured by snd_soc_codec_driver.set_pll.

Signed-off-by: Ben Zhang <benzh@chromium.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/nau8825.c
sound/soc/codecs/nau8825.h

index 5c1badf47ac9e4b55957ca9e2bc72ff10161a3cc..bd58b9697731b4d60f2da2ae60399c78ac15191c 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/slab.h>
 #include <linux/clk.h>
 #include <linux/acpi.h>
+#include <linux/math64.h>
 
 #include <sound/initval.h>
 #include <sound/tlv.h>
 
 #include "nau8825.h"
 
+#define NAU_FREF_MAX 13500000
+#define NAU_FVCO_MAX 100000000
+#define NAU_FVCO_MIN 90000000
+
+struct nau8825_fll {
+       int mclk_src;
+       int ratio;
+       int fll_frac;
+       int fll_int;
+       int clk_ref_div;
+};
+
+struct nau8825_fll_attr {
+       unsigned int param;
+       unsigned int val;
+};
+
+/* scaling for mclk from sysclk_src output */
+static const struct nau8825_fll_attr mclk_src_scaling[] = {
+       { 1, 0x0 },
+       { 2, 0x2 },
+       { 4, 0x3 },
+       { 8, 0x4 },
+       { 16, 0x5 },
+       { 32, 0x6 },
+       { 3, 0x7 },
+       { 6, 0xa },
+       { 12, 0xb },
+       { 24, 0xc },
+       { 48, 0xd },
+       { 96, 0xe },
+       { 5, 0xf },
+};
+
+/* ratio for input clk freq */
+static const struct nau8825_fll_attr fll_ratio[] = {
+       { 512000, 0x01 },
+       { 256000, 0x02 },
+       { 128000, 0x04 },
+       { 64000, 0x08 },
+       { 32000, 0x10 },
+       { 8000, 0x20 },
+       { 4000, 0x40 },
+};
+
+static const struct nau8825_fll_attr fll_pre_scalar[] = {
+       { 1, 0x0 },
+       { 2, 0x1 },
+       { 4, 0x2 },
+       { 8, 0x3 },
+};
+
 static const struct reg_default nau8825_reg_defaults[] = {
        { NAU8825_REG_ENA_CTRL, 0x00ff },
        { NAU8825_REG_CLK_DIVIDER, 0x0050 },
@@ -808,6 +861,115 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec)
        return 0;
 }
 
+/**
+ * nau8825_calc_fll_param - Calculate FLL parameters.
+ * @fll_in: external clock provided to codec.
+ * @fs: sampling rate.
+ * @fll_param: Pointer to structure of FLL parameters.
+ *
+ * Calculate FLL parameters to configure codec.
+ *
+ * Returns 0 for success or negative error code.
+ */
+static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs,
+               struct nau8825_fll *fll_param)
+{
+       u64 fvco;
+       unsigned int fref, i;
+
+       /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing
+        * freq_in by 1, 2, 4, or 8 using FLL pre-scalar.
+        * FREF = freq_in / NAU8825_FLL_REF_DIV_MASK
+        */
+       for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) {
+               fref = fll_in / fll_pre_scalar[i].param;
+               if (fref <= NAU_FREF_MAX)
+                       break;
+       }
+       if (i == ARRAY_SIZE(fll_pre_scalar))
+               return -EINVAL;
+       fll_param->clk_ref_div = fll_pre_scalar[i].val;
+
+       /* Choose the FLL ratio based on FREF */
+       for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) {
+               if (fref >= fll_ratio[i].param)
+                       break;
+       }
+       if (i == ARRAY_SIZE(fll_ratio))
+               return -EINVAL;
+       fll_param->ratio = fll_ratio[i].val;
+
+       /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs.
+        * FDCO must be within the 90MHz - 100MHz or the FFL cannot be
+        * guaranteed across the full range of operation.
+        * FDCO = freq_out * 2 * mclk_src_scaling
+        */
+       for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) {
+               fvco = 256 * fs * 2 * mclk_src_scaling[i].param;
+               if (NAU_FVCO_MIN < fvco && fvco < NAU_FVCO_MAX)
+                       break;
+       }
+       if (i == ARRAY_SIZE(mclk_src_scaling))
+               return -EINVAL;
+       fll_param->mclk_src = mclk_src_scaling[i].val;
+
+       /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional
+        * input based on FDCO, FREF and FLL ratio.
+        */
+       fvco = div_u64(fvco << 16, fref * fll_param->ratio);
+       fll_param->fll_int = (fvco >> 16) & 0x3FF;
+       fll_param->fll_frac = fvco & 0xFFFF;
+       return 0;
+}
+
+static void nau8825_fll_apply(struct nau8825 *nau8825,
+               struct nau8825_fll *fll_param)
+{
+       regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
+               NAU8825_CLK_MCLK_SRC_MASK, fll_param->mclk_src);
+       regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1,
+                       NAU8825_FLL_RATIO_MASK, fll_param->ratio);
+       /* FLL 16-bit fractional input */
+       regmap_write(nau8825->regmap, NAU8825_REG_FLL2, fll_param->fll_frac);
+       /* FLL 10-bit integer input */
+       regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL3,
+                       NAU8825_FLL_INTEGER_MASK, fll_param->fll_int);
+       /* FLL pre-scaler */
+       regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL4,
+                       NAU8825_FLL_REF_DIV_MASK, fll_param->clk_ref_div);
+       /* select divided VCO input */
+       regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5,
+                       NAU8825_FLL_FILTER_SW_MASK, 0x0000);
+       /* FLL sigma delta modulator enable */
+       regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6,
+                       NAU8825_SDM_EN_MASK, NAU8825_SDM_EN);
+}
+
+/* freq_out must be 256*Fs in order to achieve the best performance */
+static int nau8825_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
+               unsigned int freq_in, unsigned int freq_out)
+{
+       struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+       struct nau8825_fll fll_param;
+       int ret, fs;
+
+       fs = freq_out / 256;
+       ret = nau8825_calc_fll_param(freq_in, fs, &fll_param);
+       if (ret < 0) {
+               dev_err(codec->dev, "Unsupported input clock %d\n", freq_in);
+               return ret;
+       }
+       dev_dbg(codec->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n",
+               fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac,
+               fll_param.fll_int, fll_param.clk_ref_div);
+
+       nau8825_fll_apply(nau8825, &fll_param);
+       mdelay(2);
+       regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
+                       NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO);
+       return 0;
+}
+
 static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id,
        unsigned int freq)
 {
@@ -920,6 +1082,7 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec,
 static struct snd_soc_codec_driver nau8825_codec_driver = {
        .probe = nau8825_codec_probe,
        .set_sysclk = nau8825_set_sysclk,
+       .set_pll = nau8825_set_pll,
        .set_bias_level = nau8825_set_bias_level,
        .suspend_bias_off = true,
 
index 8774923502b474b942e3fe732f3a0a59843b765f..dff8edb83bfdfeb034a230ddbfce02952617eace 100644 (file)
 #define NAU8825_ENABLE_SAR_SFT 1
 
 /* CLK_DIVIDER (0x3) */
-#define NAU8825_CLK_SRC_SFT    15
-#define NAU8825_CLK_SRC_MASK   (1 << NAU8825_CLK_SRC_SFT)
-#define NAU8825_CLK_SRC_VCO    (1 << NAU8825_CLK_SRC_SFT)
-#define NAU8825_CLK_SRC_MCLK   (0 << NAU8825_CLK_SRC_SFT)
+#define NAU8825_CLK_SRC_SFT                    15
+#define NAU8825_CLK_SRC_MASK                   (1 << NAU8825_CLK_SRC_SFT)
+#define NAU8825_CLK_SRC_VCO                    (1 << NAU8825_CLK_SRC_SFT)
+#define NAU8825_CLK_SRC_MCLK                   (0 << NAU8825_CLK_SRC_SFT)
+#define NAU8825_CLK_MCLK_SRC_MASK              (0xf << 0)
+
+/* FLL1 (0x04) */
+#define NAU8825_FLL_RATIO_MASK                 (0x7f << 0)
+
+/* FLL3 (0x06) */
+#define NAU8825_FLL_INTEGER_MASK               (0x3ff << 0)
+
+/* FLL4 (0x07) */
+#define NAU8825_FLL_REF_DIV_MASK               (0x3 << 10)
+
+/* FLL5 (0x08) */
+#define NAU8825_FLL_FILTER_SW_MASK             (0x1 << 14)
 
 /* FLL6 (0x9) */
-#define NAU8825_DCO_EN (1 << 15)
+#define NAU8825_DCO_EN_MASK                    (0x1 << 15)
+#define NAU8825_DCO_EN                         (0x1 << 15)
+#define NAU8825_DCO_DIS                                (0x0 << 15)
+#define NAU8825_SDM_EN_MASK                    (0x1 << 14)
+#define NAU8825_SDM_EN                         (0x1 << 14)
+#define NAU8825_SDM_DIS                                (0x0 << 14)
 
 /* HSD_CTRL (0xc) */
 #define NAU8825_HSD_AUTO_MODE  (1 << 6)