Add sysclk auto mode. When it's sysclk auto mode, if the MCLK is
available for clock configure, using MCLK to provide sysclk directly,
otherwise, search a available pll out frequcncy and set pll.

TX and RX share the same sysclk and bclk divider register, and have
different DAC/ADC divier register. When playback and capture
simultaneously, the second stream should not set pll, should not
change the sysclk and bclk, just need set DAC/ADC divider to support
different TX and RX sample rate.

Signed-off-by: Zidan Wang <zidan.w...@freescale.com>
---
 sound/soc/codecs/wm8960.c | 256 ++++++++++++++++++++++++++++++++++++++--------
 sound/soc/codecs/wm8960.h |   1 +
 2 files changed, 214 insertions(+), 43 deletions(-)

diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
index 94c5c46..e170bdf 100644
--- a/sound/soc/codecs/wm8960.c
+++ b/sound/soc/codecs/wm8960.c
@@ -48,6 +48,9 @@
 #define WM8960_DISOP     0x40
 #define WM8960_DRES_MASK 0x30
 
+static bool is_pll_freq_available(unsigned int source, unsigned int target);
+static int wm8960_set_pll(struct snd_soc_dai *codec_dai,
+               unsigned int freq_in, unsigned int freq_out);
 /*
  * wm8960 register cache
  * We can't read the WM8960 register space when we are
@@ -126,9 +129,12 @@ struct wm8960_priv {
        struct snd_soc_dapm_widget *rout1;
        struct snd_soc_dapm_widget *out3;
        bool deemph;
-       int playback_fs;
-       int bclk;
        int sysclk;
+       int clk_id;
+       int freq_in;
+       int fs[2];
+       int dac_div[2];
+       bool is_stream_in_use[2];
        struct wm8960_data pdata;
 };
 
@@ -164,8 +170,8 @@ static int wm8960_set_deemph(struct snd_soc_codec *codec)
        if (wm8960->deemph) {
                best = 1;
                for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
-                       if (abs(deemph_settings[i] - wm8960->playback_fs) <
-                           abs(deemph_settings[best] - wm8960->playback_fs))
+                       if (abs(deemph_settings[i] - wm8960->fs[1]) <
+                           abs(deemph_settings[best] - wm8960->fs[1]))
                                best = i;
                }
 
@@ -565,6 +571,9 @@ static struct {
        {  8000, 5 },
 };
 
+/* -1 for reserved value */
+static const int sysclk_divs[] = { 1, -1, 2, -1 };
+
 /* Multiply 256 for internal 256 div */
 static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 };
 
@@ -574,61 +583,166 @@ static const int bclk_divs[] = {
        120, 160, 220, 240, 320, 320, 320
 };
 
