]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - sound/soc/codecs/wm8994.c
ASoC: multi-component - ASoC Multi-Component Support
[mv-sheeva.git] / sound / soc / codecs / wm8994.c
index 9da0724cd47aff5c26457e4ee9b6ef27a1c12c8d..7823f92413f3902d855dce07a3c258c2a6af1f28 100644 (file)
@@ -36,9 +36,6 @@
 #include "wm8994.h"
 #include "wm_hubs.h"
 
-static struct snd_soc_codec *wm8994_codec;
-struct snd_soc_codec_device soc_codec_dev_wm8994;
-
 struct fll_config {
        int src;
        int in;
@@ -62,10 +59,18 @@ static int wm8994_retune_mobile_base[] = {
 
 #define WM8994_REG_CACHE_SIZE  0x621
 
+struct wm8994_micdet {
+       struct snd_soc_jack *jack;
+       int det;
+       int shrt;
+};
+
 /* codec private data */
 struct wm8994_priv {
        struct wm_hubs_data hubs;
-       struct snd_soc_codec codec;
+       enum snd_soc_control_type control_type;
+       void *control_data;
+       struct snd_soc_codec *codec;
        u16 reg_cache[WM8994_REG_CACHE_SIZE + 1];
        int sysclk[2];
        int sysclk_rate[2];
@@ -87,6 +92,8 @@ struct wm8994_priv {
        int retune_mobile_cfg[WM8994_NUM_EQ];
        struct soc_enum retune_mobile_enum;
 
+       struct wm8994_micdet micdet[2];
+
        struct wm8994_pdata *pdata;
 };
 
@@ -1669,6 +1676,26 @@ static struct {
 
 static int wm8994_readable(unsigned int reg)
 {
+       switch (reg) {
+       case WM8994_GPIO_1:
+       case WM8994_GPIO_2:
+       case WM8994_GPIO_3:
+       case WM8994_GPIO_4:
+       case WM8994_GPIO_5:
+       case WM8994_GPIO_6:
+       case WM8994_GPIO_7:
+       case WM8994_GPIO_8:
+       case WM8994_GPIO_9:
+       case WM8994_GPIO_10:
+       case WM8994_GPIO_11:
+       case WM8994_INTERRUPT_STATUS_1:
+       case WM8994_INTERRUPT_STATUS_2:
+       case WM8994_INTERRUPT_RAW_STATUS_2:
+               return 1;
+       default:
+               break;
+       }
+
        if (reg >= ARRAY_SIZE(access_masks))
                return 0;
        return access_masks[reg].readable != 0;
@@ -1696,13 +1723,15 @@ static int wm8994_volatile(unsigned int reg)
 static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg,
        unsigned int value)
 {
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
 
        BUG_ON(reg > WM8994_MAX_REGISTER);
 
        if (!wm8994_volatile(reg))
                wm8994->reg_cache[reg] = value;
 
+       dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
        return wm8994_reg_write(codec->control_data, reg, value);
 }
 
@@ -1721,7 +1750,7 @@ static unsigned int wm8994_read(struct snd_soc_codec *codec,
 
 static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
 {
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        int rate;
        int reg1 = 0;
        int offset;
@@ -1762,6 +1791,11 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
                dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n",
                        aif + 1, rate);
        }
+
+       if (rate && rate < 3000000)
+               dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n",
+                        aif + 1, rate);
+
        wm8994->aifclk[aif] = rate;
 
        snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset,
@@ -1773,7 +1807,7 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
 
 static int configure_clock(struct snd_soc_codec *codec)
 {
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        int old, new;
 
        /* Bring up the AIF clocks first */
@@ -1866,11 +1900,9 @@ static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol,
        return snd_soc_put_volsw(kcontrol, ucontrol);
 }
 
-
-
 static void wm8994_set_drc(struct snd_soc_codec *codec, int drc)
 {
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        struct wm8994_pdata *pdata = wm8994->pdata;
        int base = wm8994_drc_base[drc];
        int cfg = wm8994->drc_cfg[drc];
@@ -1906,7 +1938,7 @@ static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol,
                               struct snd_ctl_elem_value *ucontrol)
 {
        struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
-       struct wm8994_priv *wm8994 = codec->private_data;       
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        struct wm8994_pdata *pdata = wm8994->pdata;
        int drc = wm8994_get_drc(kcontrol->id.name);
        int value = ucontrol->value.integer.value[0];
@@ -1928,7 +1960,7 @@ static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol,
                               struct snd_ctl_elem_value *ucontrol)
 {
        struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        int drc = wm8994_get_drc(kcontrol->id.name);
 
        ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc];
@@ -1938,7 +1970,7 @@ static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol,
 
 static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block)
 {
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        struct wm8994_pdata *pdata = wm8994->pdata;
        int base = wm8994_retune_mobile_base[block];
        int iface, best, best_val, save, i, cfg;
@@ -2009,7 +2041,7 @@ static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
                                         struct snd_ctl_elem_value *ucontrol)
 {
        struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
-       struct wm8994_priv *wm8994 = codec->private_data;       
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        struct wm8994_pdata *pdata = wm8994->pdata;
        int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
        int value = ucontrol->value.integer.value[0];
@@ -2031,7 +2063,7 @@ static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
                                         struct snd_ctl_elem_value *ucontrol)
 {
        struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 =snd_soc_codec_get_drvdata(codec);
        int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
 
        ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block];
@@ -2182,13 +2214,13 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec)
        /* Only support direct DAC->headphone paths */
        reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1);
        if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) {
-               dev_dbg(codec->dev, "HPL connected to output mixer\n");
+               dev_vdbg(codec->dev, "HPL connected to output mixer\n");
                enable = 0;
        }
 
        reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2);
        if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) {
-               dev_dbg(codec->dev, "HPR connected to output mixer\n");
+               dev_vdbg(codec->dev, "HPR connected to output mixer\n");
                enable = 0;
        }
 
