audio: enable fsa8008 for earjack detection

Change-Id: Ifc6f9aa6fa6520002dec150808bb9192e48ed0a8
diff --git a/arch/arm/configs/mako-perf_defconfig b/arch/arm/configs/mako-perf_defconfig
index 61d4f69..2acd191 100644
--- a/arch/arm/configs/mako-perf_defconfig
+++ b/arch/arm/configs/mako-perf_defconfig
@@ -402,6 +402,7 @@
 CONFIG_LEDS_TRIGGERS=y
 CONFIG_LEDS_TRIGGER_HEARTBEAT=y
 CONFIG_SWITCH=y
+CONFIG_SWITCH_FSA8008=y
 CONFIG_RTC_CLASS=y
 # CONFIG_RTC_DRV_MSM is not set
 CONFIG_RTC_DRV_PM8XXX=y
diff --git a/arch/arm/mach-msm/lge/mako/board-mako-pmic.c b/arch/arm/mach-msm/lge/mako/board-mako-pmic.c
index adaf779..0c5b50c 100644
--- a/arch/arm/mach-msm/lge/mako/board-mako-pmic.c
+++ b/arch/arm/mach-msm/lge/mako/board-mako-pmic.c
@@ -117,6 +117,7 @@
 static struct pm8xxx_gpio_init pm8921_gpios[] __initdata = {
 	PM8921_GPIO_OUTPUT(17, 0, HIGH), /* CAM_VCM_EN */
 	PM8921_GPIO_OUTPUT(19, 0, HIGH), /* AMP_EN_AMP */
+	PM8921_GPIO_OUTPUT(20, 0, HIGH), /* PMIC - FSA8008 EAR_MIC_BIAS_EN */
 	PM8921_GPIO_OUTPUT(31, 0, HIGH), /* PMIC - FSA8008_EAR_MIC_EN */
 	PM8921_GPIO_INPUT(32, PM_GPIO_PULL_UP_1P5), /* PMIC - FSA8008_EARPOL_DETECT */
 	PM8921_GPIO_OUTPUT(33, 0, HIGH), /* HAPTIC_EN */
diff --git a/arch/arm/mach-msm/lge/mako/board-mako-sound.c b/arch/arm/mach-msm/lge/mako/board-mako-sound.c
index 6f364ca..92cd8de 100644
--- a/arch/arm/mach-msm/lge/mako/board-mako-sound.c
+++ b/arch/arm/mach-msm/lge/mako/board-mako-sound.c
@@ -16,14 +16,11 @@
 #include <linux/platform_device.h>
 #include <linux/mfd/pm8xxx/pm8921.h>
 #include <linux/regulator/consumer.h>
+#include <linux/platform_data/hds_fsa8008.h>
 #include "devices.h"
 
 #include "board-8064.h"
 
-#ifdef CONFIG_SWITCH_FSA8008
-#include "../../../../sound/soc/codecs/wcd9310.h"
-#endif
-
 #ifdef CONFIG_SND_SOC_TPA2028D
 #include <sound/tpa2028d.h>
 #endif
@@ -38,6 +35,7 @@
 #define AGC_FIXED_GAIN              12
 
 
+#define GPIO_EAR_MIC_BIAS_EN        PM8921_GPIO_PM_TO_SYS(20)
 #define GPIO_EAR_SENSE_N            82
 #define GPIO_EAR_MIC_EN             PM8921_GPIO_PM_TO_SYS(31)
 #define GPIO_EARPOL_DETECT          PM8921_GPIO_PM_TO_SYS(32)
@@ -58,25 +56,6 @@
 	int                    len;
 };
 
-
-struct fsa8008_platform_data {
-	const char *switch_name;  /* switch device name */
-	const char *keypad_name;  /* keypad device name */
-
-	unsigned int key_code;    /* key code for hook */
-
-	unsigned int gpio_detect; /* DET : to detect jack inserted or not */
-	unsigned int gpio_mic_en; /* EN : to enable mic */
-	unsigned int gpio_jpole;  /* JPOLE : 3pole or 4pole */
-	unsigned int gpio_key;    /* S/E button */
-
-	/* callback function which is initialized while probing */
-	void (*set_headset_mic_bias)(int enable);
-
-	/* latency for pole (3 or 4)detection (in ms) */
-	unsigned int latency_for_detection;
-};
-
 #ifdef CONFIG_SND_SOC_TPA2028D
 int amp_enable(int on_state)
 {
@@ -164,7 +143,11 @@
 }
 
 
