From 7fc3ce7312fec9320aeffb1a6c6c6d4bf2408669 Mon Sep 17 00:00:00 2001 From: sbrissen Date: Wed, 23 Oct 2013 13:19:08 -0400 Subject: Add support for Note 8 (N5100 and N5110) Change-Id: I6c9798682f9f6349b37cb452353bd0c0e6958401 --- sound/soc/samsung/midas_wm1811.c | 379 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 373 insertions(+), 6 deletions(-) (limited to 'sound/soc/samsung/midas_wm1811.c') 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 #include +#ifdef CONFIG_EXYNOS_SOUND_PLATFORM_DATA +#include +#endif + +#ifdef CONFIG_USE_ADC_DET +#include +#endif + #if defined(CONFIG_SND_USE_MUIC_SWITCH) #include #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); } -- cgit v1.1