#include "wm8940.h"
 
 struct wm8940_priv {
-       unsigned int sysclk;
+       unsigned int mclk;
+       unsigned int fs;
+
        struct regmap *regmap;
 };
 
        return 0;
 }
 
+static int wm8940_update_clocks(struct snd_soc_dai *dai);
 static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
                                struct snd_pcm_hw_params *params,
                                struct snd_soc_dai *dai)
 {
        struct snd_soc_component *component = dai->component;
+       struct wm8940_priv *priv = snd_soc_component_get_drvdata(component);
        u16 iface = snd_soc_component_read(component, WM8940_IFACE) & 0xFD9F;
        u16 addcntrl = snd_soc_component_read(component, WM8940_ADDCNTRL) & 0xFFF1;
        u16 companding =  snd_soc_component_read(component,
                                                WM8940_COMPANDINGCTL) & 0xFFDF;
        int ret;
 
+       priv->fs = params_rate(params);
+       ret = wm8940_update_clocks(dai);
+       if (ret)
+               return ret;
+
        /* LoutR control */
        if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
            && params_channels(params) == 2)
        return 0;
 }
 
-static int wm8940_set_dai_sysclk(struct snd_soc_dai *codec_dai,
-                                int clk_id, unsigned int freq, int dir)
-{
-       struct snd_soc_component *component = codec_dai->component;
-       struct wm8940_priv *wm8940 = snd_soc_component_get_drvdata(component);
-
-       switch (freq) {
-       case 11289600:
-       case 12000000:
-       case 12288000:
-       case 16934400:
-       case 18432000:
-               wm8940->sysclk = freq;
-               return 0;
-       }
-       return -EINVAL;
-}
-
 static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
                                 int div_id, int div)
 {
        return ret;
 }
 
+static unsigned int wm8940_get_mclkdiv(unsigned int f_in, unsigned int f_out,
+                                      int *mclkdiv)
+{
+       unsigned int ratio = 2 * f_in / f_out;
+
+       if (ratio <= 2) {
+               *mclkdiv = WM8940_MCLKDIV_1;
+               ratio = 2;
+       } else if (ratio == 3) {
+               *mclkdiv = WM8940_MCLKDIV_1_5;
+       } else if (ratio == 4) {
+               *mclkdiv = WM8940_MCLKDIV_2;
+       } else if (ratio <= 6) {
+               *mclkdiv = WM8940_MCLKDIV_3;
+               ratio = 6;
+       } else if (ratio <= 8) {
+               *mclkdiv = WM8940_MCLKDIV_4;
+               ratio = 8;
+       } else if (ratio <= 12) {
+               *mclkdiv = WM8940_MCLKDIV_6;
+               ratio = 12;
+       } else if (ratio <= 16) {
+               *mclkdiv = WM8940_MCLKDIV_8;
+               ratio = 16;
+       } else {
+               *mclkdiv = WM8940_MCLKDIV_12;
+               ratio = 24;
+       }
+
+       return f_out * ratio / 2;
+}
+
+static int wm8940_update_clocks(struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *codec = dai->component;
+       struct wm8940_priv *priv = snd_soc_component_get_drvdata(codec);
+       unsigned int fs256;
+       unsigned int fpll = 0;
+       unsigned int f;
+       int mclkdiv;
+
+       if (!priv->mclk || !priv->fs)
+               return 0;
+
+       fs256 = 256 * priv->fs;
+
+       f = wm8940_get_mclkdiv(priv->mclk, fs256, &mclkdiv);
+       if (f != priv->mclk) {
+               /* The PLL performs best around 90MHz */
+               fpll = wm8940_get_mclkdiv(22500000, fs256, &mclkdiv);
+       }
+
+       wm8940_set_dai_pll(dai, 0, 0, priv->mclk, fpll);
+       wm8940_set_dai_clkdiv(dai, WM8940_MCLKDIV, mclkdiv);
+
+       return 0;
+}
+
+static int wm8940_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+                                unsigned int freq, int dir)
+{
+       struct snd_soc_component *codec = dai->component;
+       struct wm8940_priv *priv = snd_soc_component_get_drvdata(codec);
+
+       if (dir != SND_SOC_CLOCK_IN)
+               return -EINVAL;
+
+       priv->mclk = freq;
+
+       return wm8940_update_clocks(dai);
+}
+
 #define WM8940_RATES SNDRV_PCM_RATE_8000_48000
 
 #define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 |                          \