aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/samsung
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/samsung')
-rw-r--r--sound/soc/samsung/Kconfig49
-rw-r--r--sound/soc/samsung/Makefile16
-rw-r--r--sound/soc/samsung/audss.c59
-rw-r--r--sound/soc/samsung/dma.c8
-rw-r--r--sound/soc/samsung/grande_wm1811.c1715
-rw-r--r--sound/soc/samsung/i2s.c3
-rw-r--r--sound/soc/samsung/m3_wm1811.c1219
-rw-r--r--sound/soc/samsung/midas_wm1811.c138
-rw-r--r--sound/soc/samsung/pcm.c39
-rw-r--r--sound/soc/samsung/t0_wm1811.c1393
-rw-r--r--sound/soc/samsung/t0duos_wm1811.c1479
11 files changed, 6050 insertions, 68 deletions
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
index e040010..de7cd13 100644
--- a/sound/soc/samsung/Kconfig
+++ b/sound/soc/samsung/Kconfig
@@ -77,13 +77,39 @@ config SND_SOC_SAMSUNG_MIDAS_WM1811
help
Say Y if you want to add support for SoC audio on the MIDAS.
-config SND_SOC_SAMSUNG_SLP_MIDAS_WM1811
+config SND_SOC_SAMSUNG_GRANDE_WM1811
+ tristate "SoC I2S Audio support for WM1811 on Grande"
+ depends on SND_SOC_SAMSUNG && (MACH_M0_GRANDECTC || MACH_IRON || MACH_M0_DUOSCTC )
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+
+config SND_SOC_SAMSUNG_T0_WM1811
+ tristate "SoC I2S Audio support for WM1811 on T0"
+ depends on SND_SOC_SAMSUNG && MACH_T0
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on the T0.
+
+config SND_SOC_SAMSUNG_T0DUOS_WM1811
+ tristate "SoC I2S Audio support for WM1811 on T0DUOS"
+ depends on SND_SOC_SAMSUNG && (MACH_T0_CHN_CTC || MACH_T0_CHN_CMCC || MACH_T0_CHN_OPEN_DUOS || MACH_T0_CHN_CU_DUOS)
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+
+config SND_SOC_SAMSUNG_M3_WM1811
+ tristate "SoC I2S Audio support for WM1811 on M3"
+ depends on SND_SOC_SAMSUNG && MACH_M3
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+
+config SND_SOC_SAMSUNG_SLP_EXYNOS_WM1811
tristate "SoC I2S Audio support for WM1811 on SLP MIDAS"
- depends on SND_SOC_SAMSUNG && (MACH_SLP_MIDAS || MACH_SLP_MIDAS_D || MACH_SLP_MIDAS_Q || MACH_SLP_PQ || MACH_SLP_PQ_LTE)
+ depends on SND_SOC_SAMSUNG && (MACH_SLP_PQ || MACH_SLP_PQ_LTE || MACH_REDWOOD || MACH_SLP_T0_LTE)
select SND_SOC_WM8994
select SND_SAMSUNG_I2S
help
- Say Y if you want to add support for SoC audio on the MIDAS.
+ Say Y if you want to add support for SoC audio on the SLP Exynos PQ_Proxima, Redwood.
config SND_SOC_SAMSUNG_SLP_NAPLES_WM1811
tristate "SoC I2S Audio support for WM1811 on SLP NAPLES"
@@ -107,6 +133,17 @@ config SND_SOC_U1_MC1N2
help
Say Y if you want to add support for SoC audio on U1.
+config SND_SOC_SLP_TRATS_MC1N2
+ tristate "SoC I2S Audio support for MC1N2 on SLP TRATS"
+ depends on SND_SOC_SAMSUNG && (MACH_TRATS)
+ select SND_SOC_MC1N2
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on SLP TRATS.
+ This uses
+ Samsung Exynos-4210 audio subsystem
+ and Yamaha MC1N2 codec.
+
config SND_SOC_SAMSUNG_USE_DMA_WRAPPER
bool "DMA wrapper for ALSA Platform(DMA)"
@@ -273,6 +310,10 @@ config SND_SOC_SPEYSIDE
select SND_SOC_WM8915
select SND_SOC_WM9081
+config SND_DUOS_MODEM_SWITCH
+ bool "Select DUOS Modem"
+ depends on SND_SOC_SAMSUNG
+
config SND_USE_SUB_MIC
bool "Use SUB_MIC"
depends on SND_SOC_SAMSUNG
@@ -293,4 +334,4 @@ config SND_USE_LINEOUT_SWITCH
config SND_USE_MUIC_SWITCH
bool "Use MUIC_SWITCH CONTROL"
- depends on SND_SOC_SAMSUNG \ No newline at end of file
+ depends on SND_SOC_SAMSUNG
diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile
index f8a7d95..5891ebe 100644
--- a/sound/soc/samsung/Makefile
+++ b/sound/soc/samsung/Makefile
@@ -44,8 +44,16 @@ snd-soc-smdk-spdif-objs := smdk_spdif.o
snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o
snd-soc-speyside-objs := speyside.o
snd-soc-u1-mc1n2-objs := u1_mc1n2.o
+snd-soc-slp-trats-mc1n2-objs := slp_trats_mc1n2.o
+snd-soc-slp-jack-objs := slp_jack.o
snd-soc-midas-wm1811-objs := midas_wm1811.o
+snd-soc-t0-wm1811-objs := t0_wm1811.o
+snd-soc-t0duos-wm1811-objs := t0duos_wm1811.o
+snd-soc-m3-wm1811-objs := m3_wm1811.o
+snd-soc-slp-exynos-wm1811-objs := slp_exynos_wm1811.o
+snd-soc-slp-naples-wm1811-objs := slp_naples_wm1811.o
snd-soc-lungo-wm1811-objs := lungo_wm1811.o
+snd-soc-grande-wm1811-objs := grande_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
@@ -67,8 +75,16 @@ obj-$(CONFIG_SND_SOC_GONI_AQUILA_WM8994) += snd-soc-goni-wm8994.o
obj-$(CONFIG_SND_SOC_SMDK_WM8580_PCM) += snd-soc-smdk-wm8580pcm.o
obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o
obj-$(CONFIG_SND_SOC_U1_MC1N2) += snd-soc-u1-mc1n2.o
+obj-$(CONFIG_SND_SOC_SLP_TRATS_MC1N2) += snd-soc-slp-trats-mc1n2.o
+obj-$(CONFIG_SND_SOC_SLP_TRATS_MC1N2) += snd-soc-slp-jack.o
obj-$(CONFIG_SND_SOC_SAMSUNG_MIDAS_WM1811) += snd-soc-midas-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_T0_WM1811) += snd-soc-t0-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_T0DUOS_WM1811) += snd-soc-t0duos-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_M3_WM1811) += snd-soc-m3-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SLP_EXYNOS_WM1811) += snd-soc-slp-exynos-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SLP_NAPLES_WM1811) += snd-soc-slp-naples-wm1811.o
obj-$(CONFIG_SND_SOC_SAMSUNG_LUNGO_WM1811) += snd-soc-midas-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_GRANDE_WM1811) += snd-soc-grande-wm1811.o
obj-$(CONFIG_SND_SAMSUNG_RP) += srp_ulp/
obj-$(CONFIG_SND_SAMSUNG_ALP) += srp_alp/
diff --git a/sound/soc/samsung/audss.c b/sound/soc/samsung/audss.c
index 1c052b0..a5300de 100644
--- a/sound/soc/samsung/audss.c
+++ b/sound/soc/samsung/audss.c
@@ -52,13 +52,27 @@ static DEFINE_SPINLOCK(lock);
static int audss_clk_div_init(struct clk *src_clk)
{
- u32 clk_div = readl(S5P_CLKDIV_AUDSS);
+ struct clk *fout_epll;
u32 src_clk_rate = 0;
- u32 srp_div = 1;
- u32 bus_div = 2;
- u32 i2s_div = 2;
+ u64 srp_rate = 0;
+ u64 bus_rate = 0;
+ u64 i2s_rate = 0;
+ u32 srp_div;
+ u32 bus_div;
+ u32 i2s_div;
u32 ret = -1;
+ writel(0xF84, S5P_CLKDIV_AUDSS); /* to avoid over-clock */
+
+ fout_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ pr_err("%s: failed to get fout_epll\n", __func__);
+ return ret;
+ }
+ clk_set_parent(audss.mout_audss, fout_epll);
+
+ pr_debug("%s: CLKSRC[0x%x]\n", __func__, readl(S5P_CLKSRC_AUDSS));
+
src_clk_rate = clk_get_rate(src_clk);
if (!src_clk_rate) {
pr_err("%s: Can't get current clk_rate %d\n",
@@ -67,29 +81,32 @@ static int audss_clk_div_init(struct clk *src_clk)
}
pr_debug("%s: SRC Clock Rate[%d]\n", __func__, src_clk_rate);
- if (src_clk_rate > 100000000) {
- srp_div <<= 1;
- bus_div <<= 1;
- i2s_div <<= 1;
- }
- if (!strcmp(audss.rclksrc, "busclk"))
- i2s_div = 16; /* Use max div */
+ for (srp_div = 1; srp_div <= 16; srp_div++) {
+ if ((src_clk_rate >> (srp_div - 1)) <= 100000000)
+ break;
+ }
- clk_div &= ~(S5P_AUDSS_CLKDIV_RP_MASK
- | S5P_AUDSS_CLKDIV_BUSCLK_MASK
- | S5P_AUDSS_CLKDIV_I2SCLK_MASK);
+ for (bus_div = 1; bus_div <= 16; bus_div++) {
+ if ((src_clk_rate >> (bus_div - 1)) <= 50000000)
+ break;
+ }
- if (srp_div)
- clk_div |= (srp_div - 1) << S5P_AUDSS_CLKDIV_RP_SHIFT;
+ for (i2s_div = 1; i2s_div <= 16; i2s_div++) {
+ if ((src_clk_rate >> (i2s_div - 1)) <= 50000000)
+ break;
+ }
- if (bus_div)
- clk_div |= (bus_div - 1) << S5P_AUDSS_CLKDIV_BUSCLK_SHIFT;
+ if (!strcmp(audss.rclksrc, "busclk"))
+ i2s_div = 16; /* Use max div */
- if (i2s_div)
- clk_div |= (i2s_div - 1) << S5P_AUDSS_CLKDIV_I2SCLK_SHIFT;
+ srp_rate = src_clk_rate >> (srp_div - 1);
+ bus_rate = src_clk_rate >> (bus_div - 1);
+ i2s_rate = src_clk_rate >> (i2s_div - 1);
- writel(clk_div, S5P_CLKDIV_AUDSS);
+ clk_set_rate(audss.dout_srp, srp_rate);
+ clk_set_rate(audss.bus_clk, bus_rate);
+ clk_set_rate(audss.i2s_clk, i2s_rate);
pr_debug("%s: BUSCLK[%ld], I2SCLK[%ld]\n", __func__,
clk_get_rate(audss.bus_clk),
diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c
index a7178ed..63c475e 100644
--- a/sound/soc/samsung/dma.c
+++ b/sound/soc/samsung/dma.c
@@ -222,11 +222,19 @@ static int dma_hw_free(struct snd_pcm_substream *substream)
/* TODO - do we need to ensure DMA flushed */
snd_pcm_set_runtime_buffer(substream, NULL);
+#ifdef CONFIG_SLP_WIP
+ spin_lock(&prtd->lock);
+#endif
+
if (prtd->params) {
s3c2410_dma_free(prtd->params->channel, prtd->params->client);
prtd->params = NULL;
}
+#ifdef CONFIG_SLP_WIP
+ spin_unlock(&prtd->lock);
+#endif
+
return 0;
}
diff --git a/sound/soc/samsung/grande_wm1811.c b/sound/soc/samsung/grande_wm1811.c
new file mode 100644
index 0000000..304b592
--- /dev/null
+++ b/sound/soc/samsung/grande_wm1811.c
@@ -0,0 +1,1715 @@
+/*
+ * grande_wm1811.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co. Ltd
+ *
+ * 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.
+ */
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+#include <linux/wakelock.h>
+#include <linux/suspend.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+
+#include <mach/regs-clock.h>
+#include <mach/pmu.h>
+#include <mach/midas-sound.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>
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+#include <linux/mfd/max77693-private.h>
+#endif
+
+
+#include "i2s.h"
+#include "s3c-i2s-v2.h"
+#include "../codecs/wm8994.h"
+
+
+#define MIDAS_DEFAULT_MCLK1 24000000
+#define MIDAS_DEFAULT_MCLK2 32768
+#define MIDAS_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
+
+
+static struct wm8958_micd_rate midas_det_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 },
+};
+
+static struct wm8958_micd_rate midas_jackdet_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 11, 11 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 },
+};
+
+static int aif2_mode;
+const char *aif2_mode_text[] = {
+ "Slave", "Master"
+};
+
+static int kpcs_mode = 2;
+const char *kpcs_mode_text[] = {
+ "Off", "On"
+};
+
+static int input_clamp;
+const char *input_clamp_text[] = {
+ "Off", "On"
+};
+
+static int lineout_mode;
+const char *lineout_mode_text[] = {
+ "Off", "On"
+};
+
+static int modem_mode;
+const char *modem_mode_text[] = {
+ "CP1", "CP2"
+};
+
+
+#ifndef CONFIG_SEC_DEV_JACK
+/* To support PBA function test */
+static struct class *jack_class;
+static struct device *jack_dev;
+#endif
+
+#ifdef SND_USE_BIAS_LEVEL
+static bool midas_fll1_active;
+struct snd_soc_dai *midas_aif1_dai;
+#endif
+
+struct wm1811_machine_priv {
+ struct snd_soc_jack jack;
+ struct snd_soc_codec *codec;
+ struct delayed_work mic_work;
+ struct wake_lock jackdet_wake_lock;
+};
+
+
+static void midas_gpio_init(void)
+{
+ int err;
+#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS
+ /* Main Microphone BIAS */
+ err = gpio_request(GPIO_MIC_BIAS_EN, "MAIN MIC");
+ if (err) {
+ pr_err(KERN_ERR "MIC_BIAS_EN GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_MIC_BIAS_EN, 1);
+ gpio_set_value(GPIO_MIC_BIAS_EN, 0);
+ gpio_free(GPIO_MIC_BIAS_EN);
+#endif
+
+#ifdef CONFIG_SND_USE_SUB_MIC
+ /* Sub Microphone BIAS */
+ err = gpio_request(GPIO_SUB_MIC_BIAS_EN, "SUB MIC");
+ if (err) {
+ pr_err(KERN_ERR "SUB_MIC_BIAS_EN GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_SUB_MIC_BIAS_EN, 1);
+ gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0);
+ gpio_free(GPIO_SUB_MIC_BIAS_EN);
+#endif
+
+#ifdef CONFIG_SND_USE_THIRD_MIC
+ /* Third Microphone BIAS */
+ err = gpio_request(GPIO_THIRD_MIC_BIAS_EN, "THIRD MIC");
+ if (err) {
+ pr_err(KERN_ERR "THIRD_MIC_BIAS_EN GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_THIRD_MIC_BIAS_EN, 1);
+ gpio_set_value(GPIO_THIRD_MIC_BIAS_EN, 0);
+ gpio_free(GPIO_THIRD_MIC_BIAS_EN);
+#endif
+
+#ifdef CONFIG_FM_RADIO
+ /* FM/Third Mic GPIO */
+ err = gpio_request(GPIO_FM_MIC_SW, "GPL0");
+ if (err) {
+ pr_err(KERN_ERR "FM/THIRD_MIC Switch GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_FM_MIC_SW, 1);
+ gpio_set_value(GPIO_FM_MIC_SW, 0);
+ gpio_free(GPIO_FM_MIC_SW);
+#endif
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ err = gpio_request(GPIO_LINEOUT_EN, "LINEOUT_EN");
+ if (err) {
+ pr_err(KERN_ERR "LINEOUT_EN GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_LINEOUT_EN, 1);
+ gpio_set_value(GPIO_LINEOUT_EN, 0);
+ gpio_free(GPIO_LINEOUT_EN);
+#endif
+
+ err = gpio_request(GPIO_AUDIO_PCM_SEL, "AUDIO_PCM_SEL");
+ if (err) {
+ pr_err(KERN_ERR "AUDIO_PCM_SEL GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_AUDIO_PCM_SEL, 1);
+ gpio_set_value(GPIO_AUDIO_PCM_SEL, 0);
+ gpio_free(GPIO_AUDIO_PCM_SEL);
+}
+
+
+static const struct soc_enum modem_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(modem_mode_text), modem_mode_text),
+};
+
+static int get_modem_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = modem_mode;
+ return 0;
+}
+
+static int set_modem_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ modem_mode = ucontrol->value.integer.value[0];
+
+ if (modem_mode) {
+ gpio_set_value(GPIO_AUDIO_PCM_SEL, 1);
+ } else {
+ gpio_set_value(GPIO_AUDIO_PCM_SEL, 0);
+ /*msleep(50);*/
+ }
+ dev_info(codec->dev, "set modem select : %s\n",
+ modem_mode_text[modem_mode]);
+ return 0;
+
+}
+
+static const struct soc_enum lineout_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text),
+};
+
+static int get_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = lineout_mode;
+ return 0;
+}
+
+static int set_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ lineout_mode = ucontrol->value.integer.value[0];
+ dev_dbg(codec->dev, "set lineout mode : %s\n",
+ lineout_mode_text[lineout_mode]);
+ return 0;
+
+}
+static const struct soc_enum aif2_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text),
+};
+
+static const struct soc_enum kpcs_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(kpcs_mode_text), kpcs_mode_text),
+};
+
+static const struct soc_enum input_clamp_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_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];
+
+ pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]);
+
+ return 0;
+}
+
+static int get_kpcs_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = kpcs_mode;
+ return 0;
+}
+
+static int set_kpcs_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+
+ kpcs_mode = ucontrol->value.integer.value[0];
+
+ pr_info("set kpcs mode : %d\n", kpcs_mode);
+
+ return 0;
+}
+
+static int get_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = input_clamp;
+ return 0;
+}
+
+static int set_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ input_clamp = ucontrol->value.integer.value[0];
+
+ if (input_clamp) {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP);
+ msleep(100);
+ } else {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, 0);
+ }
+ pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]);
+
+ return 0;
+}
+
+
+static int midas_ext_micbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_dbg(codec->dev, "%s event is %02X", w->name, event);
+
+#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpio_set_value(GPIO_MIC_BIAS_EN, 1);
+ msleep(150);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpio_set_value(GPIO_MIC_BIAS_EN, 0);
+ break;
+ }
+#endif
+ return 0;
+}
+
+static int midas_ext_submicbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_dbg(codec->dev, "%s event is %02X", w->name, event);
+
+#ifdef CONFIG_SND_USE_SUB_MIC
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 1);
+ msleep(150);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0);
+ break;
+ }
+#endif
+ return 0;
+}
+
+static int midas_ext_thirdmicbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_dbg(codec->dev, "%s event is %02X", w->name, event);
+
+#ifdef CONFIG_SND_USE_THIRD_MIC
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpio_set_value(GPIO_THIRD_MIC_BIAS_EN, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpio_set_value(GPIO_THIRD_MIC_BIAS_EN, 0);
+ break;
+ }
+#endif
+ return 0;
+}
+
+/*
+ * midas_ext_spkmode :
+ * For phone device have 1 external speaker
+ * should mix LR data in a speaker mixer (mono setting)
+ */
+static int midas_ext_spkmode(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ int ret = 0;
+#ifndef CONFIG_SND_USE_STEREO_SPEAKER
+ struct snd_soc_codec *codec = w->codec;
+
+ ret = snd_soc_update_bits(codec, WM8994_SPKOUT_MIXERS,
+ WM8994_SPKMIXR_TO_SPKOUTL_MASK,
+ WM8994_SPKMIXR_TO_SPKOUTL);
+#endif
+ return ret;
+}
+
+static int midas_lineout_switch(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_dbg(codec->dev, "%s event is %02X", w->name, event);
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ msleep(150);
+ max77693_muic_set_audio_switch(1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ max77693_muic_set_audio_switch(0);
+ break;
+ }
+#endif
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ gpio_set_value(GPIO_LINEOUT_EN, 1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ gpio_set_value(GPIO_LINEOUT_EN, 0);
+ break;
+ }
+#endif
+ return 0;
+}
+
+static void midas_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 = midas_jackdet_rates;
+ num_rates = ARRAY_SIZE(midas_jackdet_rates);
+ wm8994->pdata->micd_rates = midas_jackdet_rates;
+ wm8994->pdata->num_micd_rates = num_rates;
+ } else {
+ rates = midas_det_rates;
+ num_rates = ARRAY_SIZE(midas_det_rates);
+ wm8994->pdata->micd_rates = midas_det_rates;
+ wm8994->pdata->num_micd_rates = num_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);
+}
+
+#ifdef SND_USE_BIAS_LEVEL
+static void midas_start_fll1(struct snd_soc_dai *aif1_dai)
+{
+ int ret;
+ if (midas_fll1_active)
+ return;
+
+ dev_info(aif1_dai->dev, "Moving to audio clocking settings\n");
+
+ /* Switch AIF1 to MCLK2 while we bring stuff up */
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to switch to MCLK2: %d\n", ret);
+
+ /* Start the 24MHz clock to provide a high frequency reference to
+ * provide a high frequency reference for the FLL, giving improved
+ * performance.
+ */
+ midas_snd_set_mclk(true, true);
+
+ /* Switch the FLL */
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1,
+ MIDAS_DEFAULT_SYNC_CLK);
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret);
+
+ /* Then switch AIF1CLK to it */
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1,
+ MIDAS_DEFAULT_SYNC_CLK, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret);
+
+ midas_fll1_active = true;
+}
+#endif
+
+static void midas_micdet(u16 status, void *data)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+ int report;
+
+
+ wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ);
+
+ /* Either nothing present or just starting detection */
+ if (!(status & WM8958_MICD_STS)) {
+ if (!wm8994->jackdet) {
+ /* If nothing present then clear our statuses */
+ dev_dbg(wm1811->codec->dev, "Detected open circuit\n");
+ wm8994->jack_mic = false;
+ wm8994->mic_detecting = true;
+
+ midas_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ wm8994->btn_mask |
+ SND_JACK_HEADSET);
+ }
+ /*ToDo*/
+ /*return;*/
+ }
+
+ /* If the measurement is showing a high impedence we've got a
+ * microphone.
+ */
+ if (wm8994->mic_detecting && (status & 0x400)) {
+ dev_info(wm1811->codec->dev, "Detected microphone\n");
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ midas_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET,
+ SND_JACK_HEADSET);
+ }
+
+ if (wm8994->mic_detecting && status & 0x4) {
+ dev_info(wm1811->codec->dev, "Detected headphone\n");
+ wm8994->mic_detecting = false;
+
+ midas_micd_set_rate(wm1811->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(wm1811->codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(wm1811->codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ }
+
+ mutex_unlock(&wm8994->accdet_lock);
+
+ 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);
+ }
+ }
+ }
+
+ /* 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;
+
+ dev_dbg(wm1811->codec->dev, "Detected Button: %08x (%08X)\n",
+ report, status);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, report,
+ wm8994->btn_mask);
+ }
+}
+
+#ifdef CONFIG_SND_SAMSUNG_I2S_MASTER
+static int set_epll_rate(unsigned long rate)
+{
+ struct clk *fout_epll;
+
+ fout_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
+ return -ENOENT;
+ }
+
+ if (rate == clk_get_rate(fout_epll))
+ goto out;
+
+ clk_set_rate(fout_epll, rate);
+out:
+ clk_put(fout_epll);
+
+ return 0;
+}
+#endif /* CONFIG_SND_SAMSUNG_I2S_MASTER */
+
+#ifndef CONFIG_SND_SAMSUNG_I2S_MASTER
+static int midas_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ /* AIF1CLK should be >=3MHz for optimal performance */
+ 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;
+
+#ifndef SND_USE_BIAS_LEVEL
+ /* Switch the FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1,
+ 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)
+ return ret;
+#else
+ midas_start_fll1(codec_dai);
+#endif
+
+ if (ret < 0)
+ return ret;
+
+ dev_info(codec_dai->dev, "%s --\n", __func__);
+
+ return 0;
+}
+#else /* CONFIG_SND_SAMSUNG_I2S_MASTER */
+static int midas_wm1811_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;
+
+ 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;
+ }
+
+ set_epll_rate(rclk * psr);
+
+ 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 */
+
+/*
+ * Midas WM1811 DAI operations.
+ */
+static struct snd_soc_ops midas_wm1811_aif1_ops = {
+ .hw_params = midas_wm1811_aif1_hw_params,
+};
+
+static int midas_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ 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 8000Hz\n",
+ (int)params_rate(params));
+ prate = 8000;
+ }
+
+#if defined(CONFIG_MACH_GRANDE) || defined(CONFIG_MACH_M0_DUOSCTC)
+ if (aif2_mode == 0)
+ /* Set the codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A
+ | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A
+ | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+#elif defined(CONFIG_MACH_IRON)
+ /* Set the codec DAI configuration, aif2_mode:0 is slave */
+ /* modem_mode:1 is CP2 */
+ if (aif2_mode == 0) {
+ if (modem_mode == 1)
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ } else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+#else
+ /* Set the codec DAI configuration, aif2_mode:0 is slave */
+ if (aif2_mode == 0)
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+#endif
+
+ if (ret < 0)
+ return ret;
+
+#if defined(CONFIG_MACH_GRANDE) || defined(CONFIG_MACH_M0_DUOSCTC)
+ bclk = 2048000;
+#elif defined(CONFIG_MACH_IRON)
+ if (modem_mode == 1)
+ bclk = 2048000;
+ else
+ switch (prate) {
+ case 8000:
+ bclk = 256000;
+ break;
+ case 16000:
+ bclk = 512000;
+ break;
+ default:
+ return -EINVAL;
+ }
+#else
+ switch (prate) {
+ case 8000:
+ bclk = 256000;
+ break;
+ case 16000:
+ bclk = 512000;
+ break;
+ default:
+ return -EINVAL;
+ }
+#endif
+
+#ifdef SND_USE_BIAS_LEVEL
+ if (!midas_fll1_active)
+ midas_start_fll1(midas_aif1_dai);
+#endif
+
+ if (aif2_mode == 0) {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_BCLK,
+ bclk, prate * 256);
+ } else {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1, 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, "%s --\n", __func__);
+ return 0;
+}
+
+static struct snd_soc_ops midas_wm1811_aif2_ops = {
+ .hw_params = midas_wm1811_aif2_hw_params,
+};
+
+static int midas_wm1811_aif3_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ pr_err("%s: enter\n", __func__);
+ return 0;
+}
+
+static struct snd_soc_ops midas_wm1811_aif3_ops = {
+ .hw_params = midas_wm1811_aif3_hw_params,
+};
+
+static const struct snd_kcontrol_new midas_controls[] = {
+ SOC_DAPM_PIN_SWITCH("HP"),
+ SOC_DAPM_PIN_SWITCH("SPK"),
+ SOC_DAPM_PIN_SWITCH("RCV"),
+ SOC_DAPM_PIN_SWITCH("FM In"),
+ 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("Third Mic"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+
+ SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0],
+ get_aif2_mode, set_aif2_mode),
+
+ SOC_ENUM_EXT("KPCS Mode", kpcs_mode_enum[0],
+ get_kpcs_mode, set_kpcs_mode),
+
+ SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0],
+ get_input_clamp, set_input_clamp),
+
+ SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0],
+ get_lineout_mode, set_lineout_mode),
+
+ SOC_ENUM_EXT("ModemSwitch Mode", modem_mode_enum[0],
+ get_modem_mode, set_modem_mode),
+};
+
+const struct snd_soc_dapm_widget midas_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+ SND_SOC_DAPM_SPK("SPK", midas_ext_spkmode),
+ SND_SOC_DAPM_SPK("RCV", NULL),
+ SND_SOC_DAPM_LINE("LINE", midas_lineout_switch),
+ SND_SOC_DAPM_LINE("HDMI", NULL),
+
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", midas_ext_micbias),
+ SND_SOC_DAPM_MIC("Sub Mic", midas_ext_submicbias),
+ SND_SOC_DAPM_MIC("Third Mic", midas_ext_thirdmicbias),
+ SND_SOC_DAPM_LINE("FM In", NULL),
+
+ SND_SOC_DAPM_INPUT("S5P RP"),
+};
+
+const struct snd_soc_dapm_route midas_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" },
+ { "HDMI", NULL, "LINEOUT1P" },
+
+#if defined(CONFIG_MACH_M0_DUOSCTC)
+ { "IN2LP:VXRN", NULL, "Main Mic" },
+ { "IN2LN", NULL, "Main Mic" },
+
+ { "IN1RP", NULL, "MICBIAS1" },
+ { "IN1RN", NULL, "MICBIAS1" },
+ { "MICBIAS1", NULL, "Sub Mic" },
+
+ { "IN1LP", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+ { "IN1LN", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+#else
+ { "IN1LP", NULL, "MICBIAS1" },
+ { "IN1LN", NULL, "MICBIAS1" },
+ { "MICBIAS1", NULL, "Main Mic" },
+
+ { "IN1RP", NULL, "Sub Mic" },
+ { "IN1RN", NULL, "Sub Mic" },
+
+ { "IN2LP:VXRN", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+#endif
+ { "AIF1DAC1L", NULL, "S5P RP" },
+ { "AIF1DAC1R", NULL, "S5P RP" },
+
+ { "IN2RN", NULL, "FM In" },
+ { "IN2RP:VXRP", NULL, "FM In" },
+
+ { "IN2RN", NULL, "Third Mic" },
+ { "IN2RP:VXRP", NULL, "Third Mic" },
+};
+
+static struct snd_soc_dai_driver midas_ext_dai[] = {
+ {
+ .name = "midas.cp",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "midas.bt",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+#ifndef CONFIG_SEC_DEV_JACK
+static ssize_t earjack_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) ||
+ (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) {
+ report = 1;
+ }
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_key_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0)
+ report = 1;
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_key_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_select_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __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);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ midas_micd_set_rate(codec);
+
+ if ((!size) || (buf[0] != '1')) {
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ 0, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced remove microphone\n");
+ } else {
+
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ SND_JACK_HEADSET, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced detect microphone\n");
+ }
+
+ return size;
+}
+
+static ssize_t reselect_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __func__);
+ return 0;
+}
+
+static ssize_t reselect_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 reg = 0;
+
+ reg = snd_soc_read(codec, WM8958_MIC_DETECT_3);
+ if (reg == 0x402) {
+ dev_info(codec->dev, "Detected open circuit\n");
+
+ snd_soc_update_bits(codec, WM8958_MICBIAS2,
+ WM8958_MICB2_DISCH, WM8958_MICB2_DISCH);
+ /* Enable debounce while removed */
+ snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
+ WM1811_JACKDET_DB, WM1811_JACKDET_DB);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = false;
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ } else {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_JACK);
+ }
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ SND_JACK_MECHANICAL | SND_JACK_HEADSET |
+ wm8994->btn_mask);
+ }
+ return size;
+}
+
+static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ reselect_jack_show, reselect_jack_store);
+
+static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_select_jack_show, earjack_select_jack_store);
+
+static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_key_state_show, earjack_key_state_store);
+
+static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_state_show, earjack_state_store);
+#endif
+
+static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+ struct snd_soc_dai *aif1_dai = rtd->codec_dai;
+ struct wm8994 *control = codec->control_data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+#ifdef SND_USE_BIAS_LEVEL
+ midas_aif1_dai = aif1_dai;
+#endif
+
+ midas_snd_set_mclk(true, false);
+
+ rtd->codec_dai->driver->playback.channels_max =
+ rtd->cpu_dai->driver->playback.channels_max;
+
+ ret = snd_soc_add_controls(codec, midas_controls,
+ ARRAY_SIZE(midas_controls));
+
+ ret = snd_soc_dapm_new_controls(&codec->dapm, midas_dapm_widgets,
+ ARRAY_SIZE(midas_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, midas_dapm_routes,
+ ARRAY_SIZE(midas_dapm_routes));
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret);
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2, 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 */
+ if (wm8994->revision > 1) {
+ ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n",
+ ret);
+ }
+
+ ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret);
+
+ 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, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic");
+ 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");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "Third Mic");
+
+ wm1811->codec = codec;
+
+ midas_micd_set_rate(codec);
+
+#ifdef CONFIG_SEC_DEV_JACK
+ /* By default use idle_bias_off, will override for WM8994 */
+ codec->dapm.idle_bias_off = 0;
+#else /* CONFIG_SEC_DEV_JACK */
+ wm1811->jack.status = 0;
+
+ ret = snd_soc_jack_new(codec, "Midas Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &wm1811->jack);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to create jack: %d\n", ret);
+
+ ret = snd_jack_set_key(wm1811->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(wm1811->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(wm1811->jack.jack, SND_JACK_BTN_2,
+ KEY_VOLUMEUP);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret);
+
+ if (wm8994->revision > 1) {
+ dev_info(codec->dev, "wm1811: Rev %c support mic detection\n",
+ 'A' + wm8994->revision);
+ ret = wm8958_mic_detect(codec, &wm1811->jack, midas_micdet,
+ wm1811);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed start detection: %d\n",
+ ret);
+ } else {
+ dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n",
+ 'A' + wm8994->revision);
+ codec->dapm.idle_bias_off = 0;
+ }
+ /* To wakeup for earjack event in suspend mode */
+ enable_irq_wake(control->irq);
+
+ wake_lock_init(&wm1811->jackdet_wake_lock,
+ WAKE_LOCK_SUSPEND, "midas_jackdet");
+
+ /* To support PBA function test */
+ jack_class = class_create(THIS_MODULE, "audio");
+
+ if (IS_ERR(jack_class))
+ pr_err("Failed to create class\n");
+
+ jack_dev = device_create(jack_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);
+
+ if (device_create_file(jack_dev, &dev_attr_key_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_key_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_reselect_jack.attr.name);
+
+#endif /* CONFIG_SEC_DEV_JACK */
+ return snd_soc_dapm_sync(&codec->dapm);
+}
+
+static struct snd_soc_dai_link midas_dai[] = {
+ { /* Sec_Fifo DAI i/f */
+ .name = "Sec_FIFO TX",
+ .stream_name = "Sec_Dai",
+ .cpu_dai_name = "samsung-i2s.4",
+ .codec_dai_name = "wm8994-aif1",
+#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER
+ .platform_name = "samsung-audio-idma",
+#else
+ .platform_name = "samsung-audio",
+#endif
+ .codec_name = "wm8994-codec",
+ .init = midas_wm1811_init_paiftx,
+ .ops = &midas_wm1811_aif1_ops,
+ },
+ {
+ .name = "Midas_WM1811 Voice",
+ .stream_name = "Voice Tx/Rx",
+ .cpu_dai_name = "midas.cp",
+ .codec_dai_name = "wm8994-aif2",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &midas_wm1811_aif2_ops,
+ .ignore_suspend = 1,
+ },
+ {
+ .name = "Midas_WM1811 BT",
+ .stream_name = "BT Tx/Rx",
+ .cpu_dai_name = "midas.bt",
+ .codec_dai_name = "wm8994-aif3",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &midas_wm1811_aif3_ops,
+ .ignore_suspend = 1,
+ },
+ { /* Primary DAI i/f */
+ .name = "WM8994 AIF1",
+ .stream_name = "Pri_Dai",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec",
+ .ops = &midas_wm1811_aif1_ops,
+ },
+};
+
+static int midas_card_suspend_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+static int midas_card_suspend_post(struct snd_soc_card *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) {
+#ifndef SND_USE_BIAS_LEVEL
+ ret = snd_soc_dai_set_sysclk(aif2_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch 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\n");
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch to MCLK2\n");
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to stop FLL1\n");
+#endif
+
+ midas_snd_set_mclk(false, true);
+ }
+
+#ifdef CONFIG_ARCH_EXYNOS5
+ exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0);
+#else /* for CONFIG_ARCH_EXYNOS5 */
+ exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0);
+#endif
+
+ return 0;
+}
+
+static int midas_card_resume_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
+ int ret;
+
+ midas_snd_set_mclk(true, false);
+
+#ifndef SND_USE_BIAS_LEVEL
+ /* Switch the FLL */
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1,
+ MIDAS_DEFAULT_SYNC_CLK);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret);
+
+ /* Then switch AIF1CLK to it */
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_FLL1,
+ MIDAS_DEFAULT_SYNC_CLK,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret);
+#endif
+
+ return 0;
+}
+
+static int midas_card_resume_post(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int reg = 0;
+#if !defined(CONFIG_MACH_M0_DUOSCTC)
+ snd_soc_write(codec, 0x102, 0x3);
+ snd_soc_write(codec, 0xcb, 0x5151);
+ snd_soc_write(codec, 0xd3, 0x3f3f);
+ snd_soc_write(codec, 0xd4, 0x3f3f);
+ snd_soc_write(codec, 0xd5, 0x3f3f);
+ snd_soc_write(codec, 0xd6, 0x3226);
+ snd_soc_write(codec, 0x102, 0x0);
+ snd_soc_write(codec, 0xd1, 0x87);
+ snd_soc_write(codec, 0x3b, 0x9);
+ snd_soc_write(codec, 0x3c, 0x2);
+#endif
+
+ /* 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);
+ }
+
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+#ifdef SND_USE_BIAS_LEVEL
+static int midas_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
+
+ if (dapm->dev != aif1_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ midas_start_fll1(card->rtd[0].codec_dai);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int midas_set_bias_level_post(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ 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 (dapm->dev != aif1_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+
+ /* When going idle stop FLL1 and revert to using MCLK2
+ * directly for minimum power consumptin for accessory
+ * detection.
+ */
+ if (card->dapm.bias_level == SND_SOC_BIAS_PREPARE) {
+ dev_info(aif1_dai->dev, "Moving to STANDBY\n");
+
+ ret = snd_soc_dai_set_sysclk(aif2_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to switch to MCLK2\n");
+
+ ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
+ 0, 0, 0);
+
+ if (ret < 0)
+ dev_err(codec->dev,
+ "Failed to change FLL2\n");
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec->dev,
+ "Failed to switch to MCLK2\n");
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ 0, 0, 0);
+ if (ret < 0)
+ dev_err(codec->dev,
+ "Failed to stop FLL1\n");
+
+
+ midas_fll1_active = false;
+ midas_snd_set_mclk(false, false);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ card->dapm.bias_level = level;
+
+ return 0;
+}
+#endif
+
+static struct snd_soc_card midas = {
+ .name = "Midas_WM1811",
+ .dai_link = midas_dai,
+
+ /* If you want to use sec_fifo device,
+ * changes the num_link = 2 or ARRAY_SIZE(midas_dai). */
+ .num_links = ARRAY_SIZE(midas_dai),
+
+#ifdef SND_USE_BIAS_LEVEL
+ .set_bias_level = midas_set_bias_level,
+ .set_bias_level_post = midas_set_bias_level_post,
+#endif
+
+ .suspend_post = midas_card_suspend_post,
+ .resume_pre = midas_card_resume_pre,
+ .suspend_pre = midas_card_suspend_pre,
+ .resume_post = midas_card_resume_post
+};
+
+static struct platform_device *midas_snd_device;
+
+static int __init midas_audio_init(void)
+{
+ struct wm1811_machine_priv *wm1811;
+ int ret;
+
+ wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL);
+ if (!wm1811) {
+ pr_err("Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+ snd_soc_card_set_drvdata(&midas, wm1811);
+
+ midas_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!midas_snd_device) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ ret = snd_soc_register_dais(&midas_snd_device->dev, midas_ext_dai,
+ ARRAY_SIZE(midas_ext_dai));
+ if (ret != 0)
+ pr_err("Failed to register external DAIs: %d\n", ret);
+
+ platform_set_drvdata(midas_snd_device, &midas);
+
+ ret = platform_device_add(midas_snd_device);
+ if (ret)
+ platform_device_put(midas_snd_device);
+
+ midas_gpio_init();
+
+ return ret;
+
+err_device_alloc:
+ kfree(wm1811);
+err_kzalloc:
+ return ret;
+}
+module_init(midas_audio_init);
+
+static void __exit midas_audio_exit(void)
+{
+ struct snd_soc_card *card = &midas;
+ struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card);
+ platform_device_unregister(midas_snd_device);
+ kfree(wm1811);
+}
+module_exit(midas_audio_exit);
+
+MODULE_AUTHOR("JS. Park <aitdark.park@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC Midas WM1811");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c
index 77279a4..52f308b 100644
--- a/sound/soc/samsung/i2s.c
+++ b/sound/soc/samsung/i2s.c
@@ -29,7 +29,8 @@
#include "i2s.h"
#include "srp-types.h"
-#if defined(CONFIG_SND_SAMSUNG_RP) && defined(CONFIG_MACH_U1)
+#if defined(CONFIG_SND_SAMSUNG_RP) && \
+ (defined(CONFIG_MACH_U1) || defined(CONFIG_MACH_TRATS))
#define USE_ALT_REG_RECOVER
#endif
diff --git a/sound/soc/samsung/m3_wm1811.c b/sound/soc/samsung/m3_wm1811.c
new file mode 100644
index 0000000..949f7d7
--- /dev/null
+++ b/sound/soc/samsung/m3_wm1811.c
@@ -0,0 +1,1219 @@
+/*
+ * m3_wm1811.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co. Ltd
+ *
+ * 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.
+ */
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+#include <linux/wakelock.h>
+#include <linux/suspend.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+
+#include <mach/regs-clock.h>
+#include <mach/pmu.h>
+#include <mach/midas-sound.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/wm8994/pdata.h>
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+#include <linux/mfd/max77693-private.h>
+#endif
+
+
+#include "i2s.h"
+#include "s3c-i2s-v2.h"
+#include "../codecs/wm8994.h"
+
+
+#define MIDAS_DEFAULT_MCLK1 24000000
+#define MIDAS_DEFAULT_MCLK2 32768
+#define MIDAS_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
+
+
+static struct wm8958_micd_rate m3_det_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 },
+};
+
+static struct wm8958_micd_rate m3_jackdet_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 },
+};
+
+static int aif2_mode;
+const char *aif2_mode_text[] = {
+ "Slave", "Master"
+};
+
+static int input_clamp;
+const char *input_clamp_text[] = {
+ "Off", "On"
+};
+
+static int lineout_mode;
+const char *lineout_mode_text[] = {
+ "Off", "On"
+};
+
+#ifndef CONFIG_SEC_DEV_JACK
+/* To support PBA function test */
+static struct class *jack_class;
+static struct device *jack_dev;
+#endif
+
+struct wm1811_machine_priv {
+ struct snd_soc_jack jack;
+ struct snd_soc_codec *codec;
+ struct delayed_work mic_work;
+ struct wake_lock jackdet_wake_lock;
+};
+
+
+static void m3_gpio_init(void)
+{
+ int err;
+#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS
+ /* Main Microphone BIAS */
+ err = gpio_request(GPIO_MIC_BIAS_EN, "MAIN MIC");
+ if (err) {
+ pr_err(KERN_ERR "MIC_BIAS_EN GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_MIC_BIAS_EN, 1);
+ gpio_set_value(GPIO_MIC_BIAS_EN, 0);
+ gpio_free(GPIO_MIC_BIAS_EN);
+#endif
+
+#ifdef CONFIG_SND_USE_SUB_MIC
+ /* Sub Microphone BIAS */
+ err = gpio_request(GPIO_SUB_MIC_BIAS_EN, "SUB MIC");
+ if (err) {
+ pr_err(KERN_ERR "SUB_MIC_BIAS_EN GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_SUB_MIC_BIAS_EN, 1);
+ gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0);
+ gpio_free(GPIO_SUB_MIC_BIAS_EN);
+#endif
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ err = gpio_request(GPIO_VPS_SOUND_EN, "LINEOUT_EN");
+ if (err) {
+ pr_err(KERN_ERR "LINEOUT_EN GPIO set error!\n");
+ return;
+ }
+ gpio_direction_output(GPIO_VPS_SOUND_EN, 0);
+ gpio_set_value(GPIO_VPS_SOUND_EN, 0);
+ gpio_free(GPIO_VPS_SOUND_EN);
+#endif
+}
+
+static const struct soc_enum lineout_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text),
+};
+
+static int get_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = lineout_mode;
+ return 0;
+}
+
+static int set_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ lineout_mode = ucontrol->value.integer.value[0];
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ if (lineout_mode) {
+ wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
+ gpio_set_value(GPIO_VPS_SOUND_EN, 1);
+ } else {
+ gpio_set_value(GPIO_VPS_SOUND_EN, 0);
+ wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
+ }
+#endif
+ dev_info(codec->dev, "set lineout mode : %s\n",
+ lineout_mode_text[lineout_mode]);
+ return 0;
+
+}
+static const struct soc_enum aif2_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text),
+};
+
+static const struct soc_enum input_clamp_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_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];
+
+ pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]);
+
+ return 0;
+}
+
+static int get_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = input_clamp;
+ return 0;
+}
+
+static int set_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ input_clamp = ucontrol->value.integer.value[0];
+
+ if (input_clamp) {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP);
+ msleep(100);
+ } else {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, 0);
+ }
+ pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]);
+
+ return 0;
+}
+
+
+static int set_ext_micbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpio_set_value(GPIO_MIC_BIAS_EN, 1);
+ msleep(100);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpio_set_value(GPIO_MIC_BIAS_EN, 0);
+ break;
+ }
+#endif
+ return 0;
+}
+
+static int set_ext_submicbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+#ifdef CONFIG_SND_USE_SUB_MIC
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 1);
+ msleep(100);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0);
+ break;
+ }
+#endif
+ return 0;
+}
+
+static int set_lineout_switch(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ msleep(150);
+ max77693_muic_set_audio_switch(1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ max77693_muic_set_audio_switch(0);
+ break;
+ }
+#endif
+
+ return 0;
+}
+
+static void m3_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 = m3_jackdet_rates;
+ num_rates = ARRAY_SIZE(m3_jackdet_rates);
+ wm8994->pdata->micd_rates = m3_jackdet_rates;
+ wm8994->pdata->num_micd_rates = num_rates;
+ } else {
+ rates = m3_det_rates;
+ num_rates = ARRAY_SIZE(m3_det_rates);
+ wm8994->pdata->micd_rates = m3_det_rates;
+ wm8994->pdata->num_micd_rates = num_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 m3_micdet(u16 status, void *data)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+ int report;
+
+
+ wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ);
+
+ /* Either nothing present or just starting detection */
+ if (!(status & WM8958_MICD_STS)) {
+ if (!wm8994->jackdet) {
+ /* If nothing present then clear our statuses */
+ dev_dbg(wm1811->codec->dev, "Detected open circuit\n");
+ wm8994->jack_mic = false;
+ wm8994->mic_detecting = true;
+
+ m3_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ wm8994->btn_mask |
+ SND_JACK_HEADSET);
+ }
+ /*ToDo*/
+ /*return;*/
+ }
+
+ /* If the measurement is showing a high impedence we've got a
+ * microphone.
+ */
+ if (wm8994->mic_detecting && (status & 0x400)) {
+ dev_info(wm1811->codec->dev, "Detected microphone\n");
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ m3_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET,
+ SND_JACK_HEADSET);
+ }
+
+ if (wm8994->mic_detecting && status & 0x4) {
+ dev_info(wm1811->codec->dev, "Detected headphone\n");
+ wm8994->mic_detecting = false;
+
+ m3_micd_set_rate(wm1811->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(wm1811->codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(wm1811->codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ }
+
+ mutex_unlock(&wm8994->accdet_lock);
+
+ 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);
+ }
+ }
+ }
+
+ /* 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;
+
+ dev_dbg(wm1811->codec->dev, "Detected Button: %08x (%08X)\n",
+ report, status);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, report,
+ wm8994->btn_mask);
+ }
+}
+
+static int m3_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ /* AIF1CLK should be >=3MHz for optimal performance */
+ 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 the FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1,
+ 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)
+ return ret;
+
+ dev_info(codec_dai->dev, "%s --\n", __func__);
+
+ return 0;
+}
+
+/*
+ * m3 WM1811 DAI operations.
+ */
+static struct snd_soc_ops m3_wm1811_aif1_ops = {
+ .hw_params = m3_wm1811_aif1_hw_params,
+};
+
+static int m3_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ 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 8000Hz\n",
+ (int)params_rate(params));
+ prate = 8000;
+ }
+
+ /* Set the codec DAI configuration, aif2_mode:0 is slave */
+ if (aif2_mode == 0)
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ 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;
+
+ switch (prate) {
+ case 8000:
+ bclk = 256000;
+ break;
+ case 16000:
+ bclk = 512000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (aif2_mode == 0) {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_BCLK,
+ bclk, prate * 256);
+ } else {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1, 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, "%s --\n", __func__);
+ return 0;
+}
+
+static struct snd_soc_ops m3_wm1811_aif2_ops = {
+ .hw_params = m3_wm1811_aif2_hw_params,
+};
+
+static int m3_wm1811_aif3_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ pr_err("%s: enter\n", __func__);
+ return 0;
+}
+
+static struct snd_soc_ops m3_wm1811_aif3_ops = {
+ .hw_params = m3_wm1811_aif3_hw_params,
+};
+
+static const struct snd_kcontrol_new m3_controls[] = {
+ SOC_DAPM_PIN_SWITCH("HP"),
+ SOC_DAPM_PIN_SWITCH("SPK"),
+ SOC_DAPM_PIN_SWITCH("RCV"),
+ SOC_DAPM_PIN_SWITCH("FM In"),
+ 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"),
+
+ SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0],
+ get_aif2_mode, set_aif2_mode),
+
+ SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0],
+ get_input_clamp, set_input_clamp),
+
+ SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0],
+ get_lineout_mode, set_lineout_mode),
+
+};
+
+const struct snd_soc_dapm_widget m3_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+ SND_SOC_DAPM_SPK("RCV", NULL),
+ SND_SOC_DAPM_LINE("LINE", set_lineout_switch),
+ SND_SOC_DAPM_LINE("HDMI", NULL),
+
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", set_ext_micbias),
+ SND_SOC_DAPM_MIC("Sub Mic", set_ext_submicbias),
+ SND_SOC_DAPM_LINE("FM In", NULL),
+
+ SND_SOC_DAPM_INPUT("S5P RP"),
+};
+
+const struct snd_soc_dapm_route m3_dapm_routes[] = {
+ { "HP", NULL, "HPOUT1L" },
+ { "HP", NULL, "HPOUT1R" },
+
+ { "SPK", NULL, "SPKOUTLN" },
+ { "SPK", NULL, "SPKOUTLP" },
+
+ { "RCV", NULL, "HPOUT2N" },
+ { "RCV", NULL, "HPOUT2P" },
+
+ { "LINE", NULL, "LINEOUT2N" },
+ { "LINE", NULL, "LINEOUT2P" },
+
+ { "HDMI", NULL, "LINEOUT1N" },
+ { "HDMI", NULL, "LINEOUT1P" },
+
+ { "IN2LP:VXRN", NULL, "Main Mic" },
+ { "IN2LN", NULL, "Main Mic" },
+
+ { "IN1RP", NULL, "Sub Mic" },
+ { "IN1RN", NULL, "Sub Mic" },
+
+ { "IN1LP", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+ { "IN1LN", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+
+ { "AIF1DAC1L", NULL, "S5P RP" },
+ { "AIF1DAC1R", NULL, "S5P RP" },
+
+ { "IN2RN", NULL, "FM In" },
+ { "IN2RP:VXRP", NULL, "FM In" },
+};
+
+static struct snd_soc_dai_driver m3_ext_dai[] = {
+ {
+ .name = "m3.cp",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "m3.bt",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+#ifndef CONFIG_SEC_DEV_JACK
+static ssize_t earjack_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) ||
+ (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) {
+ report = 1;
+ }
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_key_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0)
+ report = 1;
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_key_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_select_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __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);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ m3_micd_set_rate(codec);
+
+ if ((!size) || (buf[0] != '1')) {
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ 0, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced remove microphone\n");
+ } else {
+
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ SND_JACK_HEADSET, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced detect microphone\n");
+ }
+
+ return size;
+}
+
+static ssize_t reselect_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __func__);
+ return 0;
+}
+
+static ssize_t reselect_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 reg = 0;
+
+ reg = snd_soc_read(codec, WM8958_MIC_DETECT_3);
+ if (reg == 0x402) {
+ dev_info(codec->dev, "Detected open circuit\n");
+
+ snd_soc_update_bits(codec, WM8958_MICBIAS2,
+ WM8958_MICB2_DISCH, WM8958_MICB2_DISCH);
+ /* Enable debounce while removed */
+ snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
+ WM1811_JACKDET_DB, WM1811_JACKDET_DB);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = false;
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ } else {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_JACK);
+ }
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ SND_JACK_MECHANICAL | SND_JACK_HEADSET |
+ wm8994->btn_mask);
+ }
+ return size;
+}
+
+static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ reselect_jack_show, reselect_jack_store);
+
+static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_select_jack_show, earjack_select_jack_store);
+
+static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_key_state_show, earjack_key_state_store);
+
+static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_state_show, earjack_state_store);
+#endif
+
+static int m3_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+ struct snd_soc_dai *aif1_dai = rtd->codec_dai;
+ struct wm8994 *control = codec->control_data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ midas_snd_set_mclk(true, false);
+
+ rtd->codec_dai->driver->playback.channels_max =
+ rtd->cpu_dai->driver->playback.channels_max;
+
+ ret = snd_soc_add_controls(codec, m3_controls,
+ ARRAY_SIZE(m3_controls));
+
+ ret = snd_soc_dapm_new_controls(&codec->dapm, m3_dapm_widgets,
+ ARRAY_SIZE(m3_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, m3_dapm_routes,
+ ARRAY_SIZE(m3_dapm_routes));
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret);
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2, 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 */
+ if (wm8994->revision > 1) {
+ ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n",
+ ret);
+ }
+
+ ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret);
+
+ 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, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic");
+ 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");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI");
+
+ wm1811->codec = codec;
+
+ m3_micd_set_rate(codec);
+
+#ifdef CONFIG_SEC_DEV_JACK
+ /* By default use idle_bias_off, will override for WM8994 */
+ codec->dapm.idle_bias_off = 0;
+#else /* CONFIG_SEC_DEV_JACK */
+ wm1811->jack.status = 0;
+
+ ret = snd_soc_jack_new(codec, "m3 Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &wm1811->jack);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to create jack: %d\n", ret);
+
+ ret = snd_jack_set_key(wm1811->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(wm1811->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(wm1811->jack.jack, SND_JACK_BTN_2,
+ KEY_VOLUMEUP);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret);
+
+ if (wm8994->revision > 1) {
+ dev_info(codec->dev, "wm1811: Rev %c support mic detection\n",
+ 'A' + wm8994->revision);
+ ret = wm8958_mic_detect(codec, &wm1811->jack, m3_micdet,
+ wm1811);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed start detection: %d\n",
+ ret);
+ } else {
+ dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n",
+ 'A' + wm8994->revision);
+ codec->dapm.idle_bias_off = 0;
+ }
+ /* To wakeup for earjack event in suspend mode */
+ enable_irq_wake(control->irq);
+
+ wake_lock_init(&wm1811->jackdet_wake_lock,
+ WAKE_LOCK_SUSPEND, "m3_jackdet");
+
+ /* To support PBA function test */
+ jack_class = class_create(THIS_MODULE, "audio");
+
+ if (IS_ERR(jack_class))
+ pr_err("Failed to create class\n");
+
+ jack_dev = device_create(jack_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);
+
+ if (device_create_file(jack_dev, &dev_attr_key_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_key_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_reselect_jack.attr.name);
+
+#endif /* CONFIG_SEC_DEV_JACK */
+ return snd_soc_dapm_sync(&codec->dapm);
+}
+
+static struct snd_soc_dai_link m3_dai[] = {
+ { /* Sec_Fifo DAI i/f */
+ .name = "Sec_FIFO TX",
+ .stream_name = "Sec_Dai",
+ .cpu_dai_name = "samsung-i2s.4",
+ .codec_dai_name = "wm8994-aif1",
+#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER
+ .platform_name = "samsung-audio-idma",
+#else
+ .platform_name = "samsung-audio",
+#endif
+ .codec_name = "wm8994-codec",
+ .init = m3_wm1811_init_paiftx,
+ .ops = &m3_wm1811_aif1_ops,
+ },
+ {
+ .name = "m3_WM1811 Voice",
+ .stream_name = "Voice Tx/Rx",
+ .cpu_dai_name = "m3.cp",
+ .codec_dai_name = "wm8994-aif2",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &m3_wm1811_aif2_ops,
+ .ignore_suspend = 1,
+ },
+ {
+ .name = "m3_WM1811 BT",
+ .stream_name = "BT Tx/Rx",
+ .cpu_dai_name = "m3.bt",
+ .codec_dai_name = "wm8994-aif3",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &m3_wm1811_aif3_ops,
+ .ignore_suspend = 1,
+ },
+ { /* Primary DAI i/f */
+ .name = "WM8994 AIF1",
+ .stream_name = "Pri_Dai",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec",
+ .ops = &m3_wm1811_aif1_ops,
+ },
+};
+
+static int m3_card_suspend_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ if (lineout_mode == 1 &&
+ wm8994->vmid_mode == WM8994_VMID_FORCE) {
+ dev_info(codec->dev,
+ "%s: entering force vmid mode\n", __func__);
+ gpio_set_value(GPIO_VPS_SOUND_EN, 0);
+ wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
+ }
+#endif
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+static int m3_card_suspend_post(struct snd_soc_card *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) {
+ ret = snd_soc_dai_set_sysclk(aif2_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch 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\n");
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch to MCLK2\n");
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to stop FLL1\n");
+
+ ret = snd_soc_dai_set_fmt(aif1_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to set I2S SLAVE MODE\n");
+
+ midas_snd_set_mclk(false, true);
+ }
+
+#ifdef CONFIG_ARCH_EXYNOS5
+ exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0);
+#else /* for CONFIG_ARCH_EXYNOS5 */
+ exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0);
+#endif
+
+ return 0;
+}
+
+static int m3_card_resume_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
+ int ret;
+
+ midas_snd_set_mclk(true, false);
+
+ /* Switch the FLL */
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1,
+ MIDAS_DEFAULT_SYNC_CLK);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret);
+
+ /* Then switch AIF1CLK to it */
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_FLL1,
+ MIDAS_DEFAULT_SYNC_CLK,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret);
+
+ ret = snd_soc_dai_set_fmt(aif1_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to set I2S MASTER MODE\n");
+
+ return 0;
+}
+
+static int m3_card_resume_post(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ if (lineout_mode == 1 &&
+ wm8994->vmid_mode == WM8994_VMID_NORMAL) {
+ dev_info(codec->dev,
+ "%s: entering normal vmid mode\n", __func__);
+ wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
+ gpio_set_value(GPIO_VPS_SOUND_EN, 1);
+ }
+#endif
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+static struct snd_soc_card m3_card = {
+ .name = "m3_WM1811",
+ .dai_link = m3_dai,
+
+ /* If you want to use sec_fifo device,
+ * changes the num_link = 2 or ARRAY_SIZE(m3_dai). */
+ .num_links = ARRAY_SIZE(m3_dai),
+
+ .suspend_pre = m3_card_suspend_pre,
+ .suspend_post = m3_card_suspend_post,
+ .resume_pre = m3_card_resume_pre,
+ .resume_post = m3_card_resume_post
+};
+
+static struct platform_device *m3_snd_device;
+
+static int __init m3_audio_init(void)
+{
+ struct wm1811_machine_priv *wm1811;
+ int ret;
+
+ wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL);
+ if (!wm1811) {
+ pr_err("Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+ snd_soc_card_set_drvdata(&m3_card, wm1811);
+
+ m3_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!m3_snd_device) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ ret = snd_soc_register_dais(&m3_snd_device->dev, m3_ext_dai,
+ ARRAY_SIZE(m3_ext_dai));
+ if (ret != 0)
+ pr_err("Failed to register external DAIs: %d\n", ret);
+
+ platform_set_drvdata(m3_snd_device, &m3_card);
+
+ ret = platform_device_add(m3_snd_device);
+ if (ret)
+ platform_device_put(m3_snd_device);
+
+ m3_gpio_init();
+
+ return ret;
+
+err_device_alloc:
+ kfree(wm1811);
+err_kzalloc:
+ return ret;
+}
+module_init(m3_audio_init);
+
+static void __exit m3_audio_exit(void)
+{
+ struct snd_soc_card *card = &m3_card;
+ struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card);
+ platform_device_unregister(m3_snd_device);
+ kfree(wm1811);
+}
+module_exit(m3_audio_exit);
+
+MODULE_AUTHOR("Uk Kim <w0806.kim@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC m3 WM1811");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c
index dd8a43a..1f5becc 100644
--- a/sound/soc/samsung/midas_wm1811.c
+++ b/sound/soc/samsung/midas_wm1811.c
@@ -28,10 +28,14 @@
#include <mach/regs-clock.h>
#include <mach/pmu.h>
#include <mach/midas-sound.h>
+#ifdef CONFIG_MACH_GC1
+#include <mach/gc1-jack.h>
+#endif
#include <linux/mfd/wm8994/core.h>
#include <linux/mfd/wm8994/registers.h>
#include <linux/mfd/wm8994/pdata.h>
+#include <linux/mfd/wm8994/gpio.h>
#if defined(CONFIG_SND_USE_MUIC_SWITCH)
#include <linux/mfd/max77693-private.h>
@@ -64,19 +68,28 @@ static struct wm8958_micd_rate midas_det_rates[] = {
{ MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 },
};
+#ifdef CONFIG_MACH_GC1
+static struct wm8958_micd_rate midas_jackdet_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 10, 10 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 },
+};
+#else
static struct wm8958_micd_rate midas_jackdet_rates[] = {
{ MIDAS_DEFAULT_MCLK2, true, 0, 0 },
{ MIDAS_DEFAULT_MCLK2, false, 0, 0 },
{ MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 },
{ MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 },
};
+#endif
static int aif2_mode;
const char *aif2_mode_text[] = {
"Slave", "Master"
};
-static int kpcs_mode = 1;
+static int kpcs_mode = 2;
const char *kpcs_mode_text[] = {
"Off", "On"
};
@@ -109,6 +122,28 @@ struct wm1811_machine_priv {
struct wake_lock jackdet_wake_lock;
};
+#ifdef CONFIG_MACH_GC1
+static struct snd_soc_codec *wm1811_codec;
+
+void set_wm1811_micbias2(bool on)
+{
+ if (wm1811_codec == NULL) {
+ pr_err(KERN_ERR "WM1811 MICBIAS2 set error!\n");
+ return;
+ }
+
+ if (on) {
+ snd_soc_update_bits(wm1811_codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_MICB2_ENA, WM8994_MICB2_ENA);
+ } else {
+ snd_soc_update_bits(wm1811_codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_MICB2_ENA, 0);
+
+ }
+return;
+}
+EXPORT_SYMBOL(set_wm1811_micbias2);
+#endif
static void midas_gpio_init(void)
{
@@ -190,16 +225,6 @@ static int set_lineout_mode(struct snd_kcontrol *kcontrol,
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
lineout_mode = ucontrol->value.integer.value[0];
-
-#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
- if (lineout_mode) {
- wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
- gpio_set_value(GPIO_LINEOUT_EN, 1);
- } else {
- wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
- gpio_set_value(GPIO_LINEOUT_EN, 0);
- }
-#endif
dev_dbg(codec->dev, "set lineout mode : %s\n",
lineout_mode_text[lineout_mode]);
return 0;
@@ -250,7 +275,7 @@ static int set_kpcs_mode(struct snd_kcontrol *kcontrol,
kpcs_mode = ucontrol->value.integer.value[0];
- pr_info("set kpcs mode : %s\n", kpcs_mode_text[kpcs_mode]);
+ pr_info("set kpcs mode : %d\n", kpcs_mode);
return 0;
}
@@ -374,7 +399,7 @@ static int midas_lineout_switch(struct snd_soc_dapm_widget *w,
#if defined(CONFIG_SND_USE_MUIC_SWITCH)
switch (event) {
case SND_SOC_DAPM_POST_PMU:
- msleep(50);
+ msleep(150);
max77693_muic_set_audio_switch(1);
break;
case SND_SOC_DAPM_PRE_PMD:
@@ -383,6 +408,16 @@ static int midas_lineout_switch(struct snd_soc_dapm_widget *w,
}
#endif
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ gpio_set_value(GPIO_LINEOUT_EN, 1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ gpio_set_value(GPIO_LINEOUT_EN, 0);
+ break;
+ }
+#endif
return 0;
}
@@ -795,7 +830,7 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream,
}
#if defined(CONFIG_LTE_MODEM_CMC221) || defined(CONFIG_MACH_M0_CTC)
-#if defined(CONFIG_MACH_C1_KOR_LGT) || defined(CONFIG_MACH_C1VZW)
+#if defined(CONFIG_MACH_C1_KOR_LGT)
/* Set the codec DAI configuration */
if (aif2_mode == 0) {
if (kpcs_mode == 1)
@@ -837,7 +872,7 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream,
if (ret < 0)
return ret;
-#if defined(CONFIG_LTE_MODEM_CMC221) || defined(CONFIG_MACH_M0_CTC)
+#if defined(CONFIG_LTE_MODEM_CMC221)
if (kpcs_mode == 1) {
switch (prate) {
case 8000:
@@ -852,6 +887,8 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream,
} else {
bclk = 2048000;
}
+#elif defined(CONFIG_MACH_M0_CTC)
+ bclk = 2048000;
#else
switch (prate) {
case 8000:
@@ -1171,9 +1208,9 @@ static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP,
static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
{
-
- struct wm1811_machine_priv *wm1811;
struct snd_soc_codec *codec = rtd->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
struct snd_soc_dai *aif1_dai = rtd->codec_dai;
struct wm8994 *control = codec->control_data;
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
@@ -1183,6 +1220,10 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
midas_aif1_dai = aif1_dai;
#endif
+#ifdef CONFIG_MACH_GC1
+ wm1811_codec = codec;
+#endif
+
midas_snd_set_mclk(true, false);
rtd->codec_dai->driver->playback.channels_max =
@@ -1205,7 +1246,7 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(codec->dev, "Failed to boot clocking\n");
-#ifndef CONFIG_SEC_DEV_JACK
+
/* Force AIF1CLK on as it will be master for jack detection */
if (wm8994->revision > 1) {
ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
@@ -1213,17 +1254,11 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n",
ret);
}
-#endif
+
ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP");
if (ret < 0)
dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret);
- wm1811 = kmalloc(sizeof *wm1811, GFP_KERNEL);
- if (!wm1811) {
- dev_err(codec->dev, "Failed to allocate memory!");
- return -ENOMEM;
- }
-
snd_soc_dapm_ignore_suspend(&codec->dapm, "RCV");
snd_soc_dapm_ignore_suspend(&codec->dapm, "SPK");
snd_soc_dapm_ignore_suspend(&codec->dapm, "HP");
@@ -1371,17 +1406,11 @@ static struct snd_soc_dai_link midas_dai[] = {
static int midas_card_suspend_pre(struct snd_soc_card *card)
{
-#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
struct snd_soc_codec *codec = card->rtd->codec;
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
- if (lineout_mode == 1 &&
- wm8994->vmid_mode == WM8994_VMID_FORCE) {
- dev_dbg(codec->dev,
- "%s: entering force vmid mode\n", __func__);
- gpio_set_value(GPIO_LINEOUT_EN, 0);
- wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
- }
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK");
#endif
return 0;
@@ -1468,17 +1497,23 @@ static int midas_card_resume_pre(struct snd_soc_card *card)
static int midas_card_resume_post(struct snd_soc_card *card)
{
-#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
struct snd_soc_codec *codec = card->rtd->codec;
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int reg = 0;
- if (lineout_mode == 1 &&
- wm8994->vmid_mode == WM8994_VMID_NORMAL) {
- dev_dbg(codec->dev,
- "%s: entering normal vmid mode\n", __func__);
- wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
- gpio_set_value(GPIO_LINEOUT_EN, 1);
+ /* 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);
}
+
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
#endif
return 0;
@@ -1595,11 +1630,22 @@ static struct platform_device *midas_snd_device;
static int __init midas_audio_init(void)
{
+ struct wm1811_machine_priv *wm1811;
int ret;
+ wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL);
+ if (!wm1811) {
+ pr_err("Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+ snd_soc_card_set_drvdata(&midas, wm1811);
+
midas_snd_device = platform_device_alloc("soc-audio", -1);
- if (!midas_snd_device)
- return -ENOMEM;
+ if (!midas_snd_device) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
ret = snd_soc_register_dais(&midas_snd_device->dev, midas_ext_dai,
ARRAY_SIZE(midas_ext_dai));
@@ -1615,12 +1661,20 @@ static int __init midas_audio_init(void)
midas_gpio_init();
return ret;
+
+err_device_alloc:
+ kfree(wm1811);
+err_kzalloc:
+ return ret;
}
module_init(midas_audio_init);
static void __exit midas_audio_exit(void)
{
+ struct snd_soc_card *card = &midas;
+ struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card);
platform_device_unregister(midas_snd_device);
+ kfree(wm1811);
}
module_exit(midas_audio_exit);
diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c
index af4b521..f4f6adf 100644
--- a/sound/soc/samsung/pcm.c
+++ b/sound/soc/samsung/pcm.c
@@ -547,6 +547,8 @@ EXPORT_SYMBOL_GPL(s3c_pcm_dai);
static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev)
{
+ struct clk *mout_epll = NULL;
+ struct clk *sclk_audio = NULL;
struct s3c_pcm_info *pcm;
struct resource *mem_res, *dmatx_res, *dmarx_res;
struct s3c_audio_pdata *pcm_pdata;
@@ -592,6 +594,37 @@ static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev)
/* Default is 128fs */
pcm->sclk_per_fs = 128;
+ /* Clock configuration */
+ mout_epll = clk_get(&pdev->dev, "mout_epll");
+ if (IS_ERR(mout_epll)) {
+ dev_err(&pdev->dev, "failed to get mout_epll\n");
+ ret = PTR_ERR(mout_epll);
+ return ret;
+ }
+
+ switch (pdev->id) {
+ case 0:
+ sclk_audio = clk_get(&pdev->dev, "audio-bus");
+ break;
+ case 1:
+ sclk_audio = clk_get(&pdev->dev, "audio-bus1");
+ break;
+ case 2:
+ sclk_audio = clk_get(&pdev->dev, "audio-bus2");
+ break;
+ default:
+ dev_err(&pdev->dev, "Not support device num\n");
+ break;
+ }
+
+ if (IS_ERR(sclk_audio)) {
+ dev_err(&pdev->dev, "failed to get sclk_audio\n");
+ ret = PTR_ERR(sclk_audio);
+ goto err;
+ }
+
+ clk_set_parent(sclk_audio, mout_epll);
+
pcm->cclk = clk_get(&pdev->dev, "audio-bus");
if (IS_ERR(pcm->cclk)) {
dev_err(&pdev->dev, "failed to get audio-bus\n");
@@ -642,6 +675,9 @@ static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev)
pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id];
pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id];
+ clk_put(mout_epll);
+ clk_put(sclk_audio);
+
return 0;
err5:
@@ -655,6 +691,9 @@ err2:
clk_disable(pcm->cclk);
clk_put(pcm->cclk);
err1:
+ clk_put(sclk_audio);
+err:
+ clk_put(mout_epll);
return ret;
}
diff --git a/sound/soc/samsung/t0_wm1811.c b/sound/soc/samsung/t0_wm1811.c
new file mode 100644
index 0000000..9a0ac52
--- /dev/null
+++ b/sound/soc/samsung/t0_wm1811.c
@@ -0,0 +1,1393 @@
+/*
+ * t0_wm1811.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co. Ltd
+ *
+ * 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.
+ */
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+#include <linux/wakelock.h>
+#include <linux/suspend.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+
+#include <mach/regs-clock.h>
+#include <mach/pmu.h>
+#include <mach/midas-sound.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/exynos_audio.h>
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+#include <linux/mfd/max77693-private.h>
+#endif
+
+
+#include "i2s.h"
+#include "s3c-i2s-v2.h"
+#include "../codecs/wm8994.h"
+
+
+#define MIDAS_DEFAULT_MCLK1 24000000
+#define MIDAS_DEFAULT_MCLK2 32768
+#define MIDAS_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 WM1811_MIC_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_MIC1_DET)
+#define WM1811_JACKDET_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_GPIO(6))
+
+static struct wm8958_micd_rate t0_det_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 },
+};
+
+static struct wm8958_micd_rate t0_jackdet_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 },
+};
+
+static int aif2_mode;
+const char *aif2_mode_text[] = {
+ "Slave", "Master"
+};
+
+static int input_clamp;
+const char *input_clamp_text[] = {
+ "Off", "On"
+};
+
+static int lineout_mode;
+const char *lineout_mode_text[] = {
+ "Off", "On"
+};
+
+#ifndef CONFIG_SEC_DEV_JACK
+/* To support PBA function test */
+static struct class *jack_class;
+static struct device *jack_dev;
+#endif
+
+struct wm1811_machine_priv {
+ struct snd_soc_jack jack;
+ struct snd_soc_codec *codec;
+ struct wake_lock jackdet_wake_lock;
+ void (*lineout_switch_f) (int on);
+ void (*set_main_mic_f) (int on);
+ void (*set_sub_mic_f) (int on);
+ int (*get_g_det_value_f) (void);
+ int (*get_g_det_irq_num_f) (void);
+};
+
+static const struct soc_enum lineout_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text),
+};
+
+static int get_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = lineout_mode;
+ return 0;
+}
+
+static int set_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ lineout_mode = ucontrol->value.integer.value[0];
+
+ if (lineout_mode) {
+ wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(1);
+ } else {
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(0);
+ wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
+ }
+
+ dev_info(codec->dev, "set lineout mode : %s\n",
+ lineout_mode_text[lineout_mode]);
+ return 0;
+
+}
+static const struct soc_enum aif2_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text),
+};
+
+static const struct soc_enum input_clamp_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_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];
+
+ pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]);
+
+ return 0;
+}
+
+static int get_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = input_clamp;
+ return 0;
+}
+
+static int set_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ input_clamp = ucontrol->value.integer.value[0];
+
+ if (input_clamp) {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP);
+ msleep(100);
+ } else {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, 0);
+ }
+ pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]);
+
+ return 0;
+}
+
+
+static int set_ext_micbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (wm1811->set_main_mic_f)
+ wm1811->set_main_mic_f(1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ if (wm1811->set_main_mic_f)
+ wm1811->set_main_mic_f(0);
+ break;
+ }
+
+ return 0;
+}
+
+static int set_ext_submicbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (wm1811->set_sub_mic_f)
+ wm1811->set_sub_mic_f(1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ if (wm1811->set_sub_mic_f)
+ wm1811->set_sub_mic_f(0);
+ break;
+ }
+
+ return 0;
+}
+
+static int set_muic_switch(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ msleep(150);
+ max77693_muic_set_audio_switch(1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ max77693_muic_set_audio_switch(0);
+ break;
+ }
+#endif
+
+ return 0;
+}
+
+static void t0_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 = t0_jackdet_rates;
+ num_rates = ARRAY_SIZE(t0_jackdet_rates);
+ wm8994->pdata->micd_rates = t0_jackdet_rates;
+ wm8994->pdata->num_micd_rates = num_rates;
+ } else {
+ rates = t0_det_rates;
+ num_rates = ARRAY_SIZE(t0_det_rates);
+ wm8994->pdata->micd_rates = t0_det_rates;
+ wm8994->pdata->num_micd_rates = num_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 t0_micdet(u16 status, void *data)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+ int report;
+
+ /*
+ * If the jack is inserted abnormally,
+ * The variable puts back to its previous status.
+ */
+ if (!wm1811->get_g_det_value_f) {
+ dev_err(wm1811->codec->dev, "Do not use the ground detection\n");
+ } else {
+ if (wm1811->get_g_det_value_f()) {
+ dev_info(wm1811->codec->dev, "The jack is inserted abnormally\n");
+
+ wm8994->mic_detecting = false;
+ }
+ }
+
+ wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ);
+
+ /* Either nothing present or just starting detection */
+ if (!(status & WM8958_MICD_STS)) {
+ if (!wm8994->jackdet) {
+ /* If nothing present then clear our statuses */
+ dev_dbg(wm1811->codec->dev, "Detected open circuit\n");
+ wm8994->jack_mic = false;
+ wm8994->mic_detecting = true;
+
+ t0_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ wm8994->btn_mask |
+ SND_JACK_HEADSET);
+ }
+ /*ToDo*/
+ /*return;*/
+ }
+
+ /* If the measurement is showing a high impedence we've got a
+ * microphone.
+ */
+ if (wm8994->mic_detecting && (status & 0x400)) {
+ dev_info(wm1811->codec->dev, "Detected microphone\n");
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ t0_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET,
+ SND_JACK_HEADSET);
+ }
+
+ if (wm8994->mic_detecting && status & 0x4) {
+ dev_info(wm1811->codec->dev, "Detected headphone\n");
+ wm8994->mic_detecting = false;
+
+ t0_micd_set_rate(wm1811->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(wm1811->codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(wm1811->codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ }
+
+ mutex_unlock(&wm8994->accdet_lock);
+
+ 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);
+ }
+ }
+ }
+
+ /* 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;
+
+ dev_info(wm1811->codec->dev, "Detected Button: %08x (%08X)\n",
+ report, status);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, report,
+ wm8994->btn_mask);
+ }
+}
+
+static int t0_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ /* AIF1CLK should be >=3MHz for optimal performance */
+ 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 the FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1,
+ 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)
+ return ret;
+
+ dev_info(codec_dai->dev, "%s --\n", __func__);
+
+ return 0;
+}
+
+/*
+ * T0 WM1811 DAI operations.
+ */
+static struct snd_soc_ops t0_wm1811_aif1_ops = {
+ .hw_params = t0_wm1811_aif1_hw_params,
+};
+
+static int t0_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ 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 8000Hz\n",
+ (int)params_rate(params));
+ prate = 8000;
+ }
+
+ /* Set the codec DAI configuration, aif2_mode:0 is slave */
+ if (aif2_mode == 0)
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ 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;
+
+ switch (prate) {
+ case 8000:
+ bclk = 256000;
+ break;
+ case 16000:
+ bclk = 512000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (aif2_mode == 0) {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_BCLK,
+ bclk, prate * 256);
+ } else {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1, 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, "%s --\n", __func__);
+ return 0;
+}
+
+static struct snd_soc_ops t0_wm1811_aif2_ops = {
+ .hw_params = t0_wm1811_aif2_hw_params,
+};
+
+static int t0_wm1811_aif3_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ pr_err("%s: enter\n", __func__);
+ return 0;
+}
+
+static bool playback_stream_status;
+static bool capture_stream_status;
+
+static int t0_wm1811_aif3_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int base = WM8994_GPIO_8 - WM8994_GPIO_1;
+ int i;
+
+ pr_err("%s: enter\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ capture_stream_status = 1;
+ else
+ playback_stream_status = 1;
+
+ for (i = 0; i < 4; i++) {
+ if (pdata->gpio_defaults[base + i]) {
+ snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i,
+ 0xffff,
+ pdata->gpio_defaults[base + i]);
+ }
+ }
+ return 0;
+}
+
+static void t0_wm1811_aif3_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ pr_err("%s: enter, stream=%d\n", __func__, substream->stream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ capture_stream_status = 0;
+ else
+ playback_stream_status = 0;
+
+ if (playback_stream_status || capture_stream_status)
+ return;
+
+ pr_info("%s: set input gpios for AIF3\n", __func__);
+
+ for (i = 0; i < 4; i++) {
+ snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i,
+ 0xffff,
+ 0xA101);
+ }
+ return;
+}
+
+static struct snd_soc_ops t0_wm1811_aif3_ops = {
+ .startup = t0_wm1811_aif3_startup,
+ .shutdown = t0_wm1811_aif3_shutdown,
+ .hw_params = t0_wm1811_aif3_hw_params,
+};
+
+static const struct snd_kcontrol_new t0_controls[] = {
+ SOC_DAPM_PIN_SWITCH("HP"),
+ SOC_DAPM_PIN_SWITCH("SPK"),
+ SOC_DAPM_PIN_SWITCH("RCV"),
+ SOC_DAPM_PIN_SWITCH("FM In"),
+ 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"),
+
+ SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0],
+ get_aif2_mode, set_aif2_mode),
+
+ SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0],
+ get_input_clamp, set_input_clamp),
+
+ SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0],
+ get_lineout_mode, set_lineout_mode),
+
+};
+
+const struct snd_soc_dapm_widget t0_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+ SND_SOC_DAPM_SPK("RCV", NULL),
+ SND_SOC_DAPM_LINE("LINE", set_muic_switch),
+ SND_SOC_DAPM_LINE("HDMI", NULL),
+
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", set_ext_micbias),
+ SND_SOC_DAPM_MIC("Sub Mic", set_ext_submicbias),
+ SND_SOC_DAPM_LINE("FM In", NULL),
+
+ SND_SOC_DAPM_INPUT("S5P RP"),
+};
+
+const struct snd_soc_dapm_route t0_dapm_routes[] = {
+ { "HP", NULL, "HPOUT1L" },
+ { "HP", NULL, "HPOUT1R" },
+
+ { "SPK", NULL, "SPKOUTLN" },
+ { "SPK", NULL, "SPKOUTLP" },
+
+ { "RCV", NULL, "HPOUT2N" },
+ { "RCV", NULL, "HPOUT2P" },
+
+ { "LINE", NULL, "LINEOUT2N" },
+ { "LINE", NULL, "LINEOUT2P" },
+
+ { "HDMI", NULL, "LINEOUT1N" },
+ { "HDMI", NULL, "LINEOUT1P" },
+
+ { "IN2LP:VXRN", NULL, "Main Mic" },
+ { "IN2LN", NULL, "Main Mic" },
+
+ { "IN1RP", NULL, "MICBIAS1" },
+ { "IN1RN", NULL, "MICBIAS1" },
+ { "MICBIAS1", NULL, "Sub Mic" },
+
+ { "IN1LP", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+ { "IN1LN", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+
+ { "AIF1DAC1L", NULL, "S5P RP" },
+ { "AIF1DAC1R", NULL, "S5P RP" },
+
+ { "IN2RN", NULL, "FM In" },
+ { "IN2RP:VXRP", NULL, "FM In" },
+};
+
+static struct snd_soc_dai_driver t0_ext_dai[] = {
+ {
+ .name = "t0.cp",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "t0.bt",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+#ifndef CONFIG_SEC_DEV_JACK
+static ssize_t earjack_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) ||
+ (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) {
+ report = 1;
+ }
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_key_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0)
+ report = 1;
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_key_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_select_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __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);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ t0_micd_set_rate(codec);
+
+ if ((!size) || (buf[0] != '1')) {
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ 0, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced remove microphone\n");
+ } else {
+
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ SND_JACK_HEADSET, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced detect microphone\n");
+ }
+
+ return size;
+}
+
+static ssize_t reselect_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __func__);
+ return 0;
+}
+
+static ssize_t reselect_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 reg = 0;
+
+ reg = snd_soc_read(codec, WM8958_MIC_DETECT_3);
+ if (reg == 0x402) {
+ dev_info(codec->dev, "Detected open circuit\n");
+
+ snd_soc_update_bits(codec, WM8958_MICBIAS2,
+ WM8958_MICB2_DISCH, WM8958_MICB2_DISCH);
+ /* Enable debounce while removed */
+ snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
+ WM1811_JACKDET_DB, WM1811_JACKDET_DB);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = false;
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ } else {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_JACK);
+ }
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ SND_JACK_MECHANICAL | SND_JACK_HEADSET |
+ wm8994->btn_mask);
+ }
+ return size;
+}
+
+static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ reselect_jack_show, reselect_jack_store);
+
+static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_select_jack_show, earjack_select_jack_store);
+
+static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_key_state_show, earjack_key_state_store);
+
+static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_state_show, earjack_state_store);
+#endif
+
+static int t0_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+ struct snd_soc_dai *aif1_dai = rtd->codec_dai;
+ struct wm8994 *control = codec->control_data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ const struct exynos_sound_platform_data *sound_pdata;
+ int ret;
+
+ midas_snd_set_mclk(true, false);
+
+ rtd->codec_dai->driver->playback.channels_max =
+ rtd->cpu_dai->driver->playback.channels_max;
+
+ ret = snd_soc_add_controls(codec, t0_controls,
+ ARRAY_SIZE(t0_controls));
+
+ ret = snd_soc_dapm_new_controls(&codec->dapm, t0_dapm_widgets,
+ ARRAY_SIZE(t0_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, t0_dapm_routes,
+ ARRAY_SIZE(t0_dapm_routes));
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret);
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2, 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 */
+ if (wm8994->revision > 1) {
+ ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n",
+ ret);
+ }
+
+ ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret);
+
+ 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, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic");
+ 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");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI");
+
+ wm1811->codec = codec;
+
+ t0_micd_set_rate(codec);
+
+#ifdef CONFIG_SEC_DEV_JACK
+ /* By default use idle_bias_off, will override for WM8994 */
+ codec->dapm.idle_bias_off = 0;
+#else /* CONFIG_SEC_DEV_JACK */
+ wm1811->jack.status = 0;
+
+ ret = snd_soc_jack_new(codec, "T0 Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &wm1811->jack);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to create jack: %d\n", ret);
+
+ ret = snd_jack_set_key(wm1811->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(wm1811->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(wm1811->jack.jack, SND_JACK_BTN_2,
+ KEY_VOLUMEUP);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret);
+
+ if (wm8994->revision > 1) {
+ dev_info(codec->dev, "wm1811: Rev %c support mic detection\n",
+ 'A' + wm8994->revision);
+ ret = wm8958_mic_detect(codec, &wm1811->jack, t0_micdet,
+ wm1811);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed start detection: %d\n",
+ ret);
+ } else {
+ dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n",
+ 'A' + wm8994->revision);
+ codec->dapm.idle_bias_off = 0;
+ }
+ /* To wakeup for earjack event in suspend mode */
+ enable_irq_wake(control->irq);
+
+ wake_lock_init(&wm1811->jackdet_wake_lock,
+ WAKE_LOCK_SUSPEND, "T0_jackdet");
+
+ /* To support PBA function test */
+ jack_class = class_create(THIS_MODULE, "audio");
+
+ if (IS_ERR(jack_class))
+ pr_err("Failed to create class\n");
+
+ jack_dev = device_create(jack_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);
+
+ if (device_create_file(jack_dev, &dev_attr_key_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_key_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_reselect_jack.attr.name);
+
+#endif /* CONFIG_SEC_DEV_JACK */
+ sound_pdata = exynos_sound_get_platform_data();
+
+ if (sound_pdata) {
+ wm8994->hubs.dcs_codes_l = sound_pdata->dcs_offset_l;
+ wm8994->hubs.dcs_codes_r = sound_pdata->dcs_offset_r;
+ }
+
+ return snd_soc_dapm_sync(&codec->dapm);
+}
+
+static struct snd_soc_dai_link t0_dai[] = {
+ { /* Sec_Fifo DAI i/f */
+ .name = "Sec_FIFO TX",
+ .stream_name = "Sec_Dai",
+ .cpu_dai_name = "samsung-i2s.4",
+ .codec_dai_name = "wm8994-aif1",
+#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER
+ .platform_name = "samsung-audio-idma",
+#else
+ .platform_name = "samsung-audio",
+#endif
+ .codec_name = "wm8994-codec",
+ .init = t0_wm1811_init_paiftx,
+ .ops = &t0_wm1811_aif1_ops,
+ },
+ {
+ .name = "T0_WM1811 Voice",
+ .stream_name = "Voice Tx/Rx",
+ .cpu_dai_name = "t0.cp",
+ .codec_dai_name = "wm8994-aif2",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &t0_wm1811_aif2_ops,
+ .ignore_suspend = 1,
+ },
+ {
+ .name = "T0_WM1811 BT",
+ .stream_name = "BT Tx/Rx",
+ .cpu_dai_name = "t0.bt",
+ .codec_dai_name = "wm8994-aif3",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &t0_wm1811_aif3_ops,
+ .ignore_suspend = 1,
+ },
+ { /* Primary DAI i/f */
+ .name = "WM8994 AIF1",
+ .stream_name = "Pri_Dai",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec",
+ .ops = &t0_wm1811_aif1_ops,
+ },
+};
+
+static int t0_card_suspend_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+ if (lineout_mode == 1 &&
+ wm8994->vmid_mode == WM8994_VMID_FORCE) {
+ dev_info(codec->dev,
+ "%s: entering force vmid mode\n", __func__);
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(0);
+ wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
+ }
+#endif
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+static int t0_card_suspend_post(struct snd_soc_card *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) {
+ ret = snd_soc_dai_set_sysclk(aif2_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch 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\n");
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch to MCLK2\n");
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to stop FLL1\n");
+
+ midas_snd_set_mclk(false, true);
+ }
+
+#ifdef CONFIG_ARCH_EXYNOS5
+ exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0);
+#else /* for CONFIG_ARCH_EXYNOS5 */
+ exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0);
+#endif
+
+ return 0;
+}
+
+static int t0_card_resume_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
+ int ret;
+
+ midas_snd_set_mclk(true, false);
+
+ /* Switch the FLL */
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1,
+ MIDAS_DEFAULT_SYNC_CLK);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret);
+
+ /* Then switch AIF1CLK to it */
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_FLL1,
+ MIDAS_DEFAULT_SYNC_CLK,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret);
+
+ return 0;
+}
+
+static int t0_card_resume_post(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int reg = 0;
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ if (lineout_mode == 1 &&
+ wm8994->vmid_mode == WM8994_VMID_NORMAL) {
+ dev_info(codec->dev,
+ "%s: entering normal vmid mode\n", __func__);
+ wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(1);
+ }
+#endif
+ /* 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);
+ }
+
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+static struct snd_soc_card t0_card = {
+ .name = "T0_WM1811",
+ .dai_link = t0_dai,
+
+ /* If you want to use sec_fifo device,
+ * changes the num_link = 2 or ARRAY_SIZE(t0_dai). */
+ .num_links = ARRAY_SIZE(t0_dai),
+
+ .suspend_pre = t0_card_suspend_pre,
+ .suspend_post = t0_card_suspend_post,
+ .resume_pre = t0_card_resume_pre,
+ .resume_post = t0_card_resume_post
+};
+
+static struct platform_device *t0_snd_device;
+
+static void t0_jackdet_set_mode(struct snd_soc_codec *codec, u16 mode)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (!wm8994->jackdet || !wm8994->jack_cb)
+ return;
+
+ if (wm8994->active_refcount)
+ mode = WM1811_JACKDET_MODE_AUDIO;
+
+ if (mode == wm8994->jackdet_mode)
+ return;
+
+ wm8994->jackdet_mode = mode;
+
+ /* Always use audio mode to detect while the system is active */
+ if (mode != WM1811_JACKDET_MODE_NONE)
+ mode = WM1811_JACKDET_MODE_AUDIO;
+
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK, mode);
+}
+
+static irqreturn_t t0_g_det_thread(int irq, void *data)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+ struct snd_soc_codec *codec = wm8994->codec;
+
+
+ if (wm1811->get_g_det_value_f()) {
+
+ pr_info("%s: G_DET_N GPIO is High!!!!", __func__);
+
+ mutex_lock(&wm8994->accdet_lock);
+
+ snd_soc_update_bits(codec, WM8958_MICBIAS2,
+ WM8958_MICB2_DISCH, WM8958_MICB2_DISCH);
+
+ /* Enable debounce while removed */
+ snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
+ WM1811_JACKDET_DB, WM1811_JACKDET_DB);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = false;
+
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ t0_jackdet_set_mode(codec, WM1811_JACKDET_MODE_JACK);
+
+ mutex_unlock(&wm8994->accdet_lock);
+
+ mutex_lock(&codec->mutex);
+
+ snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2");
+ snd_soc_dapm_sync(&codec->dapm);
+
+ mutex_unlock(&codec->mutex);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ SND_JACK_MECHANICAL | SND_JACK_HEADSET |
+ wm8994->btn_mask);
+ } else {
+ pr_info("%s: G_DET_N GPIO is Low!!!!", __func__);
+
+ handle_nested_irq(WM1811_JACKDET_IRQ_NUM);
+ msleep(100);
+ handle_nested_irq(WM1811_MIC_IRQ_NUM);
+ }
+
+ return IRQ_HANDLED;
+
+}
+
+static int __init t0_audio_init(void)
+{
+ struct wm1811_machine_priv *wm1811;
+ const struct exynos_sound_platform_data *sound_pdata;
+ int ret;
+
+ wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL);
+ if (!wm1811) {
+ pr_err("Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+ snd_soc_card_set_drvdata(&t0_card, wm1811);
+
+ t0_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!t0_snd_device) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ ret = snd_soc_register_dais(&t0_snd_device->dev, t0_ext_dai,
+ ARRAY_SIZE(t0_ext_dai));
+ if (ret != 0)
+ pr_err("Failed to register external DAIs: %d\n", ret);
+
+ platform_set_drvdata(t0_snd_device, &t0_card);
+
+ ret = platform_device_add(t0_snd_device);
+ if (ret)
+ platform_device_put(t0_snd_device);
+
+ sound_pdata = exynos_sound_get_platform_data();
+ if (!sound_pdata) {
+ pr_info("%s: don't use sound pdata\n", __func__);
+ goto err_out_free;
+ }
+
+ if (sound_pdata->set_lineout_switch)
+ wm1811->lineout_switch_f = sound_pdata->set_lineout_switch;
+
+ if (sound_pdata->set_ext_main_mic)
+ wm1811->set_main_mic_f = sound_pdata->set_ext_main_mic;
+
+ if (sound_pdata->set_ext_sub_mic)
+ wm1811->set_sub_mic_f = sound_pdata->set_ext_sub_mic;
+
+ if (sound_pdata->get_ground_det_value)
+ wm1811->get_g_det_value_f = sound_pdata->get_ground_det_value;
+
+ if (sound_pdata->get_ground_det_irq_num) {
+ wm1811->get_g_det_irq_num_f =
+ sound_pdata->get_ground_det_irq_num;
+ ret = request_threaded_irq(wm1811->get_g_det_irq_num_f(), NULL,
+ t0_g_det_thread, IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "g_det irq", wm1811);
+ if (ret != 0)
+ pr_err("%s: Failed to register IRQ\n", __func__);
+
+ /* to handle insert/removal when we're sleeping in a call */
+ ret = enable_irq_wake(wm1811->get_g_det_irq_num_f());
+ if (ret)
+ pr_err("%s : Failed to enable_irq_wake\n", __func__);
+ }
+
+ return ret;
+
+err_out_free:
+ platform_device_put(t0_snd_device);
+err_device_alloc:
+ kfree(wm1811);
+err_kzalloc:
+ return ret;
+}
+module_init(t0_audio_init);
+
+static void __exit t0_audio_exit(void)
+{
+ struct snd_soc_card *card = &t0_card;
+ struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card);
+ platform_device_unregister(t0_snd_device);
+ kfree(wm1811);
+}
+module_exit(t0_audio_exit);
+
+MODULE_AUTHOR("Uk Kim <w0806.kim@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC T0 WM1811");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/t0duos_wm1811.c b/sound/soc/samsung/t0duos_wm1811.c
new file mode 100644
index 0000000..dfa3001
--- /dev/null
+++ b/sound/soc/samsung/t0duos_wm1811.c
@@ -0,0 +1,1479 @@
+/*
+ * t0_wm1811.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co. Ltd
+ *
+ * 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.
+ */
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+#include <linux/wakelock.h>
+#include <linux/suspend.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+
+#include <mach/regs-clock.h>
+#include <mach/pmu.h>
+#include <mach/midas-sound.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/exynos_audio.h>
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+#include <linux/mfd/max77693-private.h>
+#endif
+
+
+#include "i2s.h"
+#include "s3c-i2s-v2.h"
+#include "../codecs/wm8994.h"
+
+
+#define MIDAS_DEFAULT_MCLK1 24000000
+#define MIDAS_DEFAULT_MCLK2 32768
+#define MIDAS_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 WM1811_MIC_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_MIC1_DET)
+#define WM1811_JACKDET_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_GPIO(6))
+
+static struct wm8958_micd_rate t0_det_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 },
+};
+
+static struct wm8958_micd_rate t0_jackdet_rates[] = {
+ { MIDAS_DEFAULT_MCLK2, true, 0, 0 },
+ { MIDAS_DEFAULT_MCLK2, false, 0, 0 },
+ { MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 },
+ { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 },
+};
+
+static int aif2_mode;
+const char *aif2_mode_text[] = {
+ "Slave", "Master"
+};
+
+static int input_clamp;
+const char *input_clamp_text[] = {
+ "Off", "On"
+};
+
+static int lineout_mode;
+const char *lineout_mode_text[] = {
+ "Off", "On"
+};
+
+#if defined(CONFIG_SND_DUOS_MODEM_SWITCH)
+static int modem_mode;
+const char *modem_mode_text[] = {
+ "CP1", "CP2"
+};
+#endif
+
+#ifndef CONFIG_SEC_DEV_JACK
+/* To support PBA function test */
+static struct class *jack_class;
+static struct device *jack_dev;
+#endif
+
+struct wm1811_machine_priv {
+ struct snd_soc_jack jack;
+ struct snd_soc_codec *codec;
+ struct wake_lock jackdet_wake_lock;
+ void (*lineout_switch_f) (int on);
+ void (*set_main_mic_f) (int on);
+ void (*set_sub_mic_f) (int on);
+ int (*get_g_det_value_f) (void);
+ int (*get_g_det_irq_num_f) (void);
+#if defined(CONFIG_SND_DUOS_MODEM_SWITCH)
+ void (*set_modem_switch_f) (int on);
+#endif
+};
+
+#if defined(CONFIG_SND_DUOS_MODEM_SWITCH)
+static const struct soc_enum modem_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(modem_mode_text), modem_mode_text),
+};
+
+static int get_modem_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = modem_mode;
+ return 0;
+}
+
+static int set_modem_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ modem_mode = ucontrol->value.integer.value[0];
+
+ if (modem_mode) {
+ if (wm1811->set_modem_switch_f)
+ wm1811->set_modem_switch_f(1);
+ } else {
+ if (wm1811->set_modem_switch_f)
+ wm1811->set_modem_switch_f(0);
+ /*msleep(50);*/
+ }
+ dev_info(codec->dev, "set modem select : %s\n",
+ modem_mode_text[modem_mode]);
+ return 0;
+
+}
+#endif
+
+static const struct soc_enum lineout_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text),
+};
+
+static int get_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = lineout_mode;
+ return 0;
+}
+
+static int set_lineout_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ lineout_mode = ucontrol->value.integer.value[0];
+
+ if (lineout_mode) {
+ wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(1);
+ } else {
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(0);
+ wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
+ }
+
+ dev_info(codec->dev, "set lineout mode : %s\n",
+ lineout_mode_text[lineout_mode]);
+ return 0;
+
+}
+static const struct soc_enum aif2_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text),
+};
+
+static const struct soc_enum input_clamp_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_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];
+
+ pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]);
+
+ return 0;
+}
+
+static int get_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = input_clamp;
+ return 0;
+}
+
+static int set_input_clamp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ input_clamp = ucontrol->value.integer.value[0];
+
+ if (input_clamp) {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP);
+ msleep(100);
+ } else {
+ snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1,
+ WM8994_INPUTS_CLAMP, 0);
+ }
+ pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]);
+
+ return 0;
+}
+
+
+static int set_ext_micbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (wm1811->set_main_mic_f)
+ wm1811->set_main_mic_f(1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ if (wm1811->set_main_mic_f)
+ wm1811->set_main_mic_f(0);
+ break;
+ }
+
+ return 0;
+}
+
+static int set_ext_submicbias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (wm1811->set_sub_mic_f)
+ wm1811->set_sub_mic_f(1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ if (wm1811->set_sub_mic_f)
+ wm1811->set_sub_mic_f(0);
+ break;
+ }
+
+ return 0;
+}
+
+static int set_muic_switch(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ dev_info(codec->dev, "%s event is %02X", w->name, event);
+
+#if defined(CONFIG_SND_USE_MUIC_SWITCH)
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ msleep(150);
+ max77693_muic_set_audio_switch(1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ max77693_muic_set_audio_switch(0);
+ break;
+ }
+#endif
+
+ return 0;
+}
+
+static void t0_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 = t0_jackdet_rates;
+ num_rates = ARRAY_SIZE(t0_jackdet_rates);
+ wm8994->pdata->micd_rates = t0_jackdet_rates;
+ wm8994->pdata->num_micd_rates = num_rates;
+ } else {
+ rates = t0_det_rates;
+ num_rates = ARRAY_SIZE(t0_det_rates);
+ wm8994->pdata->micd_rates = t0_det_rates;
+ wm8994->pdata->num_micd_rates = num_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 t0_micdet(u16 status, void *data)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+ int report;
+
+ /*
+ * If the jack is inserted abnormally,
+ * The variable puts back to its previous status.
+ */
+ if (!wm1811->get_g_det_value_f) {
+ dev_err(wm1811->codec->dev, "Do not use the ground detection\n");
+ } else {
+ if (wm1811->get_g_det_value_f()) {
+ dev_info(wm1811->codec->dev, "The jack is inserted abnormally\n");
+
+ wm8994->mic_detecting = false;
+ }
+ }
+
+ wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ);
+
+ /* Either nothing present or just starting detection */
+ if (!(status & WM8958_MICD_STS)) {
+ if (!wm8994->jackdet) {
+ /* If nothing present then clear our statuses */
+ dev_dbg(wm1811->codec->dev, "Detected open circuit\n");
+ wm8994->jack_mic = false;
+ wm8994->mic_detecting = true;
+
+ t0_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ wm8994->btn_mask |
+ SND_JACK_HEADSET);
+ }
+ /*ToDo*/
+ /*return;*/
+ }
+
+ /* If the measurement is showing a high impedence we've got a
+ * microphone.
+ */
+ if (wm8994->mic_detecting && (status & 0x400)) {
+ dev_info(wm1811->codec->dev, "Detected microphone\n");
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ t0_micd_set_rate(wm1811->codec);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET,
+ SND_JACK_HEADSET);
+ }
+
+ if (wm8994->mic_detecting && status & 0x4) {
+ dev_info(wm1811->codec->dev, "Detected headphone\n");
+ wm8994->mic_detecting = false;
+
+ t0_micd_set_rate(wm1811->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(wm1811->codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(wm1811->codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ }
+
+ mutex_unlock(&wm8994->accdet_lock);
+
+ 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);
+ }
+ }
+ }
+
+ /* 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;
+
+ dev_info(wm1811->codec->dev, "Detected Button: %08x (%08X)\n",
+ report, status);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, report,
+ wm8994->btn_mask);
+ }
+}
+
+static int t0_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ /* AIF1CLK should be >=3MHz for optimal performance */
+ 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 the FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1,
+ 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)
+ return ret;
+
+ dev_info(codec_dai->dev, "%s --\n", __func__);
+
+ return 0;
+}
+
+/*
+ * T0 WM1811 DAI operations.
+ */
+static struct snd_soc_ops t0_wm1811_aif1_ops = {
+ .hw_params = t0_wm1811_aif1_hw_params,
+};
+
+static int t0_wm1811_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;
+
+ dev_info(codec_dai->dev, "%s ++\n", __func__);
+ 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 8000Hz\n",
+ (int)params_rate(params));
+ prate = 8000;
+ }
+
+ /* Set the codec DAI configuration, aif2_mode:0 is slave */
+#if defined(CONFIG_MACH_T0_CHN_CTC)
+ if (aif2_mode == 0)
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A
+ | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A
+ | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+#elif defined(CONFIG_MACH_T0_CHN_CU_DUOS) \
+ || defined(CONFIG_MACH_T0_CHN_OPEN_DUOS)
+ if (modem_mode == 1) {
+ if (aif2_mode == 0)
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_A
+ | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ } else {
+ if (aif2_mode == 0)
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ }
+#else
+ if (aif2_mode == 0)
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+#endif
+
+ if (ret < 0)
+ return ret;
+
+ switch (prate) {
+ case 8000:
+ bclk = 256000;
+ break;
+ case 16000:
+ bclk = 512000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+#if defined(CONFIG_MACH_T0_CHN_CU_DUOS)
+ if (modem_mode == 1)
+ bclk = 2048000;
+#endif
+
+#if defined(CONFIG_MACH_T0_CHN_CTC)
+ bclk = 2048000;
+#endif
+
+ if (aif2_mode == 0) {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_BCLK,
+ bclk, prate * 256);
+ } else {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1, 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, "%s --\n", __func__);
+ return 0;
+}
+
+static struct snd_soc_ops t0_wm1811_aif2_ops = {
+ .hw_params = t0_wm1811_aif2_hw_params,
+};
+
+static int t0_wm1811_aif3_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ pr_err("%s: enter\n", __func__);
+ return 0;
+}
+
+static bool playback_stream_status;
+static bool capture_stream_status;
+
+static int t0_wm1811_aif3_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int base = WM8994_GPIO_8 - WM8994_GPIO_1;
+ int i;
+
+ pr_err("%s: enter\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ capture_stream_status = 1;
+ else
+ playback_stream_status = 1;
+
+ for (i = 0; i < 4; i++) {
+ if (pdata->gpio_defaults[base + i]) {
+ snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i,
+ 0xffff,
+ pdata->gpio_defaults[base + i]);
+ }
+ }
+ return 0;
+}
+
+static void t0_wm1811_aif3_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ pr_err("%s: enter, stream=%d\n", __func__, substream->stream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ capture_stream_status = 0;
+ else
+ playback_stream_status = 0;
+
+ if (playback_stream_status || capture_stream_status)
+ return;
+
+ pr_info("%s: set input gpios for AIF3\n", __func__);
+
+ for (i = 0; i < 4; i++) {
+ snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i,
+ 0xffff,
+ 0xA101);
+ }
+ return;
+}
+
+static struct snd_soc_ops t0_wm1811_aif3_ops = {
+ .startup = t0_wm1811_aif3_startup,
+ .shutdown = t0_wm1811_aif3_shutdown,
+ .hw_params = t0_wm1811_aif3_hw_params,
+};
+
+static const struct snd_kcontrol_new t0_controls[] = {
+ SOC_DAPM_PIN_SWITCH("HP"),
+ SOC_DAPM_PIN_SWITCH("SPK"),
+ SOC_DAPM_PIN_SWITCH("RCV"),
+ SOC_DAPM_PIN_SWITCH("FM In"),
+ 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"),
+
+ SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0],
+ get_aif2_mode, set_aif2_mode),
+
+ SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0],
+ get_input_clamp, set_input_clamp),
+
+ SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0],
+ get_lineout_mode, set_lineout_mode),
+#if defined(CONFIG_SND_DUOS_MODEM_SWITCH)
+ SOC_ENUM_EXT("ModemSwitch Mode", modem_mode_enum[0],
+ get_modem_mode, set_modem_mode),
+#endif
+};
+
+const struct snd_soc_dapm_widget t0_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+ SND_SOC_DAPM_SPK("RCV", NULL),
+ SND_SOC_DAPM_LINE("LINE", set_muic_switch),
+ SND_SOC_DAPM_LINE("HDMI", NULL),
+
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", set_ext_micbias),
+ SND_SOC_DAPM_MIC("Sub Mic", set_ext_submicbias),
+ SND_SOC_DAPM_LINE("FM In", NULL),
+
+ SND_SOC_DAPM_INPUT("S5P RP"),
+};
+
+const struct snd_soc_dapm_route t0_dapm_routes[] = {
+ { "HP", NULL, "HPOUT1L" },
+ { "HP", NULL, "HPOUT1R" },
+
+ { "SPK", NULL, "SPKOUTLN" },
+ { "SPK", NULL, "SPKOUTLP" },
+
+ { "RCV", NULL, "HPOUT2N" },
+ { "RCV", NULL, "HPOUT2P" },
+
+ { "LINE", NULL, "LINEOUT2N" },
+ { "LINE", NULL, "LINEOUT2P" },
+
+ { "HDMI", NULL, "LINEOUT1N" },
+ { "HDMI", NULL, "LINEOUT1P" },
+
+ { "IN2LP:VXRN", NULL, "Main Mic" },
+ { "IN2LN", NULL, "Main Mic" },
+
+ { "IN1RP", NULL, "MICBIAS1" },
+ { "IN1RN", NULL, "MICBIAS1" },
+ { "MICBIAS1", NULL, "Sub Mic" },
+
+ { "IN1LP", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+ { "IN1LN", NULL, "MICBIAS2" },
+ { "MICBIAS2", NULL, "Headset Mic" },
+
+ { "AIF1DAC1L", NULL, "S5P RP" },
+ { "AIF1DAC1R", NULL, "S5P RP" },
+
+ { "IN2RN", NULL, "FM In" },
+ { "IN2RP:VXRP", NULL, "FM In" },
+};
+
+static struct snd_soc_dai_driver t0_ext_dai[] = {
+ {
+ .name = "t0.cp",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "t0.bt",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+#ifndef CONFIG_SEC_DEV_JACK
+static ssize_t earjack_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) ||
+ (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) {
+ report = 1;
+ }
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_key_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_codec *codec = dev_get_drvdata(dev);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ int report = 0;
+
+ if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0)
+ report = 1;
+
+ return sprintf(buf, "%d\n", report);
+}
+
+static ssize_t earjack_key_state_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ pr_info("%s : operate nothing\n", __func__);
+
+ return size;
+}
+
+static ssize_t earjack_select_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __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);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = true;
+
+ t0_micd_set_rate(codec);
+
+ if ((!size) || (buf[0] != '1')) {
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ 0, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced remove microphone\n");
+ } else {
+
+ snd_soc_jack_report(wm8994->micdet[0].jack,
+ SND_JACK_HEADSET, SND_JACK_HEADSET);
+ dev_info(codec->dev, "Forced detect microphone\n");
+ }
+
+ return size;
+}
+
+static ssize_t reselect_jack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ pr_info("%s : operate nothing\n", __func__);
+ return 0;
+}
+
+static ssize_t reselect_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 reg = 0;
+
+ reg = snd_soc_read(codec, WM8958_MIC_DETECT_3);
+ if (reg == 0x402) {
+ dev_info(codec->dev, "Detected open circuit\n");
+
+ snd_soc_update_bits(codec, WM8958_MICBIAS2,
+ WM8958_MICB2_DISCH, WM8958_MICB2_DISCH);
+ /* Enable debounce while removed */
+ snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
+ WM1811_JACKDET_DB, WM1811_JACKDET_DB);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = false;
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ if (wm8994->active_refcount) {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_AUDIO);
+ } else {
+ snd_soc_update_bits(codec,
+ WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK,
+ WM1811_JACKDET_MODE_JACK);
+ }
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ SND_JACK_MECHANICAL | SND_JACK_HEADSET |
+ wm8994->btn_mask);
+ }
+ return size;
+}
+
+static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ reselect_jack_show, reselect_jack_store);
+
+static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_select_jack_show, earjack_select_jack_store);
+
+static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_key_state_show, earjack_key_state_store);
+
+static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP,
+ earjack_state_show, earjack_state_store);
+#endif
+
+static int t0_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+ struct snd_soc_dai *aif1_dai = rtd->codec_dai;
+ struct wm8994 *control = codec->control_data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ midas_snd_set_mclk(true, false);
+
+ rtd->codec_dai->driver->playback.channels_max =
+ rtd->cpu_dai->driver->playback.channels_max;
+
+ ret = snd_soc_add_controls(codec, t0_controls,
+ ARRAY_SIZE(t0_controls));
+
+ ret = snd_soc_dapm_new_controls(&codec->dapm, t0_dapm_widgets,
+ ARRAY_SIZE(t0_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, t0_dapm_routes,
+ ARRAY_SIZE(t0_dapm_routes));
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret);
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2, 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 */
+ if (wm8994->revision > 1) {
+ ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n",
+ ret);
+ }
+
+ ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP");
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret);
+
+ 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, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic");
+ 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");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE");
+ snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI");
+
+ wm1811->codec = codec;
+
+ t0_micd_set_rate(codec);
+
+#ifdef CONFIG_SEC_DEV_JACK
+ /* By default use idle_bias_off, will override for WM8994 */
+ codec->dapm.idle_bias_off = 0;
+#else /* CONFIG_SEC_DEV_JACK */
+ wm1811->jack.status = 0;
+
+ ret = snd_soc_jack_new(codec, "T0 Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &wm1811->jack);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to create jack: %d\n", ret);
+
+ ret = snd_jack_set_key(wm1811->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(wm1811->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(wm1811->jack.jack, SND_JACK_BTN_2,
+ KEY_VOLUMEUP);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret);
+
+ if (wm8994->revision > 1) {
+ dev_info(codec->dev, "wm1811: Rev %c support mic detection\n",
+ 'A' + wm8994->revision);
+ ret = wm8958_mic_detect(codec, &wm1811->jack, t0_micdet,
+ wm1811);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Failed start detection: %d\n",
+ ret);
+ } else {
+ dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n",
+ 'A' + wm8994->revision);
+ codec->dapm.idle_bias_off = 0;
+ }
+ /* To wakeup for earjack event in suspend mode */
+ enable_irq_wake(control->irq);
+
+ wake_lock_init(&wm1811->jackdet_wake_lock,
+ WAKE_LOCK_SUSPEND, "T0_jackdet");
+
+ /* To support PBA function test */
+ jack_class = class_create(THIS_MODULE, "audio");
+
+ if (IS_ERR(jack_class))
+ pr_err("Failed to create class\n");
+
+ jack_dev = device_create(jack_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);
+
+ if (device_create_file(jack_dev, &dev_attr_key_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_key_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_state) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_state.attr.name);
+
+ if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0)
+ pr_err("Failed to create device file (%s)!\n",
+ dev_attr_reselect_jack.attr.name);
+
+#endif /* CONFIG_SEC_DEV_JACK */
+ return snd_soc_dapm_sync(&codec->dapm);
+}
+
+static struct snd_soc_dai_link t0_dai[] = {
+ { /* Sec_Fifo DAI i/f */
+ .name = "Sec_FIFO TX",
+ .stream_name = "Sec_Dai",
+ .cpu_dai_name = "samsung-i2s.4",
+ .codec_dai_name = "wm8994-aif1",
+#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER
+ .platform_name = "samsung-audio-idma",
+#else
+ .platform_name = "samsung-audio",
+#endif
+ .codec_name = "wm8994-codec",
+ .init = t0_wm1811_init_paiftx,
+ .ops = &t0_wm1811_aif1_ops,
+ },
+ {
+ .name = "T0_WM1811 Voice",
+ .stream_name = "Voice Tx/Rx",
+ .cpu_dai_name = "t0.cp",
+ .codec_dai_name = "wm8994-aif2",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &t0_wm1811_aif2_ops,
+ .ignore_suspend = 1,
+ },
+ {
+ .name = "T0_WM1811 BT",
+ .stream_name = "BT Tx/Rx",
+ .cpu_dai_name = "t0.bt",
+ .codec_dai_name = "wm8994-aif3",
+ .platform_name = "snd-soc-dummy",
+ .codec_name = "wm8994-codec",
+ .ops = &t0_wm1811_aif3_ops,
+ .ignore_suspend = 1,
+ },
+ { /* Primary DAI i/f */
+ .name = "WM8994 AIF1",
+ .stream_name = "Pri_Dai",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec",
+ .ops = &t0_wm1811_aif1_ops,
+ },
+};
+
+static int t0_card_suspend_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+ if (lineout_mode == 1 &&
+ wm8994->vmid_mode == WM8994_VMID_FORCE) {
+ dev_info(codec->dev,
+ "%s: entering force vmid mode\n", __func__);
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(0);
+ wm8994_vmid_mode(codec, WM8994_VMID_NORMAL);
+ }
+#endif
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+static int t0_card_suspend_post(struct snd_soc_card *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) {
+ ret = snd_soc_dai_set_sysclk(aif2_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch 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\n");
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_MCLK2,
+ MIDAS_DEFAULT_MCLK2,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to switch to MCLK2\n");
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
+
+ if (ret < 0)
+ dev_err(codec->dev, "Unable to stop FLL1\n");
+
+ midas_snd_set_mclk(false, true);
+ }
+
+#ifdef CONFIG_ARCH_EXYNOS5
+ exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0);
+#else /* for CONFIG_ARCH_EXYNOS5 */
+ exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0);
+#endif
+
+ return 0;
+}
+
+static int t0_card_resume_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
+ int ret;
+
+ midas_snd_set_mclk(true, false);
+
+ /* Switch the FLL */
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1,
+ MIDAS_DEFAULT_MCLK1,
+ MIDAS_DEFAULT_SYNC_CLK);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret);
+
+ /* Then switch AIF1CLK to it */
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_FLL1,
+ MIDAS_DEFAULT_SYNC_CLK,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret);
+
+ return 0;
+}
+
+static int t0_card_resume_post(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int reg = 0;
+
+#ifdef CONFIG_SND_USE_LINEOUT_SWITCH
+ struct wm1811_machine_priv *wm1811
+ = snd_soc_card_get_drvdata(codec->card);
+
+ if (lineout_mode == 1 &&
+ wm8994->vmid_mode == WM8994_VMID_NORMAL) {
+ dev_info(codec->dev,
+ "%s: entering normal vmid mode\n", __func__);
+ wm8994_vmid_mode(codec, WM8994_VMID_FORCE);
+ if (wm1811->lineout_switch_f)
+ wm1811->lineout_switch_f(1);
+ }
+#endif
+ /* 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);
+ }
+
+#ifdef CONFIG_SEC_DEV_JACK
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK");
+#endif
+
+ return 0;
+}
+
+static struct snd_soc_card t0_card = {
+ .name = "T0_WM1811",
+ .dai_link = t0_dai,
+
+ /* If you want to use sec_fifo device,
+ * changes the num_link = 2 or ARRAY_SIZE(t0_dai). */
+ .num_links = ARRAY_SIZE(t0_dai),
+
+ .suspend_pre = t0_card_suspend_pre,
+ .suspend_post = t0_card_suspend_post,
+ .resume_pre = t0_card_resume_pre,
+ .resume_post = t0_card_resume_post
+};
+
+static struct platform_device *t0_snd_device;
+
+static void t0_jackdet_set_mode(struct snd_soc_codec *codec, u16 mode)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (!wm8994->jackdet || !wm8994->jack_cb)
+ return;
+
+ if (wm8994->active_refcount)
+ mode = WM1811_JACKDET_MODE_AUDIO;
+
+ if (mode == wm8994->jackdet_mode)
+ return;
+
+ wm8994->jackdet_mode = mode;
+
+ /* Always use audio mode to detect while the system is active */
+ if (mode != WM1811_JACKDET_MODE_NONE)
+ mode = WM1811_JACKDET_MODE_AUDIO;
+
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM1811_JACKDET_MODE_MASK, mode);
+}
+
+static irqreturn_t t0_g_det_thread(int irq, void *data)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+ struct snd_soc_codec *codec = wm8994->codec;
+
+
+ if (wm1811->get_g_det_value_f()) {
+
+ pr_info("%s: G_DET_N GPIO is High!!!!", __func__);
+
+ mutex_lock(&wm8994->accdet_lock);
+
+ snd_soc_update_bits(codec, WM8958_MICBIAS2,
+ WM8958_MICB2_DISCH, WM8958_MICB2_DISCH);
+
+ /* Enable debounce while removed */
+ snd_soc_update_bits(codec, WM1811_JACKDET_CTRL,
+ WM1811_JACKDET_DB, WM1811_JACKDET_DB);
+
+ wm8994->mic_detecting = false;
+ wm8994->jack_mic = false;
+
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+
+ t0_jackdet_set_mode(codec, WM1811_JACKDET_MODE_JACK);
+
+ mutex_unlock(&wm8994->accdet_lock);
+
+ mutex_lock(&codec->mutex);
+
+ snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2");
+ snd_soc_dapm_sync(&codec->dapm);
+
+ mutex_unlock(&codec->mutex);
+
+ snd_soc_jack_report(wm8994->micdet[0].jack, 0,
+ SND_JACK_MECHANICAL | SND_JACK_HEADSET |
+ wm8994->btn_mask);
+ } else {
+ pr_info("%s: G_DET_N GPIO is Low!!!!", __func__);
+
+ handle_nested_irq(WM1811_JACKDET_IRQ_NUM);
+ msleep(100);
+ handle_nested_irq(WM1811_MIC_IRQ_NUM);
+ }
+
+ return IRQ_HANDLED;
+
+}
+
+static int __init t0_audio_init(void)
+{
+ struct wm1811_machine_priv *wm1811;
+ const struct exynos_sound_platform_data *sound_pdata;
+ int ret;
+
+ wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL);
+ if (!wm1811) {
+ pr_err("Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+ snd_soc_card_set_drvdata(&t0_card, wm1811);
+
+ t0_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!t0_snd_device) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ ret = snd_soc_register_dais(&t0_snd_device->dev, t0_ext_dai,
+ ARRAY_SIZE(t0_ext_dai));
+ if (ret != 0)
+ pr_err("Failed to register external DAIs: %d\n", ret);
+
+ platform_set_drvdata(t0_snd_device, &t0_card);
+
+ ret = platform_device_add(t0_snd_device);
+ if (ret)
+ platform_device_put(t0_snd_device);
+
+ sound_pdata = exynos_sound_get_platform_data();
+ if (!sound_pdata) {
+ pr_info("%s: don't use sound pdata\n", __func__);
+ goto err_out_free;
+ }
+
+ if (sound_pdata->set_lineout_switch)
+ wm1811->lineout_switch_f = sound_pdata->set_lineout_switch;
+
+ if (sound_pdata->set_ext_main_mic)
+ wm1811->set_main_mic_f = sound_pdata->set_ext_main_mic;
+
+ if (sound_pdata->set_ext_sub_mic)
+ wm1811->set_sub_mic_f = sound_pdata->set_ext_sub_mic;
+#if defined(CONFIG_SND_DUOS_MODEM_SWITCH)
+ if (sound_pdata->set_modem_switch)
+ wm1811->set_modem_switch_f = sound_pdata->set_modem_switch;
+#endif
+ if (sound_pdata->get_ground_det_value)
+ wm1811->get_g_det_value_f = sound_pdata->get_ground_det_value;
+
+ if (sound_pdata->get_ground_det_irq_num) {
+ wm1811->get_g_det_irq_num_f =
+ sound_pdata->get_ground_det_irq_num;
+ ret = request_threaded_irq(wm1811->get_g_det_irq_num_f(), NULL,
+ t0_g_det_thread, IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "g_det irq", wm1811);
+ if (ret != 0)
+ pr_err("%s: Failed to register IRQ\n", __func__);
+
+ /* to handle insert/removal when we're sleeping in a call */
+ ret = enable_irq_wake(wm1811->get_g_det_irq_num_f());
+ if (ret)
+ pr_err("%s : Failed to enable_irq_wake\n", __func__);
+ }
+
+ return ret;
+
+err_out_free:
+ platform_device_put(t0_snd_device);
+err_device_alloc:
+ kfree(wm1811);
+err_kzalloc:
+ return ret;
+}
+module_init(t0_audio_init);
+
+static void __exit t0_audio_exit(void)
+{
+ struct snd_soc_card *card = &t0_card;
+ struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card);
+ platform_device_unregister(t0_snd_device);
+ kfree(wm1811);
+}
+module_exit(t0_audio_exit);
+
+MODULE_AUTHOR("Uk Kim <w0806.kim@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC T0 WM1811");
+MODULE_LICENSE("GPL");