-static void wm8960_configure_clocking(struct snd_soc_codec *codec,
-               bool tx, int lrclk)
+static int wm8960_configure_clocking(struct snd_pcm_substream *substream,
+               struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
 {
+       struct snd_soc_codec *codec = dai->codec;
        struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       unsigned int sample_rate = params_rate(params);
+       unsigned int channels = params_channels(params);
+       unsigned int sysclk, bclk, freq_out, freq_in;
+       bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
        u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
        u16 iface2 = snd_soc_read(codec, WM8960_IFACE2);
-       u32 sysclk;
-       int i, j;
+       int i, j, k;
 
        if (!(iface1 & (1<<6))) {
                dev_dbg(codec->dev,
                        "Codec is slave mode, no need to configure clock\n");
-               return;
+               return 0;
+       }
+
+       /*
+        * playback and capture using the same registers of bclk and sysclk
+        * div, and different registers for dac/adc div. If playback and capture
+        * simultaneously, the second should not set pll, should not change
+        * the sysclk and bclk, only dac/adc div can be set to support different
+        * frame clock.
+        */
+       if (wm8960->is_stream_in_use[!tx]) {
+               /*
+                * 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, wm8960->dac_div[!tx] << 3);
+                       return 0;
+               }
+
+               /*
+                * If the different TX and RX sample rate can be support,
+                * setting corresponding DAC or ADC divider.
+                */
+               for (i = 0; i < ARRAY_SIZE(dac_divs); ++i) {
+                       if (wm8960->fs[!tx] * dac_divs[wm8960->dac_div[!tx]]
+                                       == wm8960->fs[tx] * dac_divs[i]) {
+                               if (tx)
+                                       snd_soc_update_bits(codec,
+                                               WM8960_CLOCK1,
+                                               0x7 << 3, dac_divs[i] << 3);
+                               else
+                                       snd_soc_update_bits(codec,
+                                               WM8960_CLOCK1,
+                                               0x7 << 6, dac_divs[i] << 6);
+                               wm8960->dac_div[tx] = dac_divs[i];
+                               return 0;
+                       }
+               }
+               if (i == ARRAY_SIZE(dac_divs)) {
+                       dev_err(codec->dev,
+                               "can't support TX=%d RX=%d\n simultaneously",
+                               wm8960->fs[1], wm8960->fs[0]);
+                       return -EINVAL;
+               }
+       }
+
+       if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) {
+               dev_err(codec->dev, "No MCLK configured\n");
+               return -EINVAL;
        }
 
-       if (!wm8960->sysclk) {
-               dev_dbg(codec->dev, "No SYSCLK configured\n");
-               return;
+       freq_in = wm8960->freq_in;
+
+       bclk = snd_soc_params_to_bclk(params);
+       if (channels == 1)
+               bclk *= 2;
+
+       /*
+        * If it's sysclk auto mode, check if the MCLK can provide sysclk or
+        * not. If MCLK can provide sysclk, using MCLK to provide sysclk
+        * directly. Otherwise, auto select a available pll out frequency
+        * and set PLL.
+        */
+       if (wm8960->clk_id == WM8960_SYSCLK_AUTO) {
+               /* disable the PLL and using MCLK to provide sysclk */
+               wm8960_set_pll(dai, 0, 0);
+               freq_out = freq_in;
+       } else if (wm8960->sysclk)
+               freq_out = wm8960->sysclk;
+       else {
+               dev_err(codec->dev, "No SYSCLK configured\n");
+               return -EINVAL;
        }
 
-       if (!wm8960->bclk || !lrclk) {
-               dev_dbg(codec->dev, "No audio clocks configured\n");
-               return;
+       /* check if the sysclk frequency is available. */
+       for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+               if (sysclk_divs[i] == -1)
+                       continue;
+               sysclk = freq_out / sysclk_divs[i];
+               for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+                       if (sysclk == dac_divs[j] * sample_rate) {
+                               for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k)
+                                       if (sysclk == bclk * bclk_divs[k] / 10)
+                                               break;
+                               if (k != ARRAY_SIZE(bclk_divs))
+                                       break;
+                       }
+               }
+               if (j != ARRAY_SIZE(dac_divs))
+                       break;
        }
 
-       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)
+       if (i != ARRAY_SIZE(sysclk_divs))
+               goto configure_clock;
+       else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
+               dev_err(codec->dev, "failed to configure clock\n");
+               return -EINVAL;
+       }
+       /* get a available pll out frequency and set pll */
+       for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+               if (sysclk_divs[i] == -1)
+                       continue;
+               for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+                       sysclk = sample_rate * dac_divs[j];
+                       freq_out = sysclk * sysclk_divs[i];
+
+                       for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+                               if (sysclk == bclk * bclk_divs[k] / 10 &&
+                                   is_pll_freq_available(freq_in, freq_out)) {
+                                       wm8960_set_pll(dai, freq_in, freq_out);
                                        break;
+                               } else
+                                       continue;
                        }
-                       if(j != ARRAY_SIZE(bclk_divs))
+                       if (k != ARRAY_SIZE(bclk_divs))
                                break;
                }
+               if (j != ARRAY_SIZE(dac_divs))
+                       break;
        }
 
