sound: Add exynos4/wm1811 machine driver
authorSylwester Nawrocki <s.nawrocki@samsung.com>
Mon, 4 Mar 2013 16:49:37 +0000 (17:49 +0100)
committerChanho Park <chanho61.park@samsung.com>
Tue, 18 Nov 2014 02:42:52 +0000 (11:42 +0900)
TODO:
 - complete bindings documentation,
 - enable remaining audio interfaces (Voice, Bluetooth,
   Secondary DAI).

Signed-off-by: KwangHui Cho <kwanghui.cho@samsung.com>
Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
Documentation/devicetree/bindings/sound/samsung-exynos4-wm1811.txt [new file with mode: 0644]
sound/soc/samsung/Kconfig
sound/soc/samsung/Makefile
sound/soc/samsung/exynos4_wm1811.c [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/sound/samsung-exynos4-wm1811.txt b/Documentation/devicetree/bindings/sound/samsung-exynos4-wm1811.txt
new file mode 100644 (file)
index 0000000..a4e0e10
--- /dev/null
@@ -0,0 +1,46 @@
+Samsung Exynos4 audio complex
+
+Required properties:
+
+- compatible : "samsung,exynos4-wm1811";
+- samsung,model : the user-visible name of this sound complex;
+- samsung,audio-routing : A list of the connections between audio components.
+  Each entry is a pair of strings, the first being the connection's sink,
+  the second being the connection's source. Valid names for sources and
+  sinks are the WM8903's pins, and the jacks on the board:
+
+  ...
+
+  * Headphone Jack
+  * Int Spk
+  * Mic Jack
+
+- samsung,i2s-controller : the phandle of the Tegra I2S1 controller;
+- samsung,audio-codec : the phandle of the WM8903 audio codec.
+
+Optional properties:
+- nvidia,spkr-en-gpios : The GPIO that enables the speakers;
+- nvidia,hp-mute-gpios : The GPIO that mutes the headphones;
+- nvidia,hp-det-gpios : The GPIO that detect headphones are plugged in;
+- nvidia,int-mic-en-gpios : The GPIO that enables the internal microphone;
+- nvidia,ext-mic-en-gpios : The GPIO that enables the external microphone.
+
+Example:
+
+sound {
+       compatible = "samsung,exynos-wm1811";
+       nvidia,model = "redwood-wm1811";
+
+       samsung,audio-routing =
+               "Headphone Jack", "HPOUTR",
+               "Headphone Jack", "HPOUTL",
+               "IN1L", "Mic Jack";
+
+       samsung,i2s-controller = <&i2s0>;
+       samsung,audio-codec = <&wm1811>;
+
+       nvidia,spkr-en-gpios = <&codec 2 0>;
+       nvidia,int-mic-en-gpios = <&gpio 184 0>; /*gpio PX0 */
+       nvidia,ext-mic-en-gpios = <&gpio 185 0>; /* gpio PX1 */
+};
+
index 0d321fc..fe8819b 100644 (file)
@@ -70,6 +70,10 @@ config SND_SOC_SAMSUNG_SMDK_WM8994
        help
                Say Y if you want to add support for SoC audio on the SMDKs.
 
+config SND_SAMSUNG_I2S_MASTER
+       bool "I2S Master Mode"
+       depends on SND_SAMSUNG_I2S
+
 config SND_SOC_SAMSUNG_SMDK2443_WM9710
        tristate "SoC AC97 Audio support for SMDK2443 - WM9710"
        depends on SND_SOC_SAMSUNG && MACH_SMDK2443
@@ -228,3 +232,11 @@ config SND_SOC_LITTLEMILL
        select SND_SAMSUNG_I2S
        select MFD_WM8994
        select SND_SOC_WM8994
+
+config SND_SOC_SAMSUNG_EXYNOS4_WM1811
+       tristate "SoC I2S Audio support for WM1811 on Tizen Exynos4 board"
+       depends on SND_SOC_SAMSUNG
+       select SND_SOC_WM8994
+       select SND_SAMSUNG_I2S
+       help
+         Say Y if you want to add support for SoC audio on the Tizen Exynos4 board.
index d2e9b37..1e212e4 100644 (file)
@@ -44,6 +44,7 @@ snd-soc-trats-objs := trats_mc1n2.o
 snd-soc-lowland-objs := lowland.o
 snd-soc-littlemill-objs := littlemill.o
 snd-soc-bells-objs := bells.o
+snd-soc-exynos4-wm1811-objs := exynos4_wm1811.o
 
 obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
 obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
@@ -69,3 +70,4 @@ obj-$(CONFIG_SND_SOC_TRATS) += snd-soc-trats.o
 obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o
 obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o
 obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_EXYNOS4_WM1811) += snd-soc-exynos4-wm1811.o
