aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2009-03-18 14:28:01 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-03-19 07:56:11 -0400
commite8523b641cddedec754ae5e44ec579dbceea5ef4 (patch)
tree2ee6bdfa6dc3484202616b7f840b53d56da3101f /sound
parent24a51029fc3055f33684e650b5e3a59f77c9b05c (diff)
ASoC: Add FLL support for WM8400
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/wm8400.c129
1 files changed, 129 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c
index b7350c25b61..510efa60400 100644
--- a/sound/soc/codecs/wm8400.c
+++ b/sound/soc/codecs/wm8400.c
@@ -70,6 +70,7 @@ struct wm8400_priv {
70 unsigned int sysclk; 70 unsigned int sysclk;
71 unsigned int pcmclk; 71 unsigned int pcmclk;
72 struct work_struct work; 72 struct work_struct work;
73 int fll_in, fll_out;
73}; 74};
74 75
75static inline unsigned int wm8400_read(struct snd_soc_codec *codec, 76static inline unsigned int wm8400_read(struct snd_soc_codec *codec,
@@ -931,6 +932,133 @@ static int wm8400_set_dai_sysclk(struct snd_soc_dai *codec_dai,
931 return 0; 932 return 0;
932} 933}
933 934
935struct fll_factors {
936 u16 n;
937 u16 k;
938 u16 outdiv;
939 u16 fratio;
940 u16 freq_ref;
941};
942
943#define FIXED_FLL_SIZE ((1 << 16) * 10)
944
945static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
946 unsigned int Fref, unsigned int Fout)
947{
948 u64 Kpart;
949 unsigned int K, Nmod, target;
950
951 factors->outdiv = 2;
952 while (Fout * factors->outdiv < 90000000 ||
953 Fout * factors->outdiv > 100000000) {
954 factors->outdiv *= 2;
955 if (factors->outdiv > 32) {
956 dev_err(wm8400->wm8400->dev,
957 "Unsupported FLL output frequency %dHz\n",
958 Fout);
959 return -EINVAL;
960 }
961 }
962 target = Fout * factors->outdiv;
963 factors->outdiv = factors->outdiv >> 2;
964
965 if (Fref < 48000)
966 factors->freq_ref = 1;
967 else
968 factors->freq_ref = 0;
969
970 if (Fref < 1000000)
971 factors->fratio = 9;
972 else
973 factors->fratio = 0;
974
975 /* Ensure we have a fractional part */
976 do {
977 if (Fref < 1000000)
978 factors->fratio--;
979 else
980 factors->fratio++;
981
982 if (factors->fratio < 1 || factors->fratio > 8) {
983 dev_err(wm8400->wm8400->dev,
984 "Unable to calculate FRATIO\n");
985 return -EINVAL;
986 }
987
988 factors->n = target / (Fref * factors->fratio);
989 Nmod = target % (Fref * factors->fratio);
990 } while (Nmod == 0);
991
992 /* Calculate fractional part - scale up so we can round. */
993 Kpart = FIXED_FLL_SIZE * (long long)Nmod;
994
995 do_div(Kpart, (Fref * factors->fratio));
996
997 K = Kpart & 0xFFFFFFFF;
998
999 if ((K % 10) >= 5)
1000 K += 5;
1001
1002 /* Move down to proper range now rounding is done */
1003 factors->k = K / 10;
1004
1005 dev_dbg(wm8400->wm8400->dev,
1006 "FLL: Fref=%d Fout=%d N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
1007 Fref, Fout,
1008 factors->n, factors->k, factors->fratio, factors->outdiv);
1009
1010 return 0;
1011}
1012
1013static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
1014 unsigned int freq_in, unsigned int freq_out)
1015{
1016 struct snd_soc_codec *codec = codec_dai->codec;
1017 struct wm8400_priv *wm8400 = codec->private_data;
1018 struct fll_factors factors;
1019 int ret;
1020 u16 reg;
1021
1022 if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out)
1023 return 0;
1024
1025 if (freq_out != 0) {
1026 ret = fll_factors(wm8400, &factors, freq_in, freq_out);
1027 if (ret != 0)
1028 return ret;
1029 }
1030
1031 wm8400->fll_out = freq_out;
1032 wm8400->fll_in = freq_in;
1033
1034 /* We *must* disable the FLL before any changes */
1035 reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_2);
1036 reg &= ~WM8400_FLL_ENA;
1037 wm8400_write(codec, WM8400_POWER_MANAGEMENT_2, reg);
1038
1039 reg = wm8400_read(codec, WM8400_FLL_CONTROL_1);
1040 reg &= ~WM8400_FLL_OSC_ENA;
1041 wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
1042
1043 if (freq_out == 0)
1044 return 0;
1045
1046 reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK);
1047 reg |= WM8400_FLL_FRAC | factors.fratio;
1048 reg |= factors.freq_ref << WM8400_FLL_REF_FREQ_SHIFT;
1049 wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
1050
1051 wm8400_write(codec, WM8400_FLL_CONTROL_2, factors.k);
1052 wm8400_write(codec, WM8400_FLL_CONTROL_3, factors.n);
1053
1054 reg = wm8400_read(codec, WM8400_FLL_CONTROL_4);
1055 reg &= WM8400_FLL_OUTDIV_MASK;
1056 reg |= factors.outdiv;
1057 wm8400_write(codec, WM8400_FLL_CONTROL_4, reg);
1058
1059 return 0;
1060}
1061
934/* 1062/*
935 * Sets ADC and Voice DAC format. 1063 * Sets ADC and Voice DAC format.
936 */ 1064 */
@@ -1188,6 +1316,7 @@ static struct snd_soc_dai_ops wm8400_dai_ops = {
1188 .set_fmt = wm8400_set_dai_fmt, 1316 .set_fmt = wm8400_set_dai_fmt,
1189 .set_clkdiv = wm8400_set_dai_clkdiv, 1317 .set_clkdiv = wm8400_set_dai_clkdiv,
1190 .set_sysclk = wm8400_set_dai_sysclk, 1318 .set_sysclk = wm8400_set_dai_sysclk,
1319 .set_pll = wm8400_set_dai_pll,
1191}; 1320};
1192 1321
1193/* 1322/*