-       if (i == ARRAY_SIZE(dac_divs)) {
-               dev_err(codec->dev, "Unsupported sysclk %d\n", wm8960->sysclk);
-               return;
+       if (i == ARRAY_SIZE(sysclk_divs)) {
+               dev_err(codec->dev, "failed to configure clock\n");
+               return -EINVAL;
        }
 
-       /*
-        * 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);
+configure_clock:
+       snd_soc_update_bits(codec, WM8960_CLOCK1, 3 << 1, i << 1);
+
+       if (iface2 & (1 << 6))
+               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, j << 3);
        else if (tx)
-               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, i << 3);
+               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, j << 3);
        else if (!tx)
-               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, i << 6);
+               snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, j << 6);
 
        /* configure bit clock */
-       snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, j);
+       snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, k);
+
+       wm8960->dac_div[tx] = j;
+
+       return 0;
 }
 
 static int wm8960_hw_params(struct snd_pcm_substream *substream,
@@ -641,10 +755,6 @@ static int wm8960_hw_params(struct snd_pcm_substream 
*substream,
        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:
@@ -667,11 +777,11 @@ static int wm8960_hw_params(struct snd_pcm_substream 
*substream,
                return -EINVAL;
        }
 
+       wm8960->fs[tx] = params_rate(params);
        /* Update filters for the new rate */
-       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-               wm8960->playback_fs = params_rate(params);
+       if (tx)
                wm8960_set_deemph(codec);
-       } else {
+       else {
                for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
                        if (alc_rates[i].rate == params_rate(params))
                                snd_soc_update_bits(codec,
@@ -682,7 +792,27 @@ 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));
+       wm8960->is_stream_in_use[tx] = true;
+
+       return wm8960_configure_clocking(substream, params, dai);
+}
+
+static int wm8960_hw_free(struct snd_pcm_substream *substream,
+               struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       u16 clock1 = snd_soc_read(codec, WM8960_CLOCK1);
+       bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+       wm8960->is_stream_in_use[tx] = false;
+       /*
+        * If it's sysclk auto mode, no stream in use, and the pll is enabled,
+        * disable the pll
+        */
+       if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (clock1 & 0x1) &&
+           !wm8960->is_stream_in_use[!tx])
+               wm8960_set_pll(dai, 0, 0);
 
        return 0;
 }
@@ -892,6 +1022,28 @@ struct _pll_div {
        u32 k:24;
 };
 
+static bool is_pll_freq_available(unsigned int source, unsigned int target)
+{
+       unsigned int Ndiv;
+
+       if (source == 0 || target == 0)
+               return false;
+
+       /* Scale up target to PLL operating frequency */
+       target *= 4;
+       Ndiv = target / source;
+
+       if (Ndiv < 6) {
+               source >>= 1;
+               Ndiv = target / source;
+       }
+
+       if ((Ndiv < 6) || (Ndiv > 12))
+               return false;
+
+       return true;
+}
+
 /* The size in bits of the pll divide multiplied by 10
  * to allow rounding later */
 #define FIXED_PLL_SIZE ((1 << 24) * 10)
@@ -943,8 +1095,8 @@ static int pll_factors(unsigned int source, unsigned int 
target,
        return 0;
 }
 
-static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
-               int source, unsigned int freq_in, unsigned int freq_out)
+static int wm8960_set_pll(struct snd_soc_dai *codec_dai,
+               unsigned int freq_in, unsigned int freq_out)
 {
        struct snd_soc_codec *codec = codec_dai->codec;
        u16 reg;
@@ -986,6 +1138,20 @@ static int wm8960_set_dai_pll(struct snd_soc_dai 
*codec_dai, int pll_id,
        return 0;
 }
 
+static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+               int source, unsigned int freq_in, unsigned int freq_out)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+       wm8960->freq_in = freq_in;
+
+       if (wm8960->clk_id == WM8960_SYSCLK_AUTO)
+               return 0;
+
+       return wm8960_set_pll(codec_dai, freq_in, freq_out);
+}
+
 static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
                int div_id, int div)
 {
@@ -1043,11 +1209,14 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai 
*dai, int clk_id,
                snd_soc_update_bits(codec, WM8960_CLOCK1,
                                        0x1, WM8960_SYSCLK_PLL);
                break;
+       case WM8960_SYSCLK_AUTO:
+               break;
        default:
                return -EINVAL;
        }
 
        wm8960->sysclk = freq;
+       wm8960->clk_id = clk_id;
 
        return 0;
 }
@@ -1060,6 +1229,7 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, 
int clk_id,
 
 static const struct snd_soc_dai_ops wm8960_dai_ops = {
        .hw_params = wm8960_hw_params,
+       .hw_free = wm8960_hw_free,
        .digital_mute = wm8960_mute,
        .set_fmt = wm8960_set_dai_fmt,
        .set_clkdiv = wm8960_set_dai_clkdiv,
diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h
index 2d8163d..ab3220d 100644
--- a/sound/soc/codecs/wm8960.h
+++ b/sound/soc/codecs/wm8960.h
@@ -82,6 +82,7 @@
 
 #define WM8960_SYSCLK_MCLK             (0 << 0)
 #define WM8960_SYSCLK_PLL              (1 << 0)
+#define WM8960_SYSCLK_AUTO             (2 << 0)
 
 #define WM8960_DAC_DIV_1               (0 << 3)
 #define WM8960_DAC_DIV_1_5             (1 << 3)
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to