diff options
Diffstat (limited to 'sound/soc/samsung/midas_wm1811.c')
-rw-r--r-- | sound/soc/samsung/midas_wm1811.c | 374 |
1 files changed, 336 insertions, 38 deletions
diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c index 1f5becc..d505f87 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,14 +171,9 @@ 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 +#if defined(CONFIG_MACH_GC1) || defined(CONFIG_MACH_GC2PD) static struct snd_soc_codec *wm1811_codec; void set_wm1811_micbias2(bool on) @@ -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,39 @@ 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) @@ -506,13 +594,170 @@ static void midas_start_fll1(struct snd_soc_dai *aif1_dai) } #endif -static void midas_micdet(u16 status, void *data) +#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); - int report; + 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); +} +#endif +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 */ @@ -582,25 +827,6 @@ static void midas_micdet(u16 status, void *data) } } } - - /* 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 @@ -812,6 +1038,9 @@ 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; +#ifndef CONFIG_MACH_BAFFIN + struct snd_soc_codec *codec = rtd->codec; +#endif struct snd_soc_dai *codec_dai = rtd->codec_dai; int ret; int prate; @@ -830,7 +1059,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) +#if defined(CONFIG_MACH_C1_KOR_LGT) || defined(CONFIG_MACH_BAFFIN_KOR_LGT) /* Set the codec DAI configuration */ if (aif2_mode == 0) { if (kpcs_mode == 1) @@ -843,9 +1072,15 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream, | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBS_CFS); } else +#if defined(CONFIG_MACH_C1_KOR_LGT) || defined(CONFIG_MACH_BAFFIN_KOR_LGT) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBM_CFM); +#else ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); +#endif #else if (aif2_mode == 0) /* Set the codec DAI configuration */ @@ -925,6 +1160,16 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream, if (ret < 0) dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret); +#ifndef CONFIG_MACH_BAFFIN + if (!(snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2) + & WM8994_FLL2_LOCK_STS)) { + dev_info(codec_dai->dev, "%s: use mclk1 for FLL2\n", __func__); + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, prate * 256); + } +#endif + dev_info(codec_dai->dev, "%s --\n", __func__); return 0; } @@ -968,6 +1213,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), + }; const struct snd_soc_dapm_widget midas_dapm_widgets[] = { @@ -997,10 +1245,14 @@ const struct snd_soc_dapm_route midas_dapm_routes[] = { { "RCV", NULL, "HPOUT2N" }, { "RCV", NULL, "HPOUT2P" }, - +#if defined(CONFIG_MACH_BAFFIN_KOR_SKT) || defined(CONFIG_MACH_BAFFIN_KOR_KT) \ + || defined(CONFIG_MACH_BAFFIN_KOR_LGT) + { "LINE", NULL, "HPOUT1L" }, + { "LINE", NULL, "HPOUT1R" }, +#else { "LINE", NULL, "LINEOUT2N" }, { "LINE", NULL, "LINEOUT2P" }, - +#endif { "HDMI", NULL, "LINEOUT1N" }, { "HDMI", NULL, "LINEOUT1P" }, @@ -1214,13 +1466,19 @@ 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; #endif -#ifdef CONFIG_MACH_GC1 +#if defined(CONFIG_MACH_GC1) || defined(CONFIG_MACH_GC2PD) wm1811_codec = codec; #endif @@ -1300,12 +1558,12 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) 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); + KEY_VOLUMEUP); 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); + KEY_VOLUMEDOWN); if (ret < 0) dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret); @@ -1313,8 +1571,23 @@ 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); - ret = wm8958_mic_detect(codec, &wm1811->jack, midas_micdet, - wm1811); +#ifdef CONFIG_EXYNOS_SOUND_PLATFORM_DATA +#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, NULL, + NULL, midas_mic_id, wm1811); +#endif +#else + ret = wm8958_mic_detect(codec, &wm1811->jack, NULL, + NULL, midas_mic_id, wm1811); +#endif if (ret < 0) dev_err(codec->dev, "Failed start detection: %d\n", @@ -1355,6 +1628,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); } @@ -1626,11 +1905,12 @@ static struct snd_soc_card midas = { .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; +#ifdef CONFIG_EXYNOS_SOUND_PLATFORM_DATA + const struct exynos_sound_platform_data *sound_pdata; +#endif int ret; wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL); @@ -1658,6 +1938,21 @@ static int __init midas_audio_init(void) if (ret) platform_device_put(midas_snd_device); +#ifdef CONFIG_EXYNOS_SOUND_PLATFORM_DATA + sound_pdata = exynos_sound_get_platform_data(); + if (!sound_pdata) + pr_info("%s: don't use sound pdata\n", __func__); +#ifdef CONFIG_USE_ADC_DET + 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 +#endif + midas_gpio_init(); return ret; @@ -1673,6 +1968,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); } |