aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/samsung/midas_wm1811.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/samsung/midas_wm1811.c')
-rw-r--r--sound/soc/samsung/midas_wm1811.c379
1 files changed, 373 insertions, 6 deletions
diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c
index 1f5becc..58ebbd8 100644
--- a/sound/soc/samsung/midas_wm1811.c
+++ b/sound/soc/samsung/midas_wm1811.c
@@ -37,6 +37,14 @@
#include <linux/mfd/wm8994/pdata.h>
#include <linux/mfd/wm8994/gpio.h>
+#ifdef CONFIG_EXYNOS_SOUND_PLATFORM_DATA
+#include <linux/exynos_audio.h>
+#endif
+
+#ifdef CONFIG_USE_ADC_DET
+#include <plat/adc.h>
+#endif
+
#if defined(CONFIG_SND_USE_MUIC_SWITCH)
#include <linux/mfd/max77693-private.h>
#endif
@@ -60,6 +68,49 @@
#define WM1811_JACKDET_BTN1 0x10
#define WM1811_JACKDET_BTN2 0x08
+#define JACK_ADC_CH 3
+#define JACK_SAMPLE_SIZE 5
+
+#define MAX_ZONE_LIMIT 10
+/* keep this value if you support double-pressed concept */
+#define WAKE_LOCK_TIME (HZ * 5) /* 5 sec */
+#define EAR_CHECK_LOOP_CNT 10
+
+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);
+#ifdef CONFIG_USE_ADC_DET
+ struct s3c_adc_client *padc;
+ struct jack_zone *zones;
+ int num_zones;
+ int use_jackdet_type;
+#endif
+};
+
+enum {
+ SEC_JACK_NO_DEVICE = 0x0,
+ SEC_HEADSET_4POLE = 0x01 << 0,
+ SEC_HEADSET_3POLE = 0x01 << 1,
+ SEC_TTY_DEVICE = 0x01 << 2,
+ SEC_FM_HEADSET = 0x01 << 3,
+ SEC_FM_SPEAKER = 0x01 << 4,
+ SEC_TVOUT_DEVICE = 0x01 << 5,
+ SEC_EXTRA_DOCK_SPEAKER = 0x01 << 6,
+ SEC_EXTRA_CAR_DOCK_SPEAKER = 0x01 << 7,
+ SEC_UNKNOWN_DEVICE = 0x01 << 8,
+};
+
+#ifdef CONFIG_USE_ADC_DET
+static bool recheck_jack;
+static int jack_get_adc_data(struct s3c_adc_client *padc);
+static void jack_set_type(struct wm1811_machine_priv *wm1811, int jack_type);
+#endif
static struct wm8958_micd_rate midas_det_rates[] = {
{ MIDAS_DEFAULT_MCLK2, true, 0, 0 },
@@ -104,6 +155,11 @@ const char *lineout_mode_text[] = {
"Off", "On"
};
+static int aif2_digital_mute;
+const char *switch_mode_text[] = {
+ "Off", "On"
+};
+
#ifndef CONFIG_SEC_DEV_JACK
/* To support PBA function test */
static struct class *jack_class;
@@ -115,12 +171,7 @@ 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 struct platform_device *midas_snd_device;
#ifdef CONFIG_MACH_GC1
static struct snd_soc_codec *wm1811_codec;
@@ -242,6 +293,10 @@ static const struct soc_enum input_clamp_enum[] = {
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_text),
};
+static const struct soc_enum switch_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(switch_mode_text), switch_mode_text),
+};
+
static int get_aif2_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
@@ -307,6 +362,38 @@ static int set_input_clamp(struct snd_kcontrol *kcontrol,
return 0;
}
+static int get_aif2_mute_status(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = aif2_digital_mute;
+ return 0;
+}
+
+static int set_aif2_mute_status(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg;
+
+ aif2_digital_mute = ucontrol->value.integer.value[0];
+
+ if (snd_soc_read(codec, WM8994_POWER_MANAGEMENT_6)
+ & WM8994_AIF2_DACDAT_SRC)
+ aif2_digital_mute = 0;
+
+ if (aif2_digital_mute)
+ reg = WM8994_AIF1DAC1_MUTE;
+ else
+ reg = 0;
+
+ snd_soc_update_bits(codec, WM8994_AIF2_DAC_FILTERS_1,
+ WM8994_AIF1DAC1_MUTE, reg);
+
+ pr_info("set aif2_digital_mute : %s\n",
+ switch_mode_text[aif2_digital_mute]);
+
+ return 0;
+}
static int midas_ext_micbias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
@@ -505,7 +592,240 @@ static void midas_start_fll1(struct snd_soc_dai *aif1_dai)
midas_fll1_active = true;
}
#endif
+#ifdef CONFIG_USE_ADC_DET
+static int jack_get_adc_data(struct s3c_adc_client *padc)
+{
+ int adc_data;
+ int adc_max = 0;
+ int adc_min = 0xFFFF;
+ int adc_total = 0;
+ int adc_retry_cnt = 0;
+ int i;
+
+ for (i = 0; i < JACK_SAMPLE_SIZE; i++) {
+
+ adc_data = s3c_adc_read(padc, JACK_ADC_CH);
+
+ if (adc_data < 0) {
+
+ adc_retry_cnt++;
+
+ if (adc_retry_cnt > 10)
+ return adc_data;
+ }
+
+ if (i != 0) {
+ if (adc_data > adc_max)
+ adc_max = adc_data;
+ else if (adc_data < adc_min)
+ adc_min = adc_data;
+ } else {
+ adc_max = adc_data;
+ adc_min = adc_data;
+ }
+ adc_total += adc_data;
+ }
+ return (adc_total - adc_max - adc_min) / (JACK_SAMPLE_SIZE - 2);
+}
+
+static void determine_jack_type(struct wm1811_machine_priv *wm1811)
+{
+ struct jack_zone *zones = wm1811->zones;
+ struct snd_soc_codec *codec = wm1811->codec;
+ int size = wm1811->num_zones;
+ int count[MAX_ZONE_LIMIT] = {0};
+ int adc;
+ int i;
+
+ /* set mic bias to enable adc */
+ while (snd_soc_read(codec, WM1811_JACKDET_CTRL) & WM1811_JACKDET_LVL) {
+ adc = jack_get_adc_data(wm1811->padc);
+
+ pr_info("%s: adc = %d\n", __func__, adc);
+
+ if (adc < 0)
+ break;
+
+ /* determine the type of headset based on the
+ * adc value. An adc value can fall in various
+ * ranges or zones. Within some ranges, the type
+ * can be returned immediately. Within others, the
+ * value is considered unstable and we need to sample
+ * a few more types (up to the limit determined by
+ * the range) before we return the type for that range.
+ */
+ for (i = 0; i < size; i++) {
+ if (adc <= zones[i].adc_high) {
+ if (++count[i] > zones[i].check_count) {
+ if (recheck_jack == true && i == 4) {
+ pr_info("%s : something wrong connection!\n",
+ __func__);
+
+ recheck_jack = false;
+ return;
+ }
+ jack_set_type(wm1811,
+ zones[i].jack_type);
+ return;
+ }
+ msleep(zones[i].delay_ms);
+ break;
+ }
+ }
+ }
+
+ recheck_jack = false;
+ /* jack removed before detection complete */
+ pr_debug("%s : jack removed before detection complete\n", __func__);
+}
+
+static void jack_set_type(struct wm1811_machine_priv *wm1811, int jack_type)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+
+ if (jack_type == SEC_HEADSET_4POLE) {
+ 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);
+
+ snd_soc_update_bits(wm1811->codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 1);
+ } else {
+ 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) {
+ 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);
+ }
+
+ 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);
+ }
+ }
+ }
+}
+
+static void midas_micdet(void *data)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+
+ struct snd_soc_codec *codec = wm1811->codec;
+
+ pr_info("%s: detected jack\n", __func__);
+ wm8994->mic_detecting = true;
+
+ wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ);
+
+ snd_soc_update_bits(codec, WM8958_MICBIAS2,
+ WM8958_MICB2_MODE, 0);
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_MICB2_ENA_MASK, WM8994_MICB2_ENA);
+
+ determine_jack_type(wm1811);
+}
+
+static void midas_mic_id(void *data, u16 status)
+{
+ struct wm1811_machine_priv *wm1811 = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec);
+
+ pr_info("%s: detected jack\n", __func__);
+ 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);
+ }
+ }
+ }
+}
+#else
static void midas_micdet(u16 status, void *data)
{
struct wm1811_machine_priv *wm1811 = data;
@@ -602,6 +922,7 @@ static void midas_micdet(u16 status, void *data)
wm8994->btn_mask);
}
}
+#endif
#ifdef CONFIG_SND_SAMSUNG_I2S_MASTER
static int set_epll_rate(unsigned long rate)
@@ -967,6 +1288,9 @@ static const struct snd_kcontrol_new midas_controls[] = {
SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0],
get_lineout_mode, set_lineout_mode),
+
+ SOC_ENUM_EXT("AIF2 digital mute", switch_mode_enum[0],
+ get_aif2_mute_status, set_aif2_mute_status),
};
@@ -1214,7 +1538,13 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
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);
+#ifdef CONFIG_EXYNOS_SOUND_PLATFORM_DATA
+ const struct exynos_sound_platform_data *sound_pdata;
+#endif
int ret;
+#ifdef CONFIG_EXYNOS_SOUND_PLATFORM_DATA
+ sound_pdata = exynos_sound_get_platform_data();
+#endif
#ifdef SND_USE_BIAS_LEVEL
midas_aif1_dai = aif1_dai;
@@ -1313,8 +1643,19 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
if (wm8994->revision > 1) {
dev_info(codec->dev, "wm1811: Rev %c support mic detection\n",
'A' + wm8994->revision);
+
+#ifdef CONFIG_USE_ADC_DET
+ if (sound_pdata->use_jackdet_type) {
+ ret = wm8958_mic_detect(codec, &wm1811->jack,
+ midas_micdet, wm1811, NULL, NULL);
+ } else {
+ ret = wm8958_mic_detect(codec, &wm1811->jack, NULL,
+ NULL, midas_mic_id, wm1811);
+ }
+#else
ret = wm8958_mic_detect(codec, &wm1811->jack, midas_micdet,
wm1811);
+#endif
if (ret < 0)
dev_err(codec->dev, "Failed start detection: %d\n",
@@ -1355,6 +1696,12 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd)
dev_attr_reselect_jack.attr.name);
#endif /* CONFIG_SEC_DEV_JACK */
+
+#ifdef CONFIG_USE_ADC_DET
+ pr_info("%s: register adc client\n", __func__);
+ wm1811->padc = s3c_adc_register(midas_snd_device, NULL, NULL, 0);
+#endif
+
return snd_soc_dapm_sync(&codec->dapm);
}
@@ -1631,6 +1978,9 @@ static struct platform_device *midas_snd_device;
static int __init midas_audio_init(void)
{
struct wm1811_machine_priv *wm1811;
+#ifdef CONFIG_USE_ADC_DET
+ const struct exynos_sound_platform_data *sound_pdata;
+#endif
int ret;
wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL);
@@ -1658,6 +2008,20 @@ static int __init midas_audio_init(void)
if (ret)
platform_device_put(midas_snd_device);
+#ifdef CONFIG_USE_ADC_DET
+ sound_pdata = exynos_sound_get_platform_data();
+ if (!sound_pdata)
+ pr_info("%s: don't use sound pdata\n", __func__);
+
+ if (sound_pdata->zones) {
+ wm1811->zones = sound_pdata->zones;
+ wm1811->num_zones = sound_pdata->num_zones;
+ }
+ pr_info("%s:use_jackdet_type = %d\n", __func__,
+ sound_pdata->use_jackdet_type);
+ wm1811->use_jackdet_type = sound_pdata->use_jackdet_type;
+#endif
+
midas_gpio_init();
return ret;
@@ -1673,6 +2037,9 @@ static void __exit midas_audio_exit(void)
{
struct snd_soc_card *card = &midas;
struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card);
+#ifdef CONFIG_USE_ADC_DET
+ s3c_adc_release(wm1811->padc);
+#endif
platform_device_unregister(midas_snd_device);
kfree(wm1811);
}