@@ -2196,26 +2228,26 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec)
        reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING);
        switch (reg) {
        case WM8994_AIF2DACL_TO_DAC1L:
-               dev_dbg(codec->dev, "Class W source AIF2DAC\n");
+               dev_vdbg(codec->dev, "Class W source AIF2DAC\n");
                source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT;
                break;
        case WM8994_AIF1DAC2L_TO_DAC1L:
-               dev_dbg(codec->dev, "Class W source AIF1DAC2\n");
+               dev_vdbg(codec->dev, "Class W source AIF1DAC2\n");
                source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT;
                break;
        case WM8994_AIF1DAC1L_TO_DAC1L:
-               dev_dbg(codec->dev, "Class W source AIF1DAC1\n");
+               dev_vdbg(codec->dev, "Class W source AIF1DAC1\n");
                source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT;
                break;
        default:
-               dev_dbg(codec->dev, "DAC mixer setting: %x\n", reg);
+               dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg);
                enable = 0;
                break;
        }
 
        reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING);
        if (reg_r != reg) {
-               dev_dbg(codec->dev, "Left and right DAC mixers different\n");
+               dev_vdbg(codec->dev, "Left and right DAC mixers different\n");
                enable = 0;
        }
 
@@ -2326,6 +2358,20 @@ SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
                0, 1, 0),
 };
 
+static const struct snd_kcontrol_new aif1adc2l_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+               1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+               0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2r_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+               1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+               0, 1, 0),
+};
+
 static const struct snd_kcontrol_new aif2dac2l_mix[] = {
 SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
                5, 1, 0),
@@ -2457,6 +2503,7 @@ static const struct snd_kcontrol_new aif3adc_mux =
 static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = {
 SND_SOC_DAPM_INPUT("DMIC1DAT"),
 SND_SOC_DAPM_INPUT("DMIC2DAT"),
+SND_SOC_DAPM_INPUT("Clock"),
 
 SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
                    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
@@ -2491,6 +2538,11 @@ SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0,
 SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0,
                   aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)),
 
+SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0,
+                  aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)),
+SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0,
+                  aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)),
+
 SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0,
                   aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)),
 SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0,