-#ifdef CONFIG_SWITCH_FSA8008
+void enable_external_mic_bias(int status)
+{
+	gpio_set_value_cansleep(GPIO_EAR_MIC_BIAS_EN, status);
+}
+
 static struct fsa8008_platform_data lge_hs_pdata = {
 	.switch_name = "h2w",
 	.keypad_name = "hs_detect",
@@ -173,11 +156,12 @@
 
 	.gpio_detect = GPIO_EAR_SENSE_N,
 	.gpio_mic_en = GPIO_EAR_MIC_EN,
+	.gpio_mic_bias_en = GPIO_EAR_MIC_BIAS_EN,
 	.gpio_jpole  = GPIO_EARPOL_DETECT,
 	.gpio_key    = GPIO_EAR_KEY_INT,
 
 	.latency_for_detection = 75,
-	.set_headset_mic_bias = tabla_codec_micbias2_ctl,
+	.set_headset_mic_bias = enable_external_mic_bias,
 };
 
 static struct platform_device lge_hsd_device = {
@@ -194,19 +178,8 @@
 	return platform_device_register(&lge_hsd_device);
 }
 
-static void __exit lge_hsd_fsa8008_exit(void)
-{
-	printk(KERN_INFO "lge_hsd_fsa8008_exit\n");
-	platform_device_unregister(&lge_hsd_device);
-}
-#endif
-
 void __init lge_add_sound_devices(void)
 {
 	lge_add_i2c_tpa2028d_devices();
-
-#ifdef CONFIG_SWITCH_FSA8008
 	lge_hsd_fsa8008_init();
-#endif
-
 }
diff --git a/drivers/switch/Kconfig b/drivers/switch/Kconfig
index 5238591..d07e4ab 100644
--- a/drivers/switch/Kconfig
+++ b/drivers/switch/Kconfig
@@ -12,4 +12,9 @@
 	help
 	  Say Y here to enable GPIO based switch support.
 
+config SWITCH_FSA8008
+	bool "FSA8008 Headset detection driver"
+	help
+	 Say Y here to enable FSA8008 driver
+
 endif # SWITCH
diff --git a/drivers/switch/Makefile b/drivers/switch/Makefile
index f7606ed..ec71d76 100644
--- a/drivers/switch/Makefile
+++ b/drivers/switch/Makefile
@@ -1,4 +1,5 @@
 # Switch Class Driver
 obj-$(CONFIG_SWITCH)		+= switch_class.o
 obj-$(CONFIG_SWITCH_GPIO)	+= switch_gpio.o
+obj-$(CONFIG_SWITCH_FSA8008) += hds_fsa8008.o
 
