Merge remote-tracking branch 'asoc/topic/rt5670' into asoc-next
[deliverable/linux.git] / sound / soc / codecs / wm8960.c
index af095b64f8804f9a45dcfe84562045d9f6c7a955..94c5c4681ce5c0c83accd7ab8682de448c9e7b07 100644 (file)
@@ -127,6 +127,8 @@ struct wm8960_priv {
        struct snd_soc_dapm_widget *out3;
        bool deemph;
        int playback_fs;
+       int bclk;
+       int sysclk;
        struct wm8960_data pdata;
 };
 
@@ -245,7 +247,7 @@ SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
 SOC_ENUM("ADC Polarity", wm8960_enum[0]),
 SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
 
-SOC_ENUM("DAC Polarity", wm8960_enum[2]),
+SOC_ENUM("DAC Polarity", wm8960_enum[1]),
 SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
                    wm8960_get_deemph, wm8960_put_deemph),
 
@@ -563,6 +565,72 @@ static struct {
        {  8000, 5 },
 };
 
+/* Multiply 256 for internal 256 div */
+static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 };
+
+/* Multiply 10 to eliminate decimials */
+static const int bclk_divs[] = {
+       10, 15, 20, 30, 40, 55, 60, 80, 110,
+       120, 160, 220, 240, 320, 320, 320
+};
+
+static void wm8960_configure_clocking(struct snd_soc_codec *codec,
+               bool tx, int lrclk)
+{
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
+       u16 iface2 = snd_soc_read(codec, WM8960_IFACE2);
+       u32 sysclk;
+       int i, j;
+
+       if (!(iface1 & (1<<6))) {
+               dev_dbg(codec->dev,
+                       "Codec is slave mode, no need to configure clock\n");
+               return;
+       }
+
+       if (!wm8960->sysclk) {
+               dev_dbg(codec->dev, "No SYSCLK configured\n");
+               return;
+       }
+
+       if (!wm8960->bclk || !lrclk) {
+               dev_dbg(codec->dev, "No audio clocks configured\n");
+               return;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(dac_divs); ++i) {
+               if (wm8960->sysclk == lrclk * dac_divs[i]) {
+                       for (j = 0; j < ARRAY_SIZE(bclk_divs); ++j) {
+                               sysclk = wm8960->bclk * bclk_divs[j] / 10;
+                               if (wm8960->sysclk == sysclk)
+                                       break;
+                       }
+                       if(j != ARRAY_SIZE(bclk_divs))
+                               break;
+               }
+       }
+
+       if (i == ARRAY_SIZE(dac_divs)) {
+               dev_err(codec->dev, "Unsupported sysclk %d\n", wm8960->sysclk);
+               return;
+       }
+
+       /*
+        * configure frame clock. If ADCLRC configure as GPIO pin, DACLRC
+        * pin is used as a frame clock for ADCs and DACs.
+        */
+       if (iface2 & (1<<6))
+               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, i << 3);
+       else if (tx)
+               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, i << 3);
+       else if (!tx)
+               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, i << 6);
+
+       /* configure bit clock */
+       snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, j);
+}
+
 static int wm8960_hw_params(struct snd_pcm_substream *substream,
                            struct snd_pcm_hw_params *params,
                            struct snd_soc_dai *dai)
@@ -570,8 +638,13 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
        struct snd_soc_codec *codec = dai->codec;
        struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
        u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
+       bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
        int i;
 
+       wm8960->bclk = snd_soc_params_to_bclk(params);
+       if (params_channels(params) == 1)
+               wm8960->bclk *= 2;
+
        /* bit size */
        switch (params_width(params)) {
        case 16:
@@ -582,6 +655,12 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
        case 24:
                iface |= 0x0008;
                break;
+       case 32:
+               /* right justify mode does not support 32 word length */
+               if ((iface & 0x3) != 0) {
+                       iface |= 0x000c;
+                       break;
+               }
        default:
                dev_err(codec->dev, "unsupported width %d\n",
                        params_width(params));
@@ -602,6 +681,9 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
 
        /* set iface */
        snd_soc_write(codec, WM8960_IFACE1, iface);
+
+       wm8960_configure_clocking(codec, tx, params_rate(params));
+
        return 0;
 }
 
@@ -946,11 +1028,35 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec,
        return wm8960->set_bias_level(codec, level);
 }
 
+static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+                                       unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+       switch (clk_id) {
+       case WM8960_SYSCLK_MCLK:
+               snd_soc_update_bits(codec, WM8960_CLOCK1,
+                                       0x1, WM8960_SYSCLK_MCLK);
+               break;
+       case WM8960_SYSCLK_PLL:
+               snd_soc_update_bits(codec, WM8960_CLOCK1,
+                                       0x1, WM8960_SYSCLK_PLL);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       wm8960->sysclk = freq;
+
+       return 0;
+}
+
 #define WM8960_RATES SNDRV_PCM_RATE_8000_48000
 
 #define WM8960_FORMATS \
        (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
-       SNDRV_PCM_FMTBIT_S24_LE)
+       SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
 
 static const struct snd_soc_dai_ops wm8960_dai_ops = {
        .hw_params = wm8960_hw_params,
@@ -958,6 +1064,7 @@ static const struct snd_soc_dai_ops wm8960_dai_ops = {
        .set_fmt = wm8960_set_dai_fmt,
        .set_clkdiv = wm8960_set_dai_clkdiv,
        .set_pll = wm8960_set_dai_pll,
+       .set_sysclk = wm8960_set_dai_sysclk,
 };
 
 static struct snd_soc_dai_driver wm8960_dai = {
This page took 0.032181 seconds and 5 git commands to generate.