@@ -2653,6 +2705,14 @@ static const struct snd_soc_dapm_route intercon[] = {
        { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" },
        { "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" },
 
+       { "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" },
+       { "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" },
+       { "AIF1ADC2L Mixer", "AIF2 Switch", "AIF2DACL" },
+
+       { "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" },
+       { "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" },
+       { "AIF1ADC2R Mixer", "AIF2 Switch", "AIF2DACR" },
+
        /* Pin level routing for AIF3 */
        { "AIF1DAC1L", NULL, "AIF1DAC Mux" },
        { "AIF1DAC1R", NULL, "AIF1DAC Mux" },
@@ -2777,9 +2837,18 @@ static int wm8994_get_fll_config(struct fll_div *fll,
 
        if (freq_in > 1000000) {
                fll->fll_fratio = 0;
-       } else {
+       } else if (freq_in > 256000) {
+               fll->fll_fratio = 1;
+               freq_in *= 2;
+       } else if (freq_in > 128000) {
+               fll->fll_fratio = 2;
+               freq_in *= 4;
+       } else if (freq_in > 64000) {
                fll->fll_fratio = 3;
                freq_in *= 8;
+       } else {
+               fll->fll_fratio = 4;
+               freq_in *= 16;
        }
        pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in);
 
@@ -2808,11 +2877,10 @@ static int wm8994_get_fll_config(struct fll_div *fll,
        return 0;
 }
 
-static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
+static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
                          unsigned int freq_in, unsigned int freq_out)
 {
-       struct snd_soc_codec *codec = dai->codec;
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        int reg_offset, ret;
        struct fll_div fll;
        u16 reg, aif1, aif2;
@@ -2836,6 +2904,21 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
                return -EINVAL;
        }
 
+       switch (src) {
+       case 0:
+               /* Allow no source specification when stopping */
+               if (freq_out)
+                       return -EINVAL;
+               break;
+       case WM8994_FLL_SRC_MCLK1:
+       case WM8994_FLL_SRC_MCLK2:
+       case WM8994_FLL_SRC_LRCLK:
+       case WM8994_FLL_SRC_BCLK:
+               break;
+       default:
+               return -EINVAL;
+       }
+
        /* Are we changing anything? */
        if (wm8994->fll[id].src == src &&
            wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out)
@@ -2876,8 +2959,10 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
                                    fll.n << WM8994_FLL1_N_SHIFT);
 
        snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset,
-                           WM8994_FLL1_REFCLK_DIV_MASK,
-                           fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT);
+                           WM8994_FLL1_REFCLK_DIV_MASK |
+                           WM8994_FLL1_REFCLK_SRC_MASK,
+                           (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) |
+                           (src - 1));
 
        /* Enable (with fractional mode if required) */
        if (freq_out) {
@@ -2892,6 +2977,7 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
 
        wm8994->fll[id].in = freq_in;
        wm8994->fll[id].out = freq_out;
+       wm8994->fll[id].src = src;
 
        /* Enable any gated AIF clocks */
        snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
@@ -2904,11 +2990,21 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
        return 0;
 }
 
+
+static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
+
+static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
+                         unsigned int freq_in, unsigned int freq_out)
+{
+       return _wm8994_set_fll(dai->codec, id, src, freq_in, freq_out);
+}
+
 static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
                int clk_id, unsigned int freq, int dir)
 {
        struct snd_soc_codec *codec = dai->codec;
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+       int i;
 
        switch (dai->id) {
        case 1:
@@ -2946,6 +3042,25 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
                dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id);
                break;
 
+       case WM8994_SYSCLK_OPCLK:
+               /* Special case - a division (times 10) is given and
+                * no effect on main clocking. 
+                */
+               if (freq) {
+                       for (i = 0; i < ARRAY_SIZE(opclk_divs); i++)
+                               if (opclk_divs[i] == freq)
+                                       break;
+                       if (i == ARRAY_SIZE(opclk_divs))
+                               return -EINVAL;
+                       snd_soc_update_bits(codec, WM8994_CLOCKING_2,
+                                           WM8994_OPCLK_DIV_MASK, i);
+                       snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+                                           WM8994_OPCLK_ENA, WM8994_OPCLK_ENA);
+               } else {
+                       snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+                                           WM8994_OPCLK_ENA, 0);
+               }
+
        default:
                return -EINVAL;
        }
