aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/samsung/t0_wm1811.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/samsung/t0_wm1811.c')
-rw-r--r--sound/soc/samsung/t0_wm1811.c356
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);
}