diff options
Diffstat (limited to 'sound/soc/samsung/t0_wm1811.c')
-rw-r--r-- | sound/soc/samsung/t0_wm1811.c | 356 |
1 files changed, 253 insertions, 103 deletions
diff --git a/sound/soc/samsung/t0_wm1811.c b/sound/soc/samsung/t0_wm1811.c index 30aa6c1..0085ff9 100644 --- a/sound/soc/samsung/t0_wm1811.c +++ b/sound/soc/samsung/t0_wm1811.c @@ -35,8 +35,13 @@ #include <linux/mfd/wm8994/registers.h> #include <linux/mfd/wm8994/pdata.h> #include <linux/mfd/wm8994/gpio.h> + #include <linux/exynos_audio.h> +#ifdef CONFIG_USE_ADC_DET +#include <plat/adc.h> +#endif + #if defined(CONFIG_SND_USE_MUIC_SWITCH) #include <linux/mfd/max77693-private.h> #endif @@ -68,6 +73,50 @@ #define MIC_FORCE_DISABLE 2 #define MIC_FORCE_ENABLE 3 +#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 t0_det_rates[] = { { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, @@ -107,16 +156,7 @@ 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 struct platform_device *t0_snd_device; static const struct soc_enum switch_mode_enum[] = { SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(switch_mode_text), switch_mode_text), @@ -270,7 +310,7 @@ static int set_sub_mic_bias_mode(struct snd_kcontrol *kcontrol, WM8994_MICB1_ENA, WM8994_MICB1_ENA); if (wm1811->set_sub_mic_f) - wm1811->set_main_mic_f(1); + wm1811->set_sub_mic_f(1); break; case MIC_ENABLE: snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, @@ -479,30 +519,174 @@ static void t0_micd_set_rate(struct snd_soc_codec *codec) WM8958_MICD_RATE_MASK, val); } -static void t0_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 wm1811_machine_priv *wm1811 = data; struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec); - int report; - int reg; - bool present; - /* - * 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"); + if (jack_type == SEC_HEADSET_4POLE) { + 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); + + snd_soc_update_bits(wm1811->codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 1); } else { - if (wm1811->get_g_det_value_f()) { - dev_info(wm1811->codec->dev, "The jack is inserted abnormally\n"); + dev_info(wm1811->codec->dev, "Detected headphone\n"); + wm8994->mic_detecting = false; + + t0_micd_set_rate(wm1811->codec); - wm8994->mic_detecting = false; + 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 t0_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); + + /* Apply delay time(150ms) to remove pop noise + * during to enable micbias */ + msleep(150); + + determine_jack_type(wm1811); + } +#endif + +static void t0_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) { @@ -570,41 +754,6 @@ static void t0_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; - - reg = snd_soc_read(wm1811->codec, WM1811_JACKDET_CTRL); - if (reg < 0) { - pr_err("%s: Failed to read jack status: %d\n", - __func__, reg); - return; - } - - pr_err("%s: JACKDET %x\n", __func__, reg); - - present = reg & WM1811_JACKDET_LVL; - - if (!present) { - pr_err("%s: button is ignored!!!\n", __func__); - return; - } - - 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, @@ -671,6 +820,7 @@ 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_codec *codec = rtd->codec; struct snd_soc_dai *codec_dai = rtd->codec_dai; int ret; int prate; @@ -730,6 +880,14 @@ static int t0_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); + 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); + } + dev_info(codec_dai->dev, "%s --\n", __func__); return 0; } @@ -1017,41 +1175,7 @@ static ssize_t reselect_jack_show(struct device *dev, 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); - } + pr_info("%s : operate nothing\n", __func__); return size; } @@ -1079,6 +1203,8 @@ static int t0_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) const struct exynos_sound_platform_data *sound_pdata; int ret; + sound_pdata = exynos_sound_get_platform_data(); + midas_snd_set_mclk(true, false); rtd->codec_dai->driver->playback.channels_max = @@ -1154,12 +1280,12 @@ static int t0_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); @@ -1167,9 +1293,18 @@ static int t0_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, t0_micdet, - wm1811); - +#ifdef CONFIG_USE_ADC_DET + if (sound_pdata->use_jackdet_type) { + ret = wm8958_mic_detect(codec, &wm1811->jack, + t0_micdet, wm1811, NULL, NULL); + } else { + ret = wm8958_mic_detect(codec, &wm1811->jack, NULL, + NULL, t0_mic_id, wm1811); + } +#else + ret = wm8958_mic_detect(codec, &wm1811->jack, NULL, + NULL, t0_mic_id, wm1811); +#endif if (ret < 0) dev_err(codec->dev, "Failed start detection: %d\n", ret); @@ -1216,6 +1351,10 @@ static int t0_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) wm8994->hubs.dcs_codes_r = sound_pdata->dcs_offset_r; } +#ifdef CONFIG_USE_ADC_DET + pr_info("%s: register adc client\n", __func__); + wm1811->padc = s3c_adc_register(t0_snd_device, NULL, NULL, 0); +#endif return snd_soc_dapm_sync(&codec->dapm); } @@ -1415,13 +1554,11 @@ static struct snd_soc_card t0_card = { .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) + if (!wm8994->jackdet || !wm8994->micdet[0].jack) return; if (wm8994->active_refcount) @@ -1529,6 +1666,15 @@ static int __init t0_audio_init(void) goto err_out_free; } +#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 if (sound_pdata->set_lineout_switch) wm1811->lineout_switch_f = sound_pdata->set_lineout_switch; @@ -1572,6 +1718,10 @@ 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); + +#ifdef CONFIG_USE_ADC_DET + s3c_adc_release(wm1811->padc); +#endif platform_device_unregister(t0_snd_device); kfree(wm1811); } |