@@ -3174,7 +3289,7 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream,
                            struct snd_soc_dai *dai)
 {
        struct snd_soc_codec *codec = dai->codec;
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        int aif1_reg;
        int bclk_reg;
        int lrclk_reg;
@@ -3338,6 +3453,36 @@ static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute)
        return 0;
 }
 
+static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       int reg, val, mask;
+
+       switch (codec_dai->id) {
+       case 1:
+               reg = WM8994_AIF1_MASTER_SLAVE;
+               mask = WM8994_AIF1_TRI;
+               break;
+       case 2:
+               reg = WM8994_AIF2_MASTER_SLAVE;
+               mask = WM8994_AIF2_TRI;
+               break;
+       case 3:
+               reg = WM8994_POWER_MANAGEMENT_6;
+               mask = WM8994_AIF3_TRI;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (tristate)
+               val = mask;
+       else
+               val = 0;
+
+       return snd_soc_update_bits(codec, reg, mask, reg);
+}
+
 #define WM8994_RATES SNDRV_PCM_RATE_8000_96000
 
 #define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
@@ -3349,6 +3494,7 @@ static struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
        .hw_params      = wm8994_hw_params,
        .digital_mute   = wm8994_aif_mute,
        .set_pll        = wm8994_set_fll,
+       .set_tristate   = wm8994_set_tristate,
 };
 
 static struct snd_soc_dai_ops wm8994_aif2_dai_ops = {
@@ -3357,12 +3503,16 @@ static struct snd_soc_dai_ops wm8994_aif2_dai_ops = {
        .hw_params      = wm8994_hw_params,
        .digital_mute   = wm8994_aif_mute,
        .set_pll        = wm8994_set_fll,
+       .set_tristate   = wm8994_set_tristate,
 };
 
-struct snd_soc_dai wm8994_dai[] = {
+static struct snd_soc_dai_ops wm8994_aif3_dai_ops = {
+       .set_tristate   = wm8994_set_tristate,
+};
+
+static struct snd_soc_dai_driver wm8994_dai[] = {
        {
-               .name = "WM8994 AIF1",
-               .id = 1,
+               .name = "wm8994-aif1",
                .playback = {
                        .stream_name = "AIF1 Playback",
                        .channels_min = 2,
@@ -3380,8 +3530,7 @@ struct snd_soc_dai wm8994_dai[] = {
                .ops = &wm8994_aif1_dai_ops,
        },
        {
-               .name = "WM8994 AIF2",
-               .id = 2,
+               .name = "wm8994-aif2",
                .playback = {
                        .stream_name = "AIF2 Playback",
                        .channels_min = 2,
@@ -3399,7 +3548,7 @@ struct snd_soc_dai wm8994_dai[] = {
                .ops = &wm8994_aif2_dai_ops,
        },
        {
-               .name = "WM8994 AIF3",
+               .name = "wm8994-aif3",
                .playback = {
                        .stream_name = "AIF3 Playback",
                        .channels_min = 2,
@@ -3414,22 +3563,20 @@ struct snd_soc_dai wm8994_dai[] = {
                        .rates = WM8994_RATES,
                        .formats = WM8994_FORMATS,
                },
+               .ops = &wm8994_aif3_dai_ops,
        }
 };
-EXPORT_SYMBOL_GPL(wm8994_dai);
 
 #ifdef CONFIG_PM
-static int wm8994_suspend(struct platform_device *pdev, pm_message_t state)
+static int wm8994_suspend(struct snd_soc_codec *codec, pm_message_t state)
 {
-       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-       struct snd_soc_codec *codec = socdev->card->codec;
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        int i, ret;
 
        for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
                memcpy(&wm8994->fll_suspend[i], &wm8994->fll[i],
                       sizeof(struct fll_config));
-               ret = wm8994_set_fll(&codec->dai[0], i + 1, 0, 0, 0);
+               ret = _wm8994_set_fll(codec, i + 1, 0, 0, 0);
                if (ret < 0)
                        dev_warn(codec->dev, "Failed to stop FLL%d: %d\n",
                                 i + 1, ret);
@@ -3440,11 +3587,9 @@ static int wm8994_suspend(struct platform_device *pdev, pm_message_t state)
        return 0;
 }
 
-static int wm8994_resume(struct platform_device *pdev)
+static int wm8994_resume(struct snd_soc_codec *codec)
 {
-       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-       struct snd_soc_codec *codec = socdev->card->codec;
-       struct wm8994_priv *wm8994 = codec->private_data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
        u16 *reg_cache = codec->reg_cache;
        int i, ret;
 
@@ -3469,7 +3614,10 @@ static int wm8994_resume(struct platform_device *pdev)
        wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 
        for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
-               ret = wm8994_set_fll(&codec->dai[0], i + 1,
+               if (!wm8994->fll_suspend[i].out)
+                       continue;
+
+               ret = _wm8994_set_fll(codec, i + 1,
                                     wm8994->fll_suspend[i].src,
                                     wm8994->fll_suspend[i].in,
                                     wm8994->fll_suspend[i].out);
@@ -3487,7 +3635,7 @@ static int wm8994_resume(struct platform_device *pdev)
 
 static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994)
 {
-       struct snd_soc_codec *codec = &wm8994->codec;
+       struct snd_soc_codec *codec = wm8994->codec;
        struct wm8994_pdata *pdata = wm8994->pdata;
        struct snd_kcontrol_new controls[] = {
                SOC_ENUM_EXT("AIF1.1 EQ Mode",
@@ -3545,16 +3693,16 @@ static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994)
        wm8994->retune_mobile_enum.max = wm8994->num_retune_mobile_texts;
        wm8994->retune_mobile_enum.texts = wm8994->retune_mobile_texts;
 
-       ret = snd_soc_add_controls(&wm8994->codec, controls,
+       ret = snd_soc_add_controls(wm8994->codec, controls,
                                   ARRAY_SIZE(controls));
        if (ret != 0)
-               dev_err(wm8994->codec.dev,
+               dev_err(wm8994->codec->dev,
                        "Failed to add ReTune Mobile controls: %d\n", ret);
 }
 
 static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
 {
-       struct snd_soc_codec *codec = &wm8994->codec;
+       struct snd_soc_codec *codec = wm8994->codec;
        struct wm8994_pdata *pdata = wm8994->pdata;
        int ret, i;
 
@@ -3586,7 +3734,7 @@ static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
                wm8994->drc_texts = kmalloc(sizeof(char *)
                                            * pdata->num_drc_cfgs, GFP_KERNEL);
                if (!wm8994->drc_texts) {
-                       dev_err(wm8994->codec.dev,
+                       dev_err(wm8994->codec->dev,
                                "Failed to allocate %d DRC config texts\n",
                                pdata->num_drc_cfgs);
                        return;
@@ -3598,10 +3746,10 @@ static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
                wm8994->drc_enum.max = pdata->num_drc_cfgs;
                wm8994->drc_enum.texts = wm8994->drc_texts;
 
-               ret = snd_soc_add_controls(&wm8994->codec, controls,
+               ret = snd_soc_add_controls(wm8994->codec, controls,
                                           ARRAY_SIZE(controls));
                if (ret != 0)
-                       dev_err(wm8994->codec.dev,
+                       dev_err(wm8994->codec->dev,
                                "Failed to add DRC mode controls: %d\n", ret);
 
                for (i = 0; i < WM8994_NUM_DRC; i++)
@@ -3614,103 +3762,114 @@ static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
        if (pdata->num_retune_mobile_cfgs)
                wm8994_handle_retune_mobile_pdata(wm8994);
        else
-               snd_soc_add_controls(&wm8994->codec, wm8994_eq_controls,
+               snd_soc_add_controls(wm8994->codec, wm8994_eq_controls,
                                     ARRAY_SIZE(wm8994_eq_controls));
 }
 
-static int wm8994_probe(struct platform_device *pdev)
+/**
+ * wm8994_mic_detect - Enable microphone detection via the WM8994 IRQ
+ *
+ * @codec:   WM8994 codec
+ * @jack:    jack to report detection events on
+ * @micbias: microphone bias to detect on
+ * @det:     value to report for presence detection
+ * @shrt:    value to report for short detection
+ *
+ * Enable microphone detection via IRQ on the WM8994.  If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8903 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * Configuration of detection levels is available via the micbias1_lvl
+ * and micbias2_lvl platform data members.
+ */
+int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+                     int micbias, int det, int shrt)
 {
-       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-       struct snd_soc_codec *codec;
-       int ret = 0;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+       struct wm8994_micdet *micdet;
+       int reg;
 
-       if (wm8994_codec == NULL) {
-               dev_err(&pdev->dev, "Codec device not registered\n");
-               return -ENODEV;
-       }
+       switch (micbias) {
+       case 1:
+               micdet = &wm8994->micdet[0];
+               break;
+       case 2:
+               micdet = &wm8994->micdet[1];
+               break;
+       default:
+               return -EINVAL;
+       }       
 
-       socdev->card->codec = wm8994_codec;
-       codec = wm8994_codec;
+       dev_dbg(codec->dev, "Configuring microphone detection on %d: %x %x\n",
+               micbias, det, shrt);
 
-       /* register pcms */
-       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
-       if (ret < 0) {
-               dev_err(codec->dev, "failed to create pcms: %d\n", ret);
-               return ret;
-       }
+       /* Store the configuration */
+       micdet->jack = jack;
+       micdet->det = det;
+       micdet->shrt = shrt;
 
-       wm8994_handle_pdata(codec->private_data);
+       /* If either of the jacks is set up then enable detection */
+       if (wm8994->micdet[0].jack || wm8994->micdet[1].jack)
+               reg = WM8994_MICD_ENA;
+       else 
+               reg = 0;
 
-       wm_hubs_add_analogue_controls(codec);
-       snd_soc_add_controls(codec, wm8994_snd_controls,
-                            ARRAY_SIZE(wm8994_snd_controls));
-       snd_soc_dapm_new_controls(codec, wm8994_dapm_widgets,
-                                 ARRAY_SIZE(wm8994_dapm_widgets));
-       wm_hubs_add_analogue_routes(codec, 0, 0);
-       snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+       snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, reg);
 
        return 0;
 }
+EXPORT_SYMBOL_GPL(wm8994_mic_detect);
 
-static int wm8994_remove(struct platform_device *pdev)
+static irqreturn_t wm8994_mic_irq(int irq, void *data)
 {
-       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct wm8994_priv *priv = data;
+       struct snd_soc_codec *codec = priv->codec;
+       int reg;
+       int report;
 
-       snd_soc_free_pcms(socdev);
-       snd_soc_dapm_free(socdev);
+       reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2);
+       if (reg < 0) {
+               dev_err(codec->dev, "Failed to read microphone status: %d\n",
+                       reg);
+               return IRQ_HANDLED;
+       }
 
-       return 0;
+       dev_dbg(codec->dev, "Microphone status: %x\n", reg);
+
+       report = 0;
+       if (reg & WM8994_MIC1_DET_STS)
+               report |= priv->micdet[0].det;
+       if (reg & WM8994_MIC1_SHRT_STS)
+               report |= priv->micdet[0].shrt;
+       snd_soc_jack_report(priv->micdet[0].jack, report,
+                           priv->micdet[0].det | priv->micdet[0].shrt);
+
+       report = 0;
+       if (reg & WM8994_MIC2_DET_STS)
+               report |= priv->micdet[1].det;
+       if (reg & WM8994_MIC2_SHRT_STS)
+               report |= priv->micdet[1].shrt;
+       snd_soc_jack_report(priv->micdet[1].jack, report,
+                           priv->micdet[1].det | priv->micdet[1].shrt);
+
+       return IRQ_HANDLED;
 }
 
-struct snd_soc_codec_device soc_codec_dev_wm8994 = {
-       .probe =        wm8994_probe,
-       .remove =       wm8994_remove,
-       .suspend =      wm8994_suspend,
-       .resume =       wm8994_resume,
-};
-EXPORT_SYMBOL_GPL(soc_codec_dev_wm8994);
-
-static int wm8994_codec_probe(struct platform_device *pdev)
+static int wm8994_codec_probe(struct snd_soc_codec *codec)
 {
-       int ret;
        struct wm8994_priv *wm8994;
-       struct snd_soc_codec *codec;
-       int i;
-       u16 rev;
+       int ret, i, rev;
 
-       if (wm8994_codec) {
-               dev_err(&pdev->dev, "Another WM8994 is registered\n");
-               return -EINVAL;
-       }
+       codec->control_data = dev_get_drvdata(codec->dev->parent);
 
        wm8994 = kzalloc(sizeof(struct wm8994_priv), GFP_KERNEL);
-       if (!wm8994) {
-               dev_err(&pdev->dev, "Failed to allocate private data\n");
+       if (wm8994 == NULL)
                return -ENOMEM;
-       }
+       snd_soc_codec_set_drvdata(codec, wm8994);
 
-       codec = &wm8994->codec;
-
-       mutex_init(&codec->mutex);
-       INIT_LIST_HEAD(&codec->dapm_widgets);
-       INIT_LIST_HEAD(&codec->dapm_paths);
-
-       codec->private_data = wm8994;
-       codec->control_data = dev_get_drvdata(pdev->dev.parent);
-       codec->name = "WM8994";
-       codec->owner = THIS_MODULE;
-       codec->read = wm8994_read;
-       codec->write = wm8994_write;
-       codec->readable_register = wm8994_readable;
-       codec->bias_level = SND_SOC_BIAS_OFF;
-       codec->set_bias_level = wm8994_set_bias_level;
-       codec->dai = &wm8994_dai[0];
-       codec->num_dai = 3;
-       codec->reg_cache_size = WM8994_MAX_REGISTER;
-       codec->reg_cache = &wm8994->reg_cache;
-       codec->dev = &pdev->dev;
-
-       wm8994->pdata = pdev->dev.parent->platform_data;
+       wm8994->pdata = dev_get_platdata(codec->dev->parent);
+       wm8994->codec = codec;
 
        /* Fill the cache with physical values we inherited; don't reset */
        ret = wm8994_bulk_read(codec->control_data, 0,
@@ -3743,6 +3902,30 @@ static int wm8994_codec_probe(struct platform_device *pdev)
                break;
        }
 
+       ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_DET,
+                                wm8994_mic_irq, "Mic 1 detect", wm8994);
+       if (ret != 0)
+               dev_warn(codec->dev,
+                        "Failed to request Mic1 detect IRQ: %d\n", ret);
+
+       ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT,
+                                wm8994_mic_irq, "Mic 1 short", wm8994);
+       if (ret != 0)
+               dev_warn(codec->dev,
+                        "Failed to request Mic1 short IRQ: %d\n", ret);
+
+       ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_DET,
+                                wm8994_mic_irq, "Mic 2 detect", wm8994);
+       if (ret != 0)
+               dev_warn(codec->dev,
+                        "Failed to request Mic2 detect IRQ: %d\n", ret);
+
+       ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT,
+                                wm8994_mic_irq, "Mic 2 short", wm8994);
+       if (ret != 0)
+               dev_warn(codec->dev,
+                        "Failed to request Mic2 short IRQ: %d\n", ret);
+
        /* Remember if AIFnLRCLK is configured as a GPIO.  This should be
         * configured on init - if a system wants to do this dynamically
         * at runtime we can deal with that then.
@@ -3750,7 +3933,7 @@ static int wm8994_codec_probe(struct platform_device *pdev)
        ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_1);
        if (ret < 0) {
                dev_err(codec->dev, "Failed to read GPIO1 state: %d\n", ret);
-               goto err;
+               goto err_irq;
        }
        if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
                wm8994->lrclk_shared[0] = 1;
@@ -3762,7 +3945,7 @@ static int wm8994_codec_probe(struct platform_device *pdev)
        ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_6);
        if (ret < 0) {
                dev_err(codec->dev, "Failed to read GPIO6 state: %d\n", ret);
-               goto err;
+               goto err_irq;
        }
        if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
                wm8994->lrclk_shared[1] = 1;
@@ -3771,13 +3954,8 @@ static int wm8994_codec_probe(struct platform_device *pdev)
                wm8994->lrclk_shared[1] = 0;
        }
 
-       for (i = 0; i < ARRAY_SIZE(wm8994_dai); i++)
-               wm8994_dai[i].dev = codec->dev;
-
        wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 
-       wm8994_codec = codec;
-
        /* Latch volume updates (right only; we always do left then right). */
        snd_soc_update_bits(codec, WM8994_AIF1_DAC1_RIGHT_VOLUME,
                            WM8994_AIF1DAC1_VU, WM8994_AIF1DAC1_VU);
@@ -3807,52 +3985,79 @@ static int wm8994_codec_probe(struct platform_device *pdev)
                            1 << WM8994_AIF2DAC_3D_GAIN_SHIFT,
                            1 << WM8994_AIF2DAC_3D_GAIN_SHIFT);
 
-       wm8994_update_class_w(codec);
+       /* Unconditionally enable AIF1 ADC TDM mode; it only affects
+        * behaviour on idle TDM clock cycles. */
+       snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
+                           WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
 
-       ret = snd_soc_register_codec(codec);
-       if (ret != 0) {
-               dev_err(codec->dev, "Failed to register codec: %d\n", ret);
-               goto err;
-       }
+       wm8994_update_class_w(codec);
 
-       ret = snd_soc_register_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai));
-       if (ret != 0) {
-               dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
-               goto err_codec;
-       }
+       wm8994_handle_pdata(wm8994);
 
-       platform_set_drvdata(pdev, wm8994);
+       wm_hubs_add_analogue_controls(codec);
+       snd_soc_add_controls(codec, wm8994_snd_controls,
+                            ARRAY_SIZE(wm8994_snd_controls));
+       snd_soc_dapm_new_controls(codec, wm8994_dapm_widgets,
+                                 ARRAY_SIZE(wm8994_dapm_widgets));
+       wm_hubs_add_analogue_routes(codec, 0, 0);
+       snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
 
        return 0;
 
-err_codec:
-       snd_soc_unregister_codec(codec);
+err_irq:
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994);
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994);
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994);
 err:
        kfree(wm8994);
        return ret;
 }
 
-static int __devexit wm8994_codec_remove(struct platform_device *pdev)
+static int  wm8994_codec_remove(struct snd_soc_codec *codec)
 {
-       struct wm8994_priv *wm8994 = platform_get_drvdata(pdev);
-       struct snd_soc_codec *codec = &wm8994->codec;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
 
        wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
-       snd_soc_unregister_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai));
-       snd_soc_unregister_codec(&wm8994->codec);
+
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994);
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994);
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
+       wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994);
        kfree(wm8994);
-       wm8994_codec = NULL;
 
        return 0;
 }
 
+static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
+       .probe =        wm8994_codec_probe,
+       .remove =       wm8994_codec_remove,
+       .suspend =      wm8994_suspend,
+       .resume =       wm8994_resume,
+       .read = wm8994_read,
+       .write = wm8994_write,
+       .set_bias_level = wm8994_set_bias_level,
+};
+
+static int __devinit wm8994_probe(struct platform_device *pdev)
+{
+       return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
+                       wm8994_dai, ARRAY_SIZE(wm8994_dai));
+}
+
+static int __devexit wm8994_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_codec(&pdev->dev);
+       return 0;
+}
+
 static struct platform_driver wm8994_codec_driver = {
        .driver = {
                   .name = "wm8994-codec",
                   .owner = THIS_MODULE,
                   },
-       .probe = wm8994_codec_probe,
-       .remove = __devexit_p(wm8994_codec_remove),
+       .probe = wm8994_probe,
+       .remove = __devexit_p(wm8994_remove),
 };
 
 static __init int wm8994_init(void)