diff --git a/sound/soc/samsung/exynos4_wm1811.c b/sound/soc/samsung/exynos4_wm1811.c
new file mode 100644 (file)
index 0000000..fb6385f
--- /dev/null
@@ -0,0 +1,1323 @@
+/*
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: KwangHui Cho <kwanghui.cho@samsung.com>
+ *          Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/wm8994/pdata.h>
+#include <linux/mfd/wm8994/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_wakeup.h>
+#include <linux/regulator/machine.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+
+#include "i2s.h"
+#include "i2s-regs.h"
+#include "s3c-i2s-v2.h"
+#include "../codecs/wm8994.h"
+
+#define XTAL_24MHZ_AP                  24000000
+#define CODEC_CLK32K                   32768
+#define CODEC_DEFAULT_SYNC_CLK         11289600
+
+#define WM1811_JACKDET_MODE_NONE       0x0000
+#define WM1811_JACKDET_MODE_JACK       0x0100
+#define WM1811_JACKDET_MODE_MIC                0x0080
+#define WM1811_JACKDET_MODE_AUDIO      0x0180
+
+#define WM1811_JACKDET_BTN0            0x04
+#define WM1811_JACKDET_BTN1            0x10
+#define WM1811_JACKDET_BTN2            0x08
+
+#define SND_SOC_DAPM_SPKMODE(wname, wevent) \
+{       .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \
+       .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
+       .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
+
+
+static char *mic_names[] = {
+       "Main Mic",
+       "Sub Mic",
+};
+
+static const struct wm8958_micd_rate machine_det_rates[] = {
+       { CODEC_CLK32K,                 true,  0, 0 },
+       { CODEC_CLK32K,                 false, 0, 0 },
+       { CODEC_DEFAULT_SYNC_CLK,       true,  0xa, 0xb },
+       { CODEC_DEFAULT_SYNC_CLK,       false, 0xa, 0xb },
+};
+
+static const struct wm8958_micd_rate machine_jackdet_rates[] = {
+       { CODEC_CLK32K,                 true,  0, 0 },
+       { CODEC_CLK32K,                 false, 0, 0 },
+       { CODEC_DEFAULT_SYNC_CLK,       true, 12, 12 },
+       { CODEC_DEFAULT_SYNC_CLK,       false, 7, 7 },
+};
+
+static int aif2_mode;
+const char *aif2_mode_text[] = {
+       "Slave", "Master"
+};
+enum {
+       MODE_CODEC_SLAVE,
+       MODE_CODEC_MASTER,
+};
+
+/* TODO: convert to DT properties */
+enum {
+       MIC_MAIN,
+       MIC_SUB,
+       MIC_MAX
+};
+
+enum exynos4_wm1811_gpios {
+       GPIO_VPS_SOUND,
+       GPIO_MIC_BIAS,
+       GPIO_MIC_SUB_BIAS,
+       GPIO_NUM
+};
+
+/* To support PBA function test */
+static struct class *audio_class;
+static struct device *jack_dev;
+static struct device *caps_dev;
+
+struct exynos_wm1811 {
+       struct snd_soc_jack jack;
+       struct snd_soc_codec *codec;
+       struct clk *codec_mclk;
+
+       int gpio_mic_bias[MIC_MAX];
+       int gpio_vps_en;
+       /* int gpios[GPIO_NUM]; */
+
+       u32 mic_avail[MIC_MAX];
+       bool ignore_earjack;
+       bool cp_wb_support;
+
+       /* Exynos4 EPLL clock */
+       struct clk *clk_pll;
+};
+
+static const struct soc_enum aif2_mode_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text),
+};
+
+static int get_aif2_mode(struct snd_kcontrol *kcontrol,
+               struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.integer.value[0] = aif2_mode;
+       return 0;
+}
+
+static int set_aif2_mode(struct snd_kcontrol *kcontrol,
+               struct snd_ctl_elem_value *ucontrol)
+{
+       if (aif2_mode == ucontrol->value.integer.value[0])
+               return 0;
+
+       aif2_mode = ucontrol->value.integer.value[0];
+
+       WARN_ON((aif2_mode != MODE_CODEC_SLAVE) &&
+               (aif2_mode != MODE_CODEC_MASTER));
+
+       pr_info("set AIF2 mode: %s\n", aif2_mode_text[aif2_mode]);
+       return 0;
+}
+
+static int exynos_snd_micbias(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct exynos_wm1811 *machine = snd_soc_card_get_drvdata(codec->card);
+       unsigned int mic;
+
+       dev_info(codec->dev, "Mic Bias: %s event is %02X", w->name, event);
+
+       if (!strncmp(w->name, mic_names[MIC_MAIN], strlen(mic_names[MIC_MAIN])))
+               mic = MIC_MAIN;
+       else if (!strncmp(w->name, mic_names[MIC_SUB],
+                         strlen(mic_names[MIC_SUB])))
+               mic = MIC_SUB;
+       else {
+               pr_err("Sound: Unknown dapm widget %s\n", w->name);
+               return 0;
+       }
+
+       if (!machine->mic_avail[mic]) {
+               dev_err(codec->dev, "%s is not available on this board",
+                       mic_names[mic]);
+               return 0;
+       }
+
+       if (gpio_is_valid(machine->gpio_mic_bias[mic])) {
+               switch (event) {
+               case SND_SOC_DAPM_PRE_PMU:
+                       pr_info("Sound: %s bias enable", mic_names[mic]);
+                       gpio_set_value(machine->gpio_mic_bias[mic], 1);
+                       /* add delay to remove main mic pop up noise */
+                       msleep(150);
+                       break;
+               case SND_SOC_DAPM_POST_PMD:
+                       pr_info("Sound: %s bias disable", mic_names[mic]);
+                       gpio_set_value(machine->gpio_mic_bias[mic], 0);
+                       break;
+               }
+       } else {
+               /* if EXYNOS gpio is not assigned to external ldo control
+                  that means MICBIAS1 (A9 pin) is used for mic bias supply */
+               switch (event) {
+               case SND_SOC_DAPM_PRE_PMU:
+                       pr_info("Sound: %s bias enable", mic_names[mic]);
+                       snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+                               WM8994_MICB1_ENA_MASK, WM8994_MICB1_ENA);
+                       break;
+               case SND_SOC_DAPM_POST_PMD:
+                       pr_info("Sound: %s bias disable", mic_names[mic]);
+                       snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+                               WM8994_MICB1_ENA_MASK, 0);
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static int exynos_snd_spkmode(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       int ret = 0;
+
+       switch (event) {
+       case SND_SOC_DAPM_PRE_PMU:
+               dev_info(codec->dev, "set speaker L+R mix");
+               ret = snd_soc_update_bits(codec, WM8994_SPKOUT_MIXERS,
+                                 WM8994_SPKMIXR_TO_SPKOUTL_MASK,
+                                 WM8994_SPKMIXR_TO_SPKOUTL);
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               dev_info(codec->dev, "unset speaker L+R mix");
+               ret = snd_soc_update_bits(codec, WM8994_SPKOUT_MIXERS,
+                                 WM8994_SPKMIXR_TO_SPKOUTL_MASK,
+                                 0);
+               break;
+       }
+
+       return ret;
+}
+
+static int exynos_snd_vps_switch(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct exynos_wm1811 *machine = snd_soc_card_get_drvdata(codec->card);
+
+       if (!gpio_is_valid(machine->gpio_vps_en))
+               return 0;
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               pr_info("vps switch enable");
+               gpio_set_value(machine->gpio_vps_en, 1);
+               break;
+       case SND_SOC_DAPM_PRE_PMD:
+               pr_info("vps switch disable");
+               gpio_set_value(machine->gpio_vps_en, 0);
+               break;
+       }
+
+       return 0;
+}
+
+static void codec_micd_set_rate(struct snd_soc_codec *codec)
+{
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+       int best, i, sysclk, val;
+       bool idle;
+       const struct wm8958_micd_rate *rates = NULL;
+       int num_rates = 0;
+
+       idle = !wm8994->jack_mic;
+
+       sysclk = snd_soc_read(codec, WM8994_CLOCKING_1);
+       if (sysclk & WM8994_SYSCLK_SRC)
+               sysclk = wm8994->aifclk[1];
+       else
+               sysclk = wm8994->aifclk[0];
+
+       if (wm8994->jackdet) {
+               rates = machine_jackdet_rates;
+               num_rates = ARRAY_SIZE(machine_jackdet_rates);
+       } else {
+               rates = machine_det_rates;
+               num_rates = ARRAY_SIZE(machine_det_rates);
+       }
+
+       best = 0;
+       for (i = 0; i < num_rates; i++) {
+               if (rates[i].idle != idle)
+                       continue;
+               if (abs(rates[i].sysclk - sysclk) <
+                   abs(rates[best].sysclk - sysclk))
+                       best = i;
+               else if (rates[best].idle != idle)
+                       best = i;
+       }
+
+       val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT
+               | rates[best].rate << WM8958_MICD_RATE_SHIFT;
+
+       snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+                           WM8958_MICD_BIAS_STARTTIME_MASK |
+                           WM8958_MICD_RATE_MASK, val);
+}
+
+static void codec_micdet(u16 status, void *data)
+{
+       struct exynos_wm1811 *machine = data;
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(machine->codec);
+       int report;
+       static int check_report;
+
+       dev_info(machine->codec->dev, "jack status 0x%x", status);
+       /*
+        * LCD is still off even though earjack event has occurred.
+        * So platform daemon need time to deal this event
+        */
+       pm_wakeup_event(machine->codec->dev, 500);
+       /* Either nothing present or just starting detection */
+       if (!(status & WM8958_MICD_STS)) {
+               if (!wm8994->jackdet) {
+                       /* If nothing present then clear our statuses */
+                       dev_dbg(machine->codec->dev, "Detected open circuit\n");
+                       wm8994->jack_mic = false;
+                       wm8994->mic_detecting = true;
+
+                       codec_micd_set_rate(machine->codec);
+
+                       snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+                                           wm8994->btn_mask |
+                                            SND_JACK_HEADSET);
+               }
+               return;
+       }
+       /*
+        * If the measurement is showing a high impedence we've got
+        * a microphone.
+        */
+       if (wm8994->mic_detecting && (status & 0x400)) {
+               dev_info(machine->codec->dev, "Detected microphone\n");
+
+               wm8994->mic_detecting = false;
+               wm8994->jack_mic = true;
+
+               codec_micd_set_rate(machine->codec);
+
+               snd_soc_jack_report(wm8994->micdet[0].jack,
+                               SND_JACK_HEADSET, SND_JACK_HEADSET);
+       }
+
+       if (wm8994->mic_detecting && status & 0x4) {
+               dev_info(machine->codec->dev, "Detected headphone\n");
+               wm8994->mic_detecting = false;
+
+               codec_micd_set_rate(machine->codec);
+
+               snd_soc_jack_report(wm8994->micdet[0].jack,
+                               SND_JACK_HEADPHONE, SND_JACK_HEADSET);
+
+               /* If we have jackdet that will detect removal */
+               if (wm8994->jackdet) {
+                       mutex_lock(&wm8994->accdet_lock);
+
+                       snd_soc_update_bits(machine->codec,
+                                       WM8958_MIC_DETECT_1,
+                                       WM8958_MICD_ENA, 0);
+
+                       if (wm8994->active_refcount) {
+                               snd_soc_update_bits(machine->codec,
+                                       WM8994_ANTIPOP_2,
+                                       WM1811_JACKDET_MODE_MASK,
+                                       WM1811_JACKDET_MODE_AUDIO);
+                       }
+
+                       mutex_unlock(&wm8994->accdet_lock);
+#if 0
+                       if (wm8994->pdata->jd_ext_cap) {
+                               mutex_lock(&wm1811->codec->mutex);
+                               snd_soc_dapm_disable_pin(&wm1811->codec->dapm,
+                                                       "MICBIAS2");
+                               snd_soc_dapm_sync(&wm1811->codec->dapm);
+                               mutex_unlock(&wm1811->codec->mutex);
+                       }
+#endif
+
+               }
+       }
+
+       /* Report short circuit as a button */
+       if (wm8994->jack_mic) {
+               report = 0;
+               if (status & WM1811_JACKDET_BTN0)
+                       report |= SND_JACK_BTN_0;
+
+               if (status & WM1811_JACKDET_BTN1)
+                       report |= SND_JACK_BTN_1;
+
+               if (status & WM1811_JACKDET_BTN2)
+                       report |= SND_JACK_BTN_2;
+
+               /* TODO : Check this on wm1811a */
+               if (check_report != report) {
+                       dev_info(machine->codec->dev,
+                               "Report Button: %08x (%08X)", report, status);
+                       snd_soc_jack_report(wm8994->micdet[0].jack, report,
+                                           wm8994->btn_mask);
+                       check_report = report;
+               } else
+                       dev_info(machine->codec->dev, "Skip button report");
+       }
+}
+
+#ifndef CONFIG_SND_SAMSUNG_I2S_MASTER
+static int machine_aif1_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       unsigned int pll_out;
+       int ret;
+
+       pr_info("params_rate: %d\n", params_rate(params));
+
+       if (params_rate(params) == 8000 || params_rate(params) == 11025)
+               pll_out = params_rate(params) * 512;
+       else
+               pll_out = params_rate(params) * 256;
+
+       ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+                                       | SND_SOC_DAIFMT_NB_NF
+                                       | SND_SOC_DAIFMT_CBM_CFM);
+       if (ret < 0)
+               return ret;
+
+       /* Set the cpu DAI configuration */
+       ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+                                       | SND_SOC_DAIFMT_NB_NF
+                                       | SND_SOC_DAIFMT_CBM_CFM);
+       if (ret < 0)
+               return ret;
+
+       /* Switch FLL */
+       ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+                                 XTAL_24MHZ_AP, pll_out);
+       if (ret < 0)
+               dev_err(codec_dai->dev, "Unable to start FLL1: %d\n", ret);
+
+       ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+                                    pll_out, SND_SOC_CLOCK_IN);
+       if (ret < 0) {
+               dev_err(codec_dai->dev,
+                       "Unable to switch to FLL1: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
+                                       0, MOD_OPCLK_PCLK);
+       if (ret < 0) {
+               dev_err(cpu_dai->dev,
+                       "Unable to set i2s opclk: 0x%x\n", ret);
+               return ret;
+       }
+
+       codec_micd_set_rate(codec_dai->codec);
+
+       dev_info(codec_dai->dev,
+               "AIF1 DAI %s params ch %d, rate %d as i2s slave\n",
+               ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? \
+                                               "playback" : "capture"),
+                                               params_channels(params),
+                                               params_rate(params));
+       return 0;
+}
+#else /* CONFIG_SND_SAMSUNG_I2S_MASTER */
+
+static int machine_aif1_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+       int bfs, psr, rfs, ret;
+       unsigned long rclk, epll_freq;
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_U24:
+       case SNDRV_PCM_FORMAT_S24:
+               bfs = 48;
+               break;
+       case SNDRV_PCM_FORMAT_U16_LE:
+       case SNDRV_PCM_FORMAT_S16_LE:
+               bfs = 32;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (params_rate(params)) {
+       case 16000:
+       case 22050:
+       case 24000:
+       case 32000:
+       case 44100:
+       case 48000:
+       case 88200:
+       case 96000:
+               if (bfs == 48)
+                       rfs = 384;
+               else
+                       rfs = 256;
+               break;
+       case 64000:
+               rfs = 384;
+               break;
+       case 8000:
+       case 11025:
+       case 12000:
+               if (bfs == 48)
+                       rfs = 768;
+               else
+                       rfs = 512;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       rclk = params_rate(params) * rfs;
+
+       switch (rclk) {
+       case 4096000:
+       case 5644800:
+       case 6144000:
+       case 8467200:
+       case 9216000:
+               psr = 8;
+               break;
+       case 8192000:
+       case 11289600:
+       case 12288000:
+       case 16934400:
+       case 18432000:
+               psr = 4;
+               break;
+       case 22579200:
+       case 24576000:
+       case 33868800:
+       case 36864000:
+               psr = 2;
+               break;
+       case 67737600:
+       case 73728000:
+               psr = 1;
+               break;
+       default:
+               printk(KERN_INFO "Not yet supported!\n");
+               return -EINVAL;
+       }
+
+       epll_freq = rclk * psr;
+
+       if (epll_freq != clk_get_rate(machine->clk_pll)) {
+               ret = clk_set_rate(machine->clk_pll, epll_freq);
+               if (ret < 0)
+                       return ret;
+               pr_info("%s: fout_epll: %ld (%ld) Hz\n", __func__,
+                       clk_get_rate(machine->clk_pll), epll_freq);
+       }
+
+       ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+                                       | SND_SOC_DAIFMT_NB_NF
+                                       | SND_SOC_DAIFMT_CBS_CFS);
+       if (ret < 0)
+               return ret;
+
+       ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+                                       | SND_SOC_DAIFMT_NB_NF
+                                       | SND_SOC_DAIFMT_CBS_CFS);
+       if (ret < 0)
+               return ret;
+
+       ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1,
+                                       rclk, SND_SOC_CLOCK_IN);
+       if (ret < 0)
+               return ret;
+
+       ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
+                                       0, SND_SOC_CLOCK_OUT);
+       if (ret < 0)
+               return ret;
+
+       ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+#endif /* CONFIG_SND_SAMSUNG_I2S_MASTER */
+
+/*
+ * WM1811 DAI operations.
+ */
+static struct snd_soc_ops machine_aif1_ops = {
+       .hw_params = machine_aif1_hw_params,
+};
+
+static int machine_aif2_hw_params(struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       int ret;
+       int prate;
+       int bclk;
+       unsigned int fmt;
+       int pll_src;
+       unsigned int pll_freq_in;
+
+       prate = params_rate(params);
+       switch (params_rate(params)) {
+       case 8000:
+       case 16000:
+              break;
+       default:
+               dev_warn(codec_dai->dev, "Unsupported LRCLK %d, falling back to 8kHz\n",
+                       (int)params_rate(params));
+               prate = 8000;
+       }
+
+       fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+       if (aif2_mode == MODE_CODEC_SLAVE)
+               fmt |= SND_SOC_DAIFMT_CBS_CFS;
+       else /* MODE_CODEC_MASTER */
+               fmt |= SND_SOC_DAIFMT_CBM_CFM;
+
+       /* Set the codec DAI configuration */
+       ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+
+       if (ret < 0)
+               return ret;
+
+       switch (prate) {
+       case 8000:
+               bclk = 256000;
+               break;
+       case 16000:
+               bclk = 512000;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (aif2_mode == MODE_CODEC_SLAVE) {
+               pll_src = WM8994_FLL_SRC_BCLK;
+               pll_freq_in = bclk;
+       } else {
+               pll_src = WM8994_FLL_SRC_MCLK1;
+               pll_freq_in = XTAL_24MHZ_AP;
+       }
+       ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+                               pll_src, pll_freq_in,
+                               prate * 256);
+       if (ret < 0)
+               dev_err(codec_dai->dev, "Unable to configure FLL2: %d\n", ret);
+
+       ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
+                                       prate * 256, SND_SOC_CLOCK_IN);
+       if (ret < 0)
+               dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret);
+
+       dev_info(codec_dai->dev,
+               "AIF2 DAI %s params ch %d, rate %d as Clock %s\n",
+               ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? \
+                                               "playback" : "capture"),
+                                               params_channels(params),
+                                               params_rate(params),
+                                               aif2_mode_text[aif2_mode]);
+       return 0;
+}
+
+static struct snd_soc_ops machine_aif2_ops = {
+       .hw_params = machine_aif2_hw_params,
+};
+
+static int machine_aif3_hw_params(struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+       dev_info(codec_dai->dev, "AIF3 DAI %s params ch %d, rate %d\n",
+               ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? \
+                                               "playback" : "capture"),
+                                               params_channels(params),
+                                               params_rate(params));
+       return 0;
+}
+
+static struct snd_soc_ops machine_aif3_ops = {
+       .hw_params = machine_aif3_hw_params,
+};
+
+static const struct snd_kcontrol_new card_controls[] = {
+       SOC_DAPM_PIN_SWITCH("HP"),
+       SOC_DAPM_PIN_SWITCH("SPK"),
+       SOC_DAPM_PIN_SWITCH("RCV"),
+       SOC_DAPM_PIN_SWITCH("LINE"),
+       SOC_DAPM_PIN_SWITCH("HDMI"),
+
+       SOC_DAPM_PIN_SWITCH("Main Mic"),
+       SOC_DAPM_PIN_SWITCH("Sub Mic"),
+       SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_kcontrol_new codec_controls[] = {
+       SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0],
+               get_aif2_mode, set_aif2_mode),
+};
+
+const struct snd_soc_dapm_widget machine_dapm_widgets[] = {
+       SND_SOC_DAPM_HP("HP", NULL),
+       SND_SOC_DAPM_SPKMODE("SPK", exynos_snd_spkmode),
+       SND_SOC_DAPM_SPK("RCV", NULL),
+       SND_SOC_DAPM_LINE("LINE", exynos_snd_vps_switch),
+       SND_SOC_DAPM_LINE("HDMI", NULL),
+
+       SND_SOC_DAPM_MIC("Headset Mic", NULL),
+       SND_SOC_DAPM_MIC("Main Mic", exynos_snd_micbias),
+       SND_SOC_DAPM_MIC("Sub Mic", exynos_snd_micbias),
+
+};
+
+const struct snd_soc_dapm_route machine_dapm_routes[] = {
+       { "HP", NULL, "HPOUT1L" },
+       { "HP", NULL, "HPOUT1R" },
+
+       { "SPK", NULL, "SPKOUTLN" },
+       { "SPK", NULL, "SPKOUTLP" },
+       { "SPK", NULL, "SPKOUTRN" },
+       { "SPK", NULL, "SPKOUTRP" },
+
+       { "RCV", NULL, "HPOUT2N" },
+       { "RCV", NULL, "HPOUT2P" },
+
+       { "LINE", NULL, "LINEOUT2N" },
+       { "LINE", NULL, "LINEOUT2P" },
+
+       { "HDMI", NULL, "LINEOUT1N" }, /* Not connected */
+       { "HDMI", NULL, "LINEOUT1P" }, /* Not connected */
+};
+
+const struct snd_soc_dapm_route wm1811_input_dapm_routes[] = {
+       { "IN1LP", NULL, "Main Mic" },
+       { "IN1LN", NULL, "Main Mic" },
+
+       { "IN1RP", NULL, "Sub Mic" },
+       { "IN1RN", NULL, "Sub Mic" },
+
+       { "IN2LP:VXRN", NULL, "MICBIAS2" },
+       { "MICBIAS2", NULL, "Headset Mic" },
+};
+
+const struct snd_soc_dapm_route redwood_input_dapm_routes[] = {
+       { "IN1LP", NULL, "Main Mic" },
+       { "IN1LN", NULL, "Main Mic" },
+
+       { "IN1RP", NULL, "MICBIAS2" },
+       { "IN1RN", NULL, "MICBIAS2" },
+
+       { "IN2LP:VXRN", NULL, "MICBIAS2" },
+       { "MICBIAS2", NULL, "Headset Mic" },
+};
+
+const struct snd_soc_dapm_route redwood45_input_dapm_routes[] = {
+       { "IN2LP:VXRN", NULL, "Main Mic" },
+       { "IN2LN", NULL, "Main Mic" },
+
+       /* if add route of MICBIAS1 to "Sub Mic",
+          it could be automatically controlled by dapm sequence
+          but we have alternative boards of n035 and redwood45
+          so MICBIAS1 should be controlled another SND_SOC_DAPM_MIC
+          "Sub Mic" control function */
+       { "IN1RP", NULL, "Sub Mic" },
+       { "IN1RN", NULL, "Sub Mic" },
+
+       { "IN1RP", NULL, "MICBIAS2" },
+       { "MICBIAS2", NULL, "Headset Mic" },
+};
+
+static ssize_t earjack_select_jack_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       pr_info("Sound: %s operate nothing", __func__);
+
+       return 0;
+}
+
+static ssize_t earjack_select_jack_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t size)
+{
+       struct snd_soc_codec *codec = dev_get_drvdata(dev);
+       struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+       int report = 0;
+
+       wm8994->mic_detecting = false;
+       wm8994->jack_mic = true;
+
+       codec_micd_set_rate(codec);
+
+       if ((!size) || (buf[0] == '0')) {
+               snd_soc_jack_report(wm8994->micdet[0].jack,
+                                   0, SND_JACK_HEADSET);
+               dev_info(codec->dev, "Forced remove earjack\n");
+       } else {
+               if (buf[0] == '1') {
+                       dev_info(codec->dev, "Forced detect microphone\n");
+                       report = SND_JACK_HEADSET;
+               } else {
+                       dev_info(codec->dev, "Forced detect headphone\n");
+                       report = SND_JACK_HEADPHONE;
+               }
+               snd_soc_jack_report(wm8994->micdet[0].jack,
+                                   report, SND_JACK_HEADSET);
+       }
+
+       return size;
+}
+
+static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+                  earjack_select_jack_show, earjack_select_jack_store);
+
+static ssize_t audio_caps_cp_show(struct device *dev,
+                       struct device_attribute *attr, char *buf)
+{
+       struct exynos_wm1811 *machine = dev_get_drvdata(dev);
+
+       if (machine->cp_wb_support == true)
+               return sprintf(buf, "wb\n") + 1;
+       else
+               return sprintf(buf, "nb\n") + 1;
+}
+static DEVICE_ATTR(cp_caps, S_IRUGO, audio_caps_cp_show, NULL);
+
+static ssize_t audio_caps_mic_count_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct exynos_wm1811 *machine = dev_get_drvdata(dev);
+       int i, cnt = 0;
+
+       if (!machine)
+               return 0;
+
+       for (i = 0; i < MIC_MAX; i++) {
+               if (machine->mic_avail[i])
+                       cnt++;
+       }
+
+       return sprintf(buf, "%d\n", cnt);
+}
+static DEVICE_ATTR(mic_count, S_IRUGO, audio_caps_mic_count_show, NULL);
+
+static int machine_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+       struct snd_soc_codec *codec = rtd->codec;
+       struct snd_soc_card *card = rtd->card;
+       struct exynos_wm1811 *machine = snd_soc_card_get_drvdata(card);
+       struct snd_soc_dai *aif1_dai = rtd->codec_dai;
+       struct wm8994 *wm8994 = dev_get_drvdata(codec->dev->parent);
+       int ret;
+
+       ret = clk_prepare_enable(machine->codec_mclk);
+       if (ret < 0)
+               return ret;
+
+       ret = snd_soc_add_card_controls(card, card_controls,
+                                       ARRAY_SIZE(card_controls));
+       if (ret != 0)
+               dev_err(card->dev, "Failed to add card ctrls: %d\n", ret);
+
+       ret = snd_soc_add_codec_controls(codec, codec_controls,
+                                       ARRAY_SIZE(codec_controls));
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add codec ctrls: %d\n", ret);
+
+       ret = snd_soc_dapm_new_controls(&codec->dapm, machine_dapm_widgets,
+                                          ARRAY_SIZE(machine_dapm_widgets));
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add DAPM widgets: %d\n", ret);
+
+       ret = snd_soc_dapm_add_routes(&codec->dapm, machine_dapm_routes,
+                                          ARRAY_SIZE(machine_dapm_routes));
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret);
+
+       if (!strcmp(card->name, "redwood45")) {
+               ret = snd_soc_dapm_add_routes(&codec->dapm,
+                               redwood45_input_dapm_routes,
+                               ARRAY_SIZE(redwood45_input_dapm_routes));
+       } else if (!strcmp(card->name, "redwood")) {
+               ret = snd_soc_dapm_add_routes(&codec->dapm,
+                                       redwood_input_dapm_routes,
+                                       ARRAY_SIZE(redwood_input_dapm_routes));
+       } else {
+               ret = snd_soc_dapm_add_routes(&codec->dapm,
+                                       wm1811_input_dapm_routes,
+                                       ARRAY_SIZE(wm1811_input_dapm_routes));
+       }
+
+       if (ret != 0)
+               dev_err(codec->dev,
+                       "Failed to add DAPM input routes: %d\n", ret);
+
+       ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+                                    CODEC_CLK32K, SND_SOC_CLOCK_IN);
+       if (ret < 0)
+               dev_err(codec->dev, "Failed to boot clocking\n");
+
+       /* Force AIF1CLK on as it will be master for jack detection */
+       ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+       if (ret < 0)
+               dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n", ret);
+
+       /* check sub mic support */
+       if (machine && !machine->mic_avail[MIC_SUB])
+               snd_soc_dapm_nc_pin(&codec->dapm, "Sub Mic");
+       else
+               snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic");
+
+       /* check earjack support */
+       if (machine->ignore_earjack)
+               snd_soc_dapm_nc_pin(&codec->dapm, "Headset Mic");
+       else
+               snd_soc_dapm_ignore_suspend(&codec->dapm, "Headset Mic");
+
+       /* disable codec ldo for mic mias when using ext mic bias */
+       if (!machine->gpio_mic_bias[MIC_MAIN] &&
+           !machine->gpio_mic_bias[MIC_SUB])
+               snd_soc_dapm_nc_pin(&codec->dapm, "MICBIAS1");
+
+       /* disable fm radio analog input */
+       snd_soc_dapm_nc_pin(&codec->dapm, "IN2RN");
+       snd_soc_dapm_nc_pin(&codec->dapm, "IN2RP:VXRP");
+
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "RCV");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "SPK");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "HP");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "Main Mic");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1DACDAT");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2DACDAT");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3DACDAT");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1ADCDAT");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2ADCDAT");
+       snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3ADCDAT");
+
+       machine->codec = codec;
+
+       codec_micd_set_rate(codec);
+
+       ret = snd_soc_jack_new(codec, "Headset Jack",
+                               SND_JACK_HEADSET | SND_JACK_BTN_0 |
+                               SND_JACK_BTN_1 | SND_JACK_BTN_2,
+                               &machine->jack);
+       if (ret < 0)
+               dev_err(codec->dev, "Failed to create jack: %d\n", ret);
+
+       ret = snd_jack_set_key(machine->jack.jack, SND_JACK_BTN_0, KEY_MEDIA);
+       if (ret < 0)
+               dev_err(codec->dev, "Failed to set KEY_MEDIA: %d\n", ret);
+
+       ret = snd_jack_set_key(machine->jack.jack, SND_JACK_BTN_1,
+                                                       KEY_VOLUMEDOWN);
+       if (ret < 0)
+               dev_err(codec->dev, "Failed to set KEY_VOLUMEUP: %d\n", ret);
+
+       ret = snd_jack_set_key(machine->jack.jack, SND_JACK_BTN_2,
+                                                       KEY_VOLUMEUP);
+       if (ret < 0)
+               dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret);
+
+       /* TODO : check this on fast boot environment */
+       machine->jack.status = 0;
+
+       /* check earjack support */
+       if (machine->ignore_earjack) {
+               dev_info(codec->dev,
+                       "Earjack event doesn't support for this board");
+               ret = snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+                               WM8958_MICD_ENA, 0);
+               if (ret < 0)
+                       dev_err(codec->dev,
+                               "Failed to disable mic detection: %d\n", ret);
+       } else {
+#if 0
+               ret = wm8958_mic_detect(codec,
+                               &machine->jack, codec_micdet, machine);
+               if (ret < 0)
+                       dev_err(codec->dev,
+                               "Failed start detection: %d\n", ret);
+#endif
+               /* To wakeup for earjack event in suspend mode */
+               enable_irq_wake(wm8994->irq);
+               device_init_wakeup(machine->codec->dev, true);
+       }
+
+       /* To support PBA function test */
+       audio_class = class_create(THIS_MODULE, "audio");
+
+       if (IS_ERR(audio_class))
+               pr_err("Failed to create class\n");
+
+       jack_dev = device_create(audio_class, NULL, 0, codec, "earjack");
+
+       if (device_create_file(jack_dev, &dev_attr_select_jack) < 0)
+               pr_err("Failed to create device file (%s)!\n",
+                       dev_attr_select_jack.attr.name);
+
+       caps_dev = device_create(audio_class, NULL, 0, NULL, "caps");
+       dev_set_drvdata(caps_dev, machine);
+
+       if (device_create_file(caps_dev, &dev_attr_cp_caps) < 0)
+               pr_err("Failed to create device file (%s)!\n",
+                       dev_attr_cp_caps.attr.name);
+
+       if (device_create_file(caps_dev, &dev_attr_mic_count) < 0)
+               pr_err("Failed to create device file (%s)!\n",
+                       dev_attr_mic_count.attr.name);
+
+       return snd_soc_dapm_sync(&codec->dapm);
+}
+
+static struct snd_soc_dai_link machine_dai[] = {
+       {
+               /* Primary DAI i/f */
+               .name = "WM1811 AIF1",
+               .stream_name = "Pri_Dai",
+               .cpu_dai_name = "samsung-i2s.0",
+               .codec_dai_name = "wm8994-aif1",
+               .platform_name = "samsung-i2s.0",
+               .codec_name = "wm8994-codec",
+               .init = machine_init_paiftx,
+               .ops = &machine_aif1_ops,
+       }, {
+               .name = "WM1811 Voice",
+               .stream_name = "Voice Tx/Rx",
+               .cpu_dai_name = "voice-modem",
+               .codec_dai_name = "wm8994-aif2",
+               .platform_name = "snd-soc-dummy",
+               .codec_name = "wm8994-codec",
+               .ops = &machine_aif2_ops,
+               .ignore_suspend = 1,
+       }, {
+               .name = "WM1811 BT",
+               .stream_name = "BT Tx/Rx",
+               .cpu_dai_name = "voice-bluetooth",
+               .codec_dai_name = "wm8994-aif3",
+               .platform_name = "snd-soc-dummy",
+               .codec_name = "wm8994-codec",
+               .ops = &machine_aif3_ops,
+               .ignore_suspend = 1,
+       }, {
+               /* Sec_Fifo DAI i/f */
+               .cpu_dai_name = "samsung-i2s.4",
+               .codec_dai_name = "wm8994-aif1",
+#ifdef CONFIG_SND_SAMSUNG_USE_IDMA
+               .name = "Sec_FIFO TX",
+               .stream_name = "Sec_Dai",
+               .platform_name = "samsung-idma",
+#else
+               .name = "WM1811 AIF1 Playback",
+               .stream_name = "Pri_Dai Playback",
+               .platform_name = "samsung-audio",
+#endif
+               .codec_name = "wm8994-codec",
+               .ops = &machine_aif1_ops,
+       },
+};
+
+static int machine_card_suspend_post(struct snd_soc_card *card)
+{
+       struct exynos_wm1811 *machine = snd_soc_card_get_drvdata(card);
+       struct snd_soc_codec *codec = card->rtd->codec;
+       struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
+       struct snd_soc_dai *aif2_dai = card->rtd[1].codec_dai;
+       int ret;
+
+
+       if (!codec->active) {
+               /* change sysclk from pll out of mclk1 to mclk2
+                  to disable mclk1 from AP */
+               dev_info(codec->dev, "use mclk2 and disable fll");
+               ret = snd_soc_dai_set_sysclk(aif2_dai,
+                               WM8994_SYSCLK_MCLK2,
+                               CODEC_CLK32K,
+                               SND_SOC_CLOCK_IN);
+               if (ret < 0)
+                       dev_err(codec->dev,
+                       "Unable to switch aif2 sysclk to MCLK2: %d\n", ret);
+
+               ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, 0, 0, 0);
+               if (ret < 0)
+                       dev_err(codec->dev,
+                               "Unable to stop FLL2: %d\n", ret);
+
+               ret = snd_soc_dai_set_sysclk(aif1_dai,
+                               WM8994_SYSCLK_MCLK2,
+                               CODEC_CLK32K,
+                               SND_SOC_CLOCK_IN);
+               if (ret < 0)
+                       dev_err(codec->dev,
+                       "Unable to switch aif1 sysclk to MCLK2: %d\n", ret);
+
+               ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
+               if (ret < 0)
+                       dev_err(codec->dev, "Unsable to stop FLL1: 0x%x", ret);
+
+               /* Jack det rate and mic det rate should be changed
+               to match the above changed sysclk */
+               codec_micd_set_rate(codec);
+
+               clk_disable_unprepare(machine->codec_mclk);
+       }
+
+       snd_soc_dapm_sync(&codec->dapm);
+       return 0;
+}
+
+static int machine_card_resume_pre(struct snd_soc_card *card)
+{
+       struct exynos_wm1811 *machine = snd_soc_card_get_drvdata(card);
+       struct snd_soc_codec *codec = card->rtd->codec;
+       struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
+       int reg = 0;
+       int ret;
+
+       if (!codec->active)
+               clk_prepare_enable(machine->codec_mclk);
+
+       /* change sysclk from mclk2 to pll out of mclk1 */
+       ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+                       WM8994_FLL_SRC_MCLK1,
+                       XTAL_24MHZ_AP,
+                       CODEC_DEFAULT_SYNC_CLK);
+       if (ret < 0)
+               dev_err(aif1_dai->dev, "Unable to start FLL1: 0x%x\n", ret);
+
+       ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1,
+                       CODEC_DEFAULT_SYNC_CLK, SND_SOC_CLOCK_IN);
+       if (ret < 0)
+               dev_err(aif1_dai->dev,
+                       "Unable to switch sysclk to FLL1: 0x%x\n", ret);
+
+       codec_micd_set_rate(codec);
+
+       /* workaround for jack detection
+        * sometimes WM8994_GPIO_1 type changed wrong function type
+        * so if type mismatched, update to IRQ type
+        */
+       reg = snd_soc_read(codec, WM8994_GPIO_1);
+
+       if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_IRQ) {
+               dev_err(codec->dev, "%s: GPIO1 type 0x%x\n", __func__, reg);
+               snd_soc_write(codec, WM8994_GPIO_1, WM8994_GP_FN_IRQ);
+       }
+
+       return 0;
+}
+
+static int exynos4_wm1811_parse_dt(struct exynos_wm1811 *machine,
+                                  struct device *dev)
+{
+       struct device_node *node = dev->of_node;
+       int gpio, ret, i;
+
+       of_property_read_u32_array(node, "samsung,mic-availability",
+                                  machine->mic_avail,
+                                  ARRAY_SIZE(machine->mic_avail));
+       machine->ignore_earjack = of_property_read_bool(node,
+                                       "samsung,ignore-earjack");
+       machine->cp_wb_support = of_property_read_bool(node, "samsung,cp-wb");
+       machine->gpio_vps_en = -EINVAL;
+
+       for (i = MIC_MAIN; i < MIC_MAX; i++) {
+               machine->gpio_mic_bias[i] = -EINVAL;
+
+               if (!machine->mic_avail[i])
+                       break;
+
+               gpio = of_get_named_gpio(node, "samsung,mic-en-gpios", i);
+
+               if (!gpio_is_valid(gpio)) {
+                       dev_info(dev, "%s uses CODEC MICBIAS1\n", mic_names[i]);
+                       continue;
+               }
+               ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_LOW,
+                                                       mic_names[i]);
+               if (ret) {
+                       dev_err(dev, "%s gpio request failed\n", mic_names[i]);
+                       continue;
+               }
+               dev_info(dev, "%s enable GPIO initialized\n", mic_names[i]);
+               machine->gpio_mic_bias[i] = gpio;
+       }
+
+       gpio = of_get_named_gpio(node, "samsung,vps-en-gpios", 0);
+       if (!gpio_is_valid(gpio))
+               return 0;
+       ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_LOW, "wm1811");
+       if (!ret)
+               machine->gpio_vps_en = gpio;
+
+       pr_info("gpio: %d, ret: %d\n", ret, gpio);
+       return ret;
+}
+
+static int exynos4_wm1811_probe(struct platform_device *pdev)
+{
+       struct snd_soc_dai_link *dai_link = &machine_dai[0];
+       struct exynos_wm1811 *machine;
+       struct snd_soc_card *card;
+       struct clk *parent, *out_mux;
+       int ret;
+
+       if (!pdev->dev.of_node)
+               return -ENODEV;
+
+       card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
+       if (!card)
+               return -ENOMEM;
+
+       card->dai_link = machine_dai;
+       /* FIXME: Enable all audio interfaces */
+       card->num_links = 1; /* ARRAY_SIZE(machine_dai); */
+       card->suspend_post = machine_card_suspend_post;
+       card->resume_pre = machine_card_resume_pre;
+       card->dev = &pdev->dev;
+
+       machine = devm_kzalloc(&pdev->dev, sizeof(*machine), GFP_KERNEL);
+       if (!machine)
+               return -ENOMEM;
+
+       ret = exynos4_wm1811_parse_dt(machine, &pdev->dev);
+       if (ret < 0)
+               return ret;
+
+       dai_link->cpu_dai_name = NULL;
+       dai_link->cpu_of_node = of_parse_phandle(pdev->dev.of_node,
+                                               "samsung,i2s-controller", 0);
+       if (dai_link->cpu_of_node == NULL) {
+               dev_err(&pdev->dev, "i2s-controller property parse error\n");
+               return -EINVAL;
+       }
+       dai_link->platform_name = NULL;
+       dai_link->platform_of_node = dai_link->cpu_of_node;
+
+       machine->codec_mclk = clk_get(&pdev->dev, "out");
+       if (IS_ERR(machine->codec_mclk)) {
+               dev_err(&pdev->dev, "failed to get out clock\n");
+               return PTR_ERR(machine->codec_mclk);
+       }
+
+       out_mux = clk_get(&pdev->dev, "out-mux");
+       if (IS_ERR(out_mux)) {
+               dev_err(&pdev->dev, "failed to get out mux clock\n");
+               return PTR_ERR(out_mux);
+       }
+
+       parent = clk_get(&pdev->dev, "parent");
+       if (IS_ERR(parent)) {
+               dev_err(&pdev->dev, "failed to get parent clock\n");
+               return PTR_ERR(parent);
+       }
+
+       clk_set_parent(out_mux, parent);
+
+       machine->clk_pll = clk_get(&pdev->dev, "pll");
+       if (IS_ERR(machine->clk_pll)) {
+               dev_err(&pdev->dev, "failed to get pll clock\n");
+               return PTR_ERR(machine->clk_pll);
+       }
+
+       ret = snd_soc_of_parse_card_name(card, "samsung,model");
+       if (ret)
+               return -ENODEV;
+
+       dev_info(&pdev->dev, "Card name is %s\n", card->name);
+       /* FIXME: Clean up on error path */
+
+       snd_soc_card_set_drvdata(card, machine);
+
+       return snd_soc_register_card(card);
+}
+
+static int exynos4_wm1811_remove(struct platform_device *pdev)
+{
+       struct snd_soc_card *card = platform_get_drvdata(pdev);
+       struct exynos_wm1811 *machine = snd_soc_card_get_drvdata(card);
+
+       snd_soc_unregister_card(card);
+       clk_put(machine->codec_mclk);
+       clk_put(machine->clk_pll);
+       return 0;
+}
+
+static const struct of_device_id exynos4_wm1811_of_match[] = {
+       {
+               .compatible = "samsung,exynos4-wm1811",
+       },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, exynos4_wm1811_of_match);
+
+static struct platform_driver exynos4_wm1811_driver = {
+       .driver = {
+               .name = "exynos4-wm1811",
+               .of_match_table = exynos4_wm1811_of_match,
+               .owner = THIS_MODULE,
+               .pm = &snd_soc_pm_ops,
+       },
+       .probe = exynos4_wm1811_probe,
+       .remove = exynos4_wm1811_remove,
+};
+
+module_platform_driver(exynos4_wm1811_driver);
+
+MODULE_AUTHOR("KwangHui Cho <kwanghui.cho@samsung.com>");
+MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
+MODULE_DESCRIPTION("Exynos4+WM1811 machine ASoC driver");
+MODULE_LICENSE("GPL");