diff --git a/drivers/switch/hds_fsa8008.c b/drivers/switch/hds_fsa8008.c
new file mode 100644
index 0000000..cc1e5c6
--- /dev/null
+++ b/drivers/switch/hds_fsa8008.c
@@ -0,0 +1,675 @@
+/* drivers/switch/hds_fsa8008.c
+ *
+ *  LGE 3.5 PI Headset detection driver using fsa8008.
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * Copyright (C) 2009-2012 LGE, Inc.
+ * Lee SungYoung <lsy@lge.com>
+ * Kim Eun Hye <ehgrace.kim@lge.com>
+ * Yoon Gi Souk <gisouk.yoon@lge.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/* Interface is following:
+ * android:frameworks/base/services/java/com/android/server/HeadsetObserver.java
+ * HEADSET_UEVENT_MATCH = "DEVPATH=/sys/devices/virtual/switch/h2w"
+ * HEADSET_STATE_PATH = /sys/class/switch/h2w/state
+ * HEADSET_NAME_PATH = /sys/class/switch/h2w/name
+ */
+#ifdef CONFIG_SWITCH_FSA8008
+#define CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/switch.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/mutex.h>
+#include <linux/hrtimer.h>
+#include <linux/input.h>
+#include <linux/debugfs.h>
+#include <linux/wakelock.h>
+#include <linux/platform_data/hds_fsa8008.h>
+
+#undef  HSD_DEBUG_PRINT
+#undef  HSD_ERROR_PRINT
+
+/* TODO */
+/* 1. coding for additional excetion case in probe function */
+/* 2. additional sleep related/excetional case  */
+
+#if defined(HSD_DEBUG_PRINT)
+#define HSD_DBG(fmt, args...) printk(KERN_INFO "HSD.fsa8008[%-18s:%5d]" fmt, __func__, __LINE__, ## args)
+#else
+#define HSD_DBG(fmt, args...) do {} while (0)
+#endif
+
+#if defined(HSD_ERROR_PRINT)
+#define HSD_ERR(fmt, args...) printk(KERN_ERR "HSD.fsa8008[%-18s:%5d]" fmt, __func__, __LINE__, ## args)
+#else
+#define HSD_ERR(fmt, args...) do { } while (0)
+#endif
+
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+static struct workqueue_struct *local_fsa8008_workqueue;
+#endif
+
+static struct wake_lock ear_hook_wake_lock;
+
+struct hsd_info {
+	/* function devices provided by this driver */
+	struct switch_dev sdev;
+	struct input_dev *input;
+	/* mutex */
+	struct mutex mutex_lock;
+	/* h/w configuration : initilized by platform data */
+	unsigned int gpio_detect; /* DET : to detect jack inserted or not */
+	unsigned int gpio_mic_en; /* EN : to enable mic */
+	unsigned int gpio_mic_bias_en; /* EN : to enable mic bias */
+	unsigned int gpio_jpole;  /* JPOLE : 3pole or 4pole */
+	unsigned int gpio_key;    /* S/E button */
+
+	/* callback function which is initialized while probing */
+	void (*set_headset_mic_bias)(int enable);
+
+	unsigned int latency_for_detection;
+	unsigned int latency_for_key;
+
+	unsigned int key_code;
+
+	/* irqs */
+	unsigned int irq_detect;
+	unsigned int irq_key;
+	/* internal states */
+	atomic_t irq_key_enabled;
+	atomic_t is_3_pole_or_not;
+	atomic_t btn_state;
+	/* work for detect_work */
+	struct delayed_work work;
+	struct delayed_work work_for_key_pressed;
+	struct delayed_work work_for_key_released;
+};
+
+enum {
+	NO_DEVICE   = 0,
+	HEADSET_WITH_MIC = (1 << 0),
+	HEADSET_NO_MIC = (1 << 1),
+};
+enum {
+	HEADSET_INSERT = 0,
+	HEADSET_REMOVE = 1,
+};
+
+enum {
+	HEADSET_4POLE = 0,
+	HEADSET_3POLE = 1,
+};
+
+enum {
+	FALSE = 0,
+	TRUE = 1,
+};
+
+static ssize_t hsd_print_name(struct switch_dev *sdev, char *buf)
+{
+	switch (switch_get_state(sdev)) {
+	case NO_DEVICE:
+		return sprintf(buf, "No Device\n");
+	case HEADSET_WITH_MIC:
+		return sprintf(buf, "Headset\n");
+	case HEADSET_NO_MIC:
+		return sprintf(buf, "Headset_no_mic\n");
+
+	}
+	return -EINVAL;
+}
+
+static ssize_t hsd_print_state(struct switch_dev *sdev, char *buf)
+{
+	return sprintf(buf, "%d\n", switch_get_state(sdev));
+}
+
+static void button_pressed(struct work_struct *work)
+{
+	struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+	struct hsd_info *hi = container_of(dwork, struct hsd_info, work_for_key_pressed);
+
+
+	if (gpio_get_value_cansleep(hi->gpio_detect) &&
+			(switch_get_state(&hi->sdev)== HEADSET_WITH_MIC)) {
+		HSD_ERR("button_pressed but ear jack is plugged out already!"
+			"just ignore the event.\n");
+		return;
+	}
+
+	HSD_DBG("button_pressed \n");
+
+	atomic_set(&hi->btn_state, 1);
+	input_report_key(hi->input, hi->key_code, 1);
+	input_sync(hi->input);
+}
+
+static void button_released(struct work_struct *work)
+{
+	struct delayed_work *dwork = container_of(
+			work, struct delayed_work, work);
+	struct hsd_info *hi = container_of(
+			dwork, struct hsd_info, work_for_key_released);
+
+	if (gpio_get_value_cansleep(hi->gpio_detect) &&
+			(switch_get_state(&hi->sdev)== HEADSET_WITH_MIC)){
+		HSD_ERR("button_released but ear jack is plugged out already!"
+			"just ignore the event.\n");
+		return;
+	}
+
+	HSD_DBG("button_released \n");
+
+	atomic_set(&hi->btn_state, 0);
+	input_report_key(hi->input, hi->key_code, 0);
+	input_sync(hi->input);
+}
+
+static void insert_headset(struct hsd_info *hi)
+{
+	int earjack_type;
+
+	HSD_DBG("insert_headset");
+
+	if (hi->set_headset_mic_bias)
+		hi->set_headset_mic_bias(TRUE);
+
+	gpio_set_value_cansleep(hi->gpio_mic_en, 1);
+
+	msleep(hi->latency_for_detection);
+
+	earjack_type = gpio_get_value_cansleep(hi->gpio_jpole);
+
+	if (earjack_type == HEADSET_3POLE) {
+		HSD_DBG("3 polarity earjack");
+
+		atomic_set(&hi->is_3_pole_or_not, 1);
+
+		mutex_lock(&hi->mutex_lock);
+		switch_set_state(&hi->sdev, HEADSET_NO_MIC);
+		mutex_unlock(&hi->mutex_lock);
+
+		gpio_set_value_cansleep(hi->gpio_mic_en, 0);
+		if (hi->set_headset_mic_bias)
+			hi->set_headset_mic_bias(FALSE);
+
+		input_report_switch(hi->input, SW_HEADPHONE_INSERT, 1);
+		input_sync(hi->input);
+	} else {
+		HSD_DBG("4 polarity earjack");
+
+		atomic_set(&hi->is_3_pole_or_not, 0);
+
+		mutex_lock(&hi->mutex_lock);
+		switch_set_state(&hi->sdev, HEADSET_WITH_MIC);
+		mutex_unlock(&hi->mutex_lock);
+
+		if (!atomic_read(&hi->irq_key_enabled)) {
+			unsigned long irq_flags;
+			HSD_DBG("enable_irq - irq_key");
+			local_irq_save(irq_flags);
+			enable_irq(hi->irq_key);
+			local_irq_restore(irq_flags);
+
+			atomic_set(&hi->irq_key_enabled, TRUE);
+		}
+		input_report_switch(hi->input, SW_HEADPHONE_INSERT, 1);
+		input_report_switch(hi->input, SW_MICROPHONE_INSERT, 1);
+		input_sync(hi->input);
+	}
+}
+
+static void remove_headset(struct hsd_info *hi)
+{
+	int has_mic = switch_get_state(&hi->sdev);
+
+	HSD_DBG("remove_headset");
+
+	gpio_set_value_cansleep(hi->gpio_mic_en, 0);
+	if (hi->set_headset_mic_bias)
+		hi->set_headset_mic_bias(FALSE);
+
+	atomic_set(&hi->is_3_pole_or_not, 1);
+	mutex_lock(&hi->mutex_lock);
+	switch_set_state(&hi->sdev, NO_DEVICE);
+	mutex_unlock(&hi->mutex_lock);
+
+	if (atomic_read(&hi->irq_key_enabled)) {
+		unsigned long irq_flags;
+
+		local_irq_save(irq_flags);
+		disable_irq(hi->irq_key);
+		local_irq_restore(irq_flags);
+		atomic_set(&hi->irq_key_enabled, FALSE);
+	}
+
+	if (atomic_read(&hi->btn_state))
+#ifdef	CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+	queue_delayed_work(local_fsa8008_workqueue,
+			&(hi->work_for_key_released), hi->latency_for_key );
+#else
+	schedule_delayed_work(&(hi->work_for_key_released),
+			hi->latency_for_key );
+#endif
+	input_report_switch(hi->input, SW_HEADPHONE_INSERT, 0);
+	if (has_mic == HEADSET_WITH_MIC)
+		input_report_switch(hi->input, SW_MICROPHONE_INSERT, 0);
+	input_sync(hi->input);
+}
+
+static void detect_work(struct work_struct *work)
+{
+	int state;
+	struct delayed_work *dwork = container_of(
+			work, struct delayed_work, work);
+	struct hsd_info *hi = container_of(dwork, struct hsd_info, work);
+
+	HSD_DBG("detect_work");
+
+	state = gpio_get_value_cansleep(hi->gpio_detect);
+
+	if (state == HEADSET_REMOVE) {
+		if (switch_get_state(&hi->sdev) != NO_DEVICE) {
+			HSD_DBG("headset removing\n");
+			remove_headset(hi);
+		} else {
+			HSD_DBG("err_invalid_state state = %d\n", state);
+		}
+	} else {
+
+		if (switch_get_state(&hi->sdev) == NO_DEVICE) {
+			HSD_DBG("headset inserting\n");
+			insert_headset(hi);
+		} else {
+			HSD_DBG("err_invalid_state state = %d\n", state);
+		}
+	}
+
+}
+
+static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
+{
+	struct hsd_info *hi = (struct hsd_info *) dev_id;
+
+	wake_lock_timeout(&ear_hook_wake_lock, 2 * HZ);
+
+	HSD_DBG("gpio_irq_handler");
+
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+	queue_delayed_work(local_fsa8008_workqueue, &(hi->work), HZ/2 ); /* 500ms */
+#else
+	schedule_delayed_work(&(hi->work), HZ/2); /* 500ms */
+#endif
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t button_irq_handler(int irq, void *dev_id)
+{
+	struct hsd_info *hi = (struct hsd_info *) dev_id;
+	int value;
+
+	wake_lock_timeout(&ear_hook_wake_lock, 2 * HZ);
+
+	HSD_DBG("button_irq_handler");
+
+	value = gpio_get_value_cansleep(hi->gpio_key);
+
+#ifdef	CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+	if (value)
+		queue_delayed_work(local_fsa8008_workqueue,
+				&(hi->work_for_key_pressed),
+				hi->latency_for_key );
+	else
+		queue_delayed_work(local_fsa8008_workqueue,
+				&(hi->work_for_key_released),
+				hi->latency_for_key );
+#else
+	if (value)
+		schedule_delayed_work(&(hi->work_for_key_pressed),
+				hi->latency_for_key );
+	else
+		schedule_delayed_work(&(hi->work_for_key_released),
+				hi->latency_for_key );
+#endif
+
+	return IRQ_HANDLED;
+}
+
+static int hsd_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct fsa8008_platform_data *pdata = pdev->dev.platform_data;
+
+	struct hsd_info *hi;
+
+	HSD_DBG("hsd_probe");
+
+	hi = kzalloc(sizeof(struct hsd_info), GFP_KERNEL);
+	if (NULL == hi) {
+		HSD_ERR("Failed to allloate headset per device info\n");
+		return -ENOMEM;
+	}
+
+	hi->key_code = pdata->key_code;
+
+	platform_set_drvdata(pdev, hi);
+
+	atomic_set(&hi->btn_state, 0);
+	atomic_set(&hi->is_3_pole_or_not, 1);
+
+	hi->gpio_detect = pdata->gpio_detect;
+	hi->gpio_mic_en = pdata->gpio_mic_en;
+	hi->gpio_mic_bias_en = pdata->gpio_mic_bias_en;
+	hi->gpio_jpole = pdata->gpio_jpole;
+	hi->gpio_key = pdata->gpio_key;
+	hi->set_headset_mic_bias = pdata->set_headset_mic_bias;
+
+	hi->latency_for_detection = pdata->latency_for_detection;
+
+	/* 200 ms * HZ / 100 */
+	hi->latency_for_key = 200 * HZ / 1000; /* convert milli to jiffies */
+	mutex_init(&hi->mutex_lock);
+	INIT_DELAYED_WORK(&hi->work, detect_work);
+	INIT_DELAYED_WORK(&hi->work_for_key_pressed, button_pressed);
+	INIT_DELAYED_WORK(&hi->work_for_key_released, button_released);
+
+	/* initialize gpio_detect */
+	ret = gpio_request(hi->gpio_detect, "gpio_detect");
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_request gpio%d (gpio_detect)\n",
+				hi->gpio_detect);
+		goto error_01;
+	}
+
+	ret = gpio_direction_input(hi->gpio_detect);
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_direction_input gpio%d (gpio_detect)\n",
+				hi->gpio_detect);
+		goto error_02;
+	}
+
+	/* initialize gpio_jpole */
+	ret = gpio_request(hi->gpio_jpole, "gpio_jpole");
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_request gpio%d (gpio_jpole)\n",
+				hi->gpio_jpole);
+		goto error_02;
+	}
+
+	ret = gpio_direction_input(hi->gpio_jpole);
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_direction_input gpio%d (gpio_jpole)\n",
+				hi->gpio_jpole);
+		goto error_03;
+	}
+
+	/* initialize gpio_key */
+	ret = gpio_request(hi->gpio_key, "gpio_key");
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_request gpio%d (gpio_key)\n",
+				hi->gpio_key);
+		goto error_03;
+	}
+
+	ret = gpio_direction_input(hi->gpio_key);
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_direction_input gpio%d (gpio_key)\n",
+				hi->gpio_key);
+		goto error_04;
+	}
+
+	/* initialize gpio_mic_en */
+	ret = gpio_request(hi->gpio_mic_en, "gpio_mic_en");
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_request gpio%d (gpio_mic_en)\n",
+				hi->gpio_mic_en);
+		goto error_04;
+	}
+
+	ret = gpio_direction_output(hi->gpio_mic_en, 0);
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_direction_output gpio%d (gpio_mic_en)\n",
+				hi->gpio_mic_en);
+		goto error_05;
+	}
+
+	/* initialize gpio_mic_bias_en */
+	ret = gpio_request(hi->gpio_mic_bias_en, "gpio_mic_bias_en");
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_request gpio%d (gpio_mic_bias_en)\n",
+				hi->gpio_mic_en);
+		goto error_04;
+	}
+
+	ret = gpio_direction_output(hi->gpio_mic_bias_en, 0);
+	if (ret < 0) {
+		HSD_ERR("Failed to gpio_direction_output gpio%d (gpio_mic_bias_en)\n",
+				hi->gpio_mic_en);
+		goto error_05;
+	}
+
+	/* initialize irq of gpio_jpole */
+	hi->irq_detect = gpio_to_irq(hi->gpio_detect);
+	HSD_DBG("hi->irq_detect = %d\n", hi->irq_detect);
+	if (hi->irq_detect < 0) {
+		HSD_ERR("Failed to get interrupt number\n");
+		ret = hi->irq_detect;
+		goto error_05;
+	}
+
+	ret = request_threaded_irq(hi->irq_detect, NULL, gpio_irq_handler,
+			IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
+			pdev->name, hi);
+	if (ret) {
+		HSD_ERR("failed to request button irq");
+		goto error_05;
+	}
+
+	ret = irq_set_irq_wake(hi->irq_detect, 1);
+	if (ret < 0) {
+		HSD_ERR("Failed to set irq_detect interrupt wake\n");
+		goto error_06;
+	}
+
+	/* initialize irq of gpio_key */
+	hi->irq_key = gpio_to_irq(hi->gpio_key);
+	HSD_DBG("hi->irq_key = %d\n", hi->irq_key);
+	if (hi->irq_key < 0) {
+		HSD_ERR("Failed to get interrupt number\n");
+		ret = hi->irq_key;
+		goto error_06;
+	}
+
+	ret = request_threaded_irq(hi->irq_key, NULL, button_irq_handler,
+			IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
+			pdev->name, hi);
+	if (ret) {
+		HSD_ERR("failed to request button irq");
+		goto error_06;
+	}
+
+	disable_irq(hi->irq_key);
+
+	ret = irq_set_irq_wake(hi->irq_key, 1);
+	if (ret < 0) {
+		HSD_ERR("Failed to set irq_key interrupt wake\n");
+		goto error_07;
+	}
+
+	/* initialize switch device */
+	hi->sdev.name = pdata->switch_name;
+	hi->sdev.print_state = hsd_print_state;
+	hi->sdev.print_name = hsd_print_name;
+
+	ret = switch_dev_register(&hi->sdev);
+	if (ret < 0) {
+		HSD_ERR("Failed to register switch device\n");
+		goto error_07;
+	}
+
+	/* initialize input device */
+	hi->input = input_allocate_device();
+	if (!hi->input) {
+		HSD_ERR("Failed to allocate input device\n");
+		ret = -ENOMEM;
+		goto error_08;
+	}
+
+	hi->input->name = pdata->keypad_name;
+
+	hi->input->id.vendor    = 0x0001;
+	hi->input->id.product   = 1;
+	hi->input->id.version   = 1;
+
+	set_bit(EV_SYN, hi->input->evbit);
+	set_bit(EV_KEY, hi->input->evbit);
+	set_bit(EV_SW, hi->input->evbit);
+	set_bit(hi->key_code, hi->input->keybit);
+	set_bit(SW_HEADPHONE_INSERT, hi->input->swbit);
+	set_bit(SW_MICROPHONE_INSERT, hi->input->swbit);
+
+	ret = input_register_device(hi->input);
+	if (ret) {
+		HSD_ERR("Failed to register input device\n");
+		goto error_09;
+	}
+
+	if (!gpio_get_value_cansleep(hi->gpio_detect)) {
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+		/* to detect in initialization with eacjack insertion */
+		queue_delayed_work(local_fsa8008_workqueue, &(hi->work), 0);
+#else
+		/* to detect in initialization with eacjack insertion */
+		schedule_delayed_work(&(hi->work), 0);
+#endif
+	}
+
+#ifdef AT_TEST_GPKD
+	ret = device_create_file(&pdev->dev, &dev_attr_hookkeylog);
+#endif
+
+	return ret;
+
+error_09:
+	input_free_device(hi->input);
+error_08:
+	switch_dev_unregister(&hi->sdev);
+error_07:
+	free_irq(hi->irq_key, 0);
+error_06:
+	free_irq(hi->irq_detect, 0);
+error_05:
+	gpio_free(hi->gpio_mic_en);
+error_04:
+	gpio_free(hi->gpio_key);
+error_03:
+	gpio_free(hi->gpio_jpole);
+error_02:
+	gpio_free(hi->gpio_detect);
+error_01:
+	mutex_destroy(&hi->mutex_lock);
+	kfree(hi);
+
+	return ret;
+}
+
+static int hsd_remove(struct platform_device *pdev)
+{
+	struct hsd_info *hi = (struct hsd_info *)platform_get_drvdata(pdev);
+
+	HSD_DBG("hsd_remove");
+
+	if (switch_get_state(&hi->sdev))
+		remove_headset(hi);
+
+	input_unregister_device(hi->input);
+	switch_dev_unregister(&hi->sdev);
+
+	free_irq(hi->irq_key, 0);
+	free_irq(hi->irq_detect, 0);
+
+	gpio_free(hi->gpio_mic_en);
+	gpio_free(hi->gpio_mic_bias_en);
+	gpio_free(hi->gpio_key);
+	gpio_free(hi->gpio_jpole);
+	gpio_free(hi->gpio_detect);
+
+	mutex_destroy(&hi->mutex_lock);
+
+	kfree(hi);
+
+	return 0;
+}
+
+static struct platform_driver hsd_driver = {
+	.probe          = hsd_probe,
+	.remove         = hsd_remove,
+	.driver         = {
+		.name   = "fsa8008",
+		.owner  = THIS_MODULE,
+	},
+};
+
+static int __init hsd_init(void)
+{
+	int ret;
+
+	HSD_DBG("hsd_init");
+
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+	local_fsa8008_workqueue = create_workqueue("fsa8008");
+	if(!local_fsa8008_workqueue)
+		return -ENOMEM;
+#endif
+
+	ret = platform_driver_register(&hsd_driver);
+	if (ret)
+		HSD_ERR("Fail to register platform driver\n");
+
+	wake_lock_init(&ear_hook_wake_lock, WAKE_LOCK_SUSPEND, "ear_hook");
+
+	return ret;
+}
+
+static void __exit hsd_exit(void)
+{
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+	if (local_fsa8008_workqueue)
+		destroy_workqueue(local_fsa8008_workqueue);
+	local_fsa8008_workqueue = NULL;
+#endif
+
+	platform_driver_unregister(&hsd_driver);
+	wake_lock_destroy(&ear_hook_wake_lock);
+	HSD_DBG("hsd_exit");
+}
+
+/* to make init after pmic8058-othc module */
+late_initcall_sync(hsd_init);
+module_exit(hsd_exit);
+
+MODULE_AUTHOR("Yoon Gi Souk <gisouk.yoon@lge.com>");
+MODULE_DESCRIPTION("FSA8008 Headset detection driver");
+MODULE_LICENSE("GPL");
+#endif
diff --git a/include/linux/platform_data/hds_fsa8008.h b/include/linux/platform_data/hds_fsa8008.h
new file mode 100644
index 0000000..f076c8b
--- /dev/null
+++ b/include/linux/platform_data/hds_fsa8008.h
@@ -0,0 +1,37 @@
+/* include/linux/platform_data/hds_fsa8008.h
+ *
+ * Copyright (C) 2012 LG Electronics Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LGE_ANDROID_USB_H__
+#define __LGE_ANDROID_USB_H__
+
+struct fsa8008_platform_data {
+	const char *switch_name;  /* switch device name */
+	const char *keypad_name;  /* keypad device name */
+
+	unsigned int key_code;    /* key code for hook */
+
+	unsigned int gpio_detect; /* DET : to detect jack inserted or not */
+	unsigned int gpio_mic_en; /* EN : to enable mic */
+	unsigned int gpio_mic_bias_en; /* EN : to enable mic bias */
+	unsigned int gpio_jpole;  /* JPOLE : 3pole or 4pole */
+	unsigned int gpio_key;    /* S/E button */
+
+	/* callback function which is initialized while probing */
+	void (*set_headset_mic_bias)(int enable);
+
+	/* latency for pole (3 or 4)detection (in ms) */
+	unsigned int latency_for_detection;
+};
+
+#endif /* __LGE_ANDROID_USB_H__ */
diff --git a/sound/soc/msm/apq8064.c b/sound/soc/msm/apq8064.c
index 862a666..1d81083 100644
--- a/sound/soc/msm/apq8064.c
+++ b/sound/soc/msm/apq8064.c
@@ -31,9 +31,7 @@
 #ifdef CONFIG_SND_SOC_TPA2028D
 #include <sound/tpa2028d.h>
 #endif
-
 /* 8064 machine driver */
-
 #define PM8921_GPIO_BASE		NR_GPIO_IRQS
 #define PM8921_GPIO_PM_TO_SYS(pm_gpio)  (pm_gpio - 1 + PM8921_GPIO_BASE)
 
@@ -99,7 +97,9 @@
 static struct clk *codec_clk;
 static int clk_users;
 
+#ifndef CONFIG_SWITCH_FSA8008
 static int msm_headset_gpios_configured;
+#endif
 
 static struct snd_soc_jack hs_jack;
 static struct snd_soc_jack button_jack;
@@ -1134,7 +1134,9 @@
 static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd)
 {
 	int err;
+#ifndef CONFIG_SWITCH_FSA8008
 	uint32_t revision;
+#endif
 	struct snd_soc_codec *codec = rtd->codec;
 	struct snd_soc_dapm_context *dapm = &codec->dapm;
 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
@@ -1188,6 +1190,7 @@
 
 	codec_clk = clk_get(cpu_dai->dev, "osr_clk");
 
+#ifndef CONFIG_SWITCH_FSA8008
 	/* APQ8064 Rev 1.1 CDP and Liquid have mechanical switch */
 	revision = socinfo_get_version();
 	if (apq8064_hs_detect_use_gpio != -1) {
@@ -1233,6 +1236,9 @@
 	err = tabla_hs_detect(codec, &mbhc_cfg);
 
 	return err;
+#else
+	return 0;
+#endif
 }
 
 static int msm_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
@@ -1349,6 +1355,7 @@
 	int ret = 0;
 
 	pr_debug("%s\n", __func__);
+
 #ifdef CONFIG_SND_SOC_QDSP6_HDMI_AUDIO
 	ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT");
 	if (ret < 0) {
@@ -1377,7 +1384,6 @@
 		goto fail_clk;
 	}
 #endif
-
 	return 0;
 
 #ifdef CONFIG_SND_SOC_QDSP6_HDMI_AUDIO
@@ -1389,7 +1395,6 @@
 	gpio_free(GPIO_AUX_PCM_DOUT);
 fail_dout:
 #endif
-
 	return ret;
 }
 
@@ -1470,14 +1475,12 @@
 	.hw_params = msm_slimbus_4_hw_params,
 	.shutdown = msm_shutdown,
 };
-
 static struct snd_soc_ops msm_slimbus_2_be_ops = {
 	.startup = msm_startup,
 	.hw_params = msm_slimbus_2_hw_params,
 	.shutdown = msm_shutdown,
 };
 
-
 /* Digital audio interface glue - connects codec <---> CPU */
 static struct snd_soc_dai_link msm_dai[] = {
 	/* FrontEnd DAI Links */
@@ -1929,6 +1932,7 @@
 
 static struct platform_device *msm_snd_device;
 
+#ifndef CONFIG_SWITCH_FSA8008
 static int msm_configure_headset_mic_gpios(void)
 {
 	int ret;
@@ -1969,15 +1973,17 @@
 			PM8921_GPIO_PM_TO_SYS(35));
 	else
 		gpio_direction_output(PM8921_GPIO_PM_TO_SYS(35), 0);
-
 	return 0;
 }
+#endif
 static void msm_free_headset_mic_gpios(void)
 {
+#ifndef CONFIG_SWITCH_FSA8008
 	if (msm_headset_gpios_configured) {
 		gpio_free(PM8921_GPIO_PM_TO_SYS(23));
 		gpio_free(PM8921_GPIO_PM_TO_SYS(35));
 	}
+#endif
 }
 
 static int __init msm_audio_init(void)
@@ -2010,12 +2016,13 @@
 		return ret;
 	}
 
+#ifndef CONFIG_SWITCH_FSA8008
 	if (msm_configure_headset_mic_gpios()) {
 		pr_err("%s Fail to configure headset mic gpios\n", __func__);
 		msm_headset_gpios_configured = 0;
 	} else
 		msm_headset_gpios_configured = 1;
-
+#endif
 	return ret;
 
 }