Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/htc_headset.c b/arch/arm/mach-msm/htc_headset.c
new file mode 100644
index 0000000..a69a2e1
--- /dev/null
+++ b/arch/arm/mach-msm/htc_headset.c
@@ -0,0 +1,1246 @@
+/*
+ *  H2W device detection driver.
+ *
+ *  Copyright (C) 2008 Google, Inc.
+ *  Copyright (C) 2008 HTC, Inc.
+ *
+ *  Authors:
+ *      Laurence Chen <Laurence_Chen@htc.com>
+ *      Nick Pelly <npelly@google.com>
+ *      Thomas Tsai <thomas_tsai@htc.com>
+ *      Farmer Tseng <farmer_tseng@htc.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ */
+
+/*  For detecting HTC 2 Wire devices, such as wired headset.
+
+    Logically, the H2W driver is always present, and H2W state (hi->state)
+    indicates what is currently plugged into the H2W interface.
+
+    When the headset is plugged in, CABLE_IN1 is pulled low. When the headset
+    button is pressed, CABLE_IN2 is pulled low. These two lines are shared with
+    the TX and RX (respectively) of UART3 - used for serial debugging.
+
+    This headset driver keeps the CPLD configured as UART3 for as long as
+    possible, so that we can do serial FIQ debugging even when the kernel is
+    locked and this driver no longer runs. So it only configures the CPLD to
+    GPIO while the headset is plugged in, and for 10ms during detection work.
+
+    Unfortunately we can't leave the CPLD as UART3 while a headset is plugged
+    in, UART3 is pullup on TX but the headset is pull-down, causing a 55 mA
+    drain on trout.
+
+    The headset detection work involves setting CPLD to GPIO, and then pulling
+    CABLE_IN1 high with a stronger pullup than usual. A H2W headset will still
+    pull this line low, whereas other attachments such as a serial console
+    would get pulled up by this stronger pullup.
+
+    Headset insertion/removal causes UEvent's to be sent, and
+    /sys/class/switch/h2w/state to be updated.
+
+    Button presses are interpreted as input event (KEY_MEDIA). Button presses
+    are ignored if the headset is plugged in, so the buttons on 11 pin -> 3.5mm
+    jack adapters do not work until a headset is plugged into the adapter. This
+    is to avoid serial RX traffic causing spurious button press events.
+
+    We tend to check the status of CABLE_IN1 a few more times than strictly
+    necessary during headset detection, to avoid spurious headset insertion
+    events caused by serial debugger TX traffic.
+*/
+
+#include <linux/module.h>
+#include <linux/sysdev.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/hrtimer.h>
+#include <linux/switch.h>
+#include <linux/input.h>
+#include <linux/debugfs.h>
+#include <asm/gpio.h>
+#include <asm/atomic.h>
+#include <mach/board.h>
+#include <mach/vreg.h>
+#include <asm/mach-types.h>
+
+#include <mach/htc_headset.h>
+
+#define H2WI(fmt, arg...) \
+	printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg)
+#define H2WE(fmt, arg...) \
+	printk(KERN_ERR "[H2W] %s " fmt "\r\n", __func__, ## arg)
+
+#ifdef CONFIG_DEBUG_H2W
+#define H2W_DBG(fmt, arg...) printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg)
+#else
+#define H2W_DBG(fmt, arg...) do {} while (0)
+#endif
+
+static struct workqueue_struct *g_detection_work_queue;
+static void detection_work(struct work_struct *work);
+static DECLARE_WORK(g_detection_work, detection_work);
+
+struct h2w_info {
+	struct switch_dev sdev;
+	struct input_dev *input;
+	struct mutex mutex_lock;
+
+	atomic_t btn_state;
+	int ignore_btn;
+
+	unsigned int irq;
+	unsigned int irq_btn;
+
+	int cable_in1;
+	int cable_in2;
+	int h2w_clk;
+	int h2w_data;
+	int debug_uart;
+
+	void (*config_cpld) (int);
+	void (*init_cpld) (void);
+	/* for h2w */
+	void (*set_dat)(int);
+	void (*set_clk)(int);
+	void (*set_dat_dir)(int);
+	void (*set_clk_dir)(int);
+	int (*get_dat)(void);
+	int (*get_clk)(void);
+
+	int htc_headset_flag;
+
+	struct hrtimer timer;
+	ktime_t debounce_time;
+
+	struct hrtimer btn_timer;
+	ktime_t btn_debounce_time;
+
+	H2W_INFO h2w_info;
+	H2W_SPEED speed;
+	struct vreg *vreg_h2w;
+};
+static struct h2w_info *hi;
+
+static ssize_t h2w_print_name(struct switch_dev *sdev, char *buf)
+{
+	switch (switch_get_state(&hi->sdev)) {
+	case H2W_NO_DEVICE:
+		return sprintf(buf, "No Device\n");
+	case H2W_HTC_HEADSET:
+		return sprintf(buf, "Headset\n");
+	}
+	return -EINVAL;
+}
+
+static void button_pressed(void)
+{
+	H2W_DBG("button_pressed \n");
+	atomic_set(&hi->btn_state, 1);
+	input_report_key(hi->input, KEY_MEDIA, 1);
+	input_sync(hi->input);
+}
+
+static void button_released(void)
+{
+	H2W_DBG("button_released \n");
+	atomic_set(&hi->btn_state, 0);
+	input_report_key(hi->input, KEY_MEDIA, 0);
+	input_sync(hi->input);
+}
+
+/*****************
+ * H2W proctocol *
+ *****************/
+static inline void h2w_begin_command(void)
+{
+	/* Disable H2W interrupt */
+	set_irq_type(hi->irq_btn, IRQF_TRIGGER_HIGH);
+	disable_irq(hi->irq);
+	disable_irq(hi->irq_btn);
+
+	/* Set H2W_CLK as output low */
+	hi->set_clk(0);
+	hi->set_clk_dir(1);
+}
+
+static inline void h2w_end_command(void)
+{
+	/* Set H2W_CLK as input */
+	hi->set_clk_dir(0);
+
+	/* Enable H2W interrupt */
+	enable_irq(hi->irq);
+	enable_irq(hi->irq_btn);
+	set_irq_type(hi->irq_btn, IRQF_TRIGGER_RISING);
+}
+
+/*
+ * One bit write data
+ *                     ________
+ *       SCLK O ______|        |______O(L)
+ *
+ *
+ *	     SDAT I <XXXXXXXXXXXXXXXXXXXX>
+ */
+static inline void one_clock_write(unsigned short flag)
+{
+	if (flag)
+		hi->set_dat(1);
+	else
+		hi->set_dat(0);
+
+	udelay(hi->speed);
+	hi->set_clk(1);
+	udelay(hi->speed);
+	hi->set_clk(0);
+}
+
+/*
+ * One bit write data R/W bit
+ *                   ________
+ *       SCLK ______|        |______O(L)
+ *            1---->         1----->
+ *                  2-------> ______
+ *	 SDAT <XXXXXXXXXXXXXX>      I
+ *                         O(H/L)
+ */
+static inline void one_clock_write_RWbit(unsigned short flag)
+{
+	if (flag)
+		hi->set_dat(1);
+	else
+		hi->set_dat(0);
+
+	udelay(hi->speed);
+	hi->set_clk(1);
+	udelay(hi->speed);
+	hi->set_clk(0);
+	hi->set_dat_dir(0);
+	udelay(hi->speed);
+}
+
+/*
+ * H2W Reset
+ *                       ___________
+ *       SCLK O(L)______|           |___O(L)
+ *                1---->
+ *                      4-->1-->1-->1us-->
+ *                          ____
+ *       SDAT O(L)________ |    |_______O(L)
+ *
+ * H2w reset command needs to be issued before every access
+ */
+static inline void h2w_reset(void)
+{
+	/* Set H2W_DAT as output low */
+	hi->set_dat(0);
+	hi->set_dat_dir(1);
+
+	udelay(hi->speed);
+	hi->set_clk(1);
+	udelay(4 * hi->speed);
+	hi->set_dat(1);
+	udelay(hi->speed);
+	hi->set_dat(0);
+	udelay(hi->speed);
+	hi->set_clk(0);
+	udelay(hi->speed);
+}
+
+/*
+ * H2W Start
+ *                       ___________
+ *       SCLK O(L)______|           |___O(L)
+ *                1---->
+ *                      2----------->1-->
+ *
+ *       SDAT O(L)______________________O(L)
+ */
+static inline void h2w_start(void)
+{
+	udelay(hi->speed);
+	hi->set_clk(1);
+	udelay(2 * hi->speed);
+	hi->set_clk(0);
+	udelay(hi->speed);
+}
+
+/*
+ * H2W Ack
+ *                  __________
+ *       SCLK _____|          |_______O(L)
+ *            1---->	      1------>
+ *		           2--------->
+ *            ________________________
+ *	 SDAT  become Input mode here I
+ */
+static inline int h2w_ack(void)
+{
+	int retry_times = 0;
+
+ack_resend:
+	if (retry_times == MAX_ACK_RESEND_TIMES)
+		return -1;
+
+	udelay(hi->speed);
+	hi->set_clk(1);
+	udelay(2 * hi->speed);
+
+	if (!hi->get_dat()) {
+		retry_times++;
+		hi->set_clk(0);
+		udelay(hi->speed);
+		goto ack_resend;
+	}
+
+	hi->set_clk(0);
+	udelay(hi->speed);
+	return 0;
+}
+
+/*
+ * One bit read data
+ *                   ________
+ *       SCLK ______|        |______O(L)
+ *            2---->         2----->
+ *                  2------->
+ *	 SDAT <XXXXXXXXXXXXXXXXXXXX>I
+ */
+static unsigned char h2w_readc(void)
+{
+	unsigned char h2w_read_data = 0x0;
+	int index;
+
+	for (index = 0; index < 8; index++) {
+		hi->set_clk(0);
+		udelay(hi->speed);
+		hi->set_clk(1);
+		udelay(hi->speed);
+		if (hi->get_dat())
+			h2w_read_data |= (1 << (7 - index));
+	}
+	hi->set_clk(0);
+	udelay(hi->speed);
+
+	return h2w_read_data;
+}
+
+static int h2w_readc_cmd(H2W_ADDR address)
+{
+	int ret = -1, retry_times = 0;
+	unsigned char read_data;
+
+read_resend:
+	if (retry_times == MAX_HOST_RESEND_TIMES)
+		goto err_read;
+
+	h2w_reset();
+	h2w_start();
+	/* Write address */
+	one_clock_write(address & 0x1000);
+	one_clock_write(address & 0x0800);
+	one_clock_write(address & 0x0400);
+	one_clock_write(address & 0x0200);
+	one_clock_write(address & 0x0100);
+	one_clock_write(address & 0x0080);
+	one_clock_write(address & 0x0040);
+	one_clock_write(address & 0x0020);
+	one_clock_write(address & 0x0010);
+	one_clock_write(address & 0x0008);
+	one_clock_write(address & 0x0004);
+	one_clock_write(address & 0x0002);
+	one_clock_write(address & 0x0001);
+	one_clock_write_RWbit(1);
+	if (h2w_ack() < 0) {
+		H2W_DBG("Addr NO ACK(%d).\n", retry_times);
+		retry_times++;
+		hi->set_clk(0);
+		mdelay(RESEND_DELAY);
+		goto read_resend;
+	}
+
+	read_data = h2w_readc();
+
+	if (h2w_ack() < 0) {
+		H2W_DBG("Data NO ACK(%d).\n", retry_times);
+		retry_times++;
+		hi->set_clk(0);
+		mdelay(RESEND_DELAY);
+		goto read_resend;
+	}
+	ret = (int)read_data;
+
+err_read:
+	if (ret < 0)
+		H2WE("NO ACK.\n");
+
+	return ret;
+}
+
+static int h2w_writec_cmd(H2W_ADDR address, unsigned char data)
+{
+	int ret = -1;
+	int retry_times = 0;
+
+write_resend:
+	if (retry_times == MAX_HOST_RESEND_TIMES)
+		goto err_write;
+
+	h2w_reset();
+	h2w_start();
+
+	/* Write address */
+	one_clock_write(address & 0x1000);
+	one_clock_write(address & 0x0800);
+	one_clock_write(address & 0x0400);
+	one_clock_write(address & 0x0200);
+	one_clock_write(address & 0x0100);
+	one_clock_write(address & 0x0080);
+	one_clock_write(address & 0x0040);
+	one_clock_write(address & 0x0020);
+	one_clock_write(address & 0x0010);
+	one_clock_write(address & 0x0008);
+	one_clock_write(address & 0x0004);
+	one_clock_write(address & 0x0002);
+	one_clock_write(address & 0x0001);
+	one_clock_write_RWbit(0);
+	if (h2w_ack() < 0) {
+		H2W_DBG("Addr NO ACK(%d).\n", retry_times);
+		retry_times++;
+		hi->set_clk(0);
+		mdelay(RESEND_DELAY);
+		goto write_resend;
+	}
+
+	/* Write data */
+	hi->set_dat_dir(1);
+	one_clock_write(data & 0x0080);
+	one_clock_write(data & 0x0040);
+	one_clock_write(data & 0x0020);
+	one_clock_write(data & 0x0010);
+	one_clock_write(data & 0x0008);
+	one_clock_write(data & 0x0004);
+	one_clock_write(data & 0x0002);
+	one_clock_write_RWbit(data & 0x0001);
+	if (h2w_ack() < 0) {
+		H2W_DBG("Data NO ACK(%d).\n", retry_times);
+		retry_times++;
+		hi->set_clk(0);
+		mdelay(RESEND_DELAY);
+		goto write_resend;
+	}
+	ret = 0;
+
+err_write:
+	if (ret < 0)
+		H2WE("NO ACK.\n");
+
+	return ret;
+}
+
+static int h2w_get_fnkey(void)
+{
+	int ret;
+	h2w_begin_command();
+	ret = h2w_readc_cmd(H2W_FNKEY_UPDOWN);
+	h2w_end_command();
+	return ret;
+}
+
+static int h2w_dev_init(H2W_INFO *ph2w_info)
+{
+	int ret = -1;
+	unsigned char ascr0 = 0;
+	int h2w_sys = 0, maxgpadd = 0, maxadd = 0, key = 0;
+
+	hi->speed = H2W_50KHz;
+	h2w_begin_command();
+
+	/* read H2W_SYSTEM */
+	h2w_sys = h2w_readc_cmd(H2W_SYSTEM);
+	if (h2w_sys == -1) {
+		H2WE("read H2W_SYSTEM(0x0000) failed.\n");
+		goto err_plugin;
+	}
+	ph2w_info->ACC_CLASS = (h2w_sys & 0x03);
+	ph2w_info->AUDIO_DEVICE  = (h2w_sys & 0x04) > 0 ? 1 : 0;
+	ph2w_info->HW_REV = (h2w_sys & 0x18) >> 3;
+	ph2w_info->SLEEP_PR  = (h2w_sys & 0x20) >> 5;
+	ph2w_info->CLK_SP = (h2w_sys & 0xC0) >> 6;
+
+	/* enter init mode */
+	if (h2w_writec_cmd(H2W_ASCR0, H2W_ASCR_DEVICE_INI) < 0) {
+		H2WE("write H2W_ASCR0(0x0002) failed.\n");
+		goto err_plugin;
+	}
+	udelay(10);
+
+	/* read H2W_MAX_GP_ADD */
+	maxgpadd = h2w_readc_cmd(H2W_MAX_GP_ADD);
+	if (maxgpadd == -1) {
+		H2WE("write H2W_MAX_GP_ADD(0x0001) failed.\n");
+		goto err_plugin;
+	}
+	ph2w_info->CLK_SP += (maxgpadd & 0x60) >> 3;
+	ph2w_info->MAX_GP_ADD = (maxgpadd & 0x1F);
+
+	/* read key group */
+	if (ph2w_info->MAX_GP_ADD >= 1) {
+		ph2w_info->KEY_MAXADD = h2w_readc_cmd(H2W_KEY_MAXADD);
+		if (ph2w_info->KEY_MAXADD == -1)
+			goto err_plugin;
+		if (ph2w_info->KEY_MAXADD >= 1) {
+			key = h2w_readc_cmd(H2W_ASCII_DOWN);
+			if (key < 0)
+				goto err_plugin;
+			ph2w_info->ASCII_DOWN = (key == 0xFF) ? 1 : 0;
+		}
+		if (ph2w_info->KEY_MAXADD >= 2) {
+			key = h2w_readc_cmd(H2W_ASCII_UP);
+			if (key == -1)
+				goto err_plugin;
+			ph2w_info->ASCII_UP = (key == 0xFF) ? 1 : 0;
+		}
+		if (ph2w_info->KEY_MAXADD >= 3) {
+			key = h2w_readc_cmd(H2W_FNKEY_UPDOWN);
+			if (key == -1)
+				goto err_plugin;
+			ph2w_info->FNKEY_UPDOWN = (key == 0xFF) ? 1 : 0;
+		}
+		if (ph2w_info->KEY_MAXADD >= 4) {
+			key = h2w_readc_cmd(H2W_KD_STATUS);
+			if (key == -1)
+				goto err_plugin;
+			ph2w_info->KD_STATUS = (key == 0x01) ? 1 : 0;
+		}
+	}
+
+	/* read led group */
+	if (ph2w_info->MAX_GP_ADD >= 2) {
+		ph2w_info->LED_MAXADD = h2w_readc_cmd(H2W_LED_MAXADD);
+		if (ph2w_info->LED_MAXADD == -1)
+			goto err_plugin;
+		if (ph2w_info->LED_MAXADD >= 1) {
+			key = h2w_readc_cmd(H2W_LEDCT0);
+			if (key == -1)
+				goto err_plugin;
+			ph2w_info->LEDCT0 = (key == 0x02) ? 1 : 0;
+		}
+	}
+
+	/* read group 3, 4, 5 */
+	if (ph2w_info->MAX_GP_ADD >= 3) {
+		maxadd = h2w_readc_cmd(H2W_CRDL_MAXADD);
+		if (maxadd == -1)
+			goto err_plugin;
+	}
+	if (ph2w_info->MAX_GP_ADD >= 4) {
+		maxadd = h2w_readc_cmd(H2W_CARKIT_MAXADD);
+		if (maxadd == -1)
+			goto err_plugin;
+	}
+	if (ph2w_info->MAX_GP_ADD >= 5) {
+		maxadd = h2w_readc_cmd(H2W_USBHOST_MAXADD);
+		if (maxadd == -1)
+			goto err_plugin;
+	}
+
+	/* read medical group */
+	if (ph2w_info->MAX_GP_ADD >= 6) {
+		ph2w_info->MED_MAXADD = h2w_readc_cmd(H2W_MED_MAXADD);
+		if (ph2w_info->MED_MAXADD == -1)
+			goto err_plugin;
+		if (ph2w_info->MED_MAXADD >= 1) {
+			key = h2w_readc_cmd(H2W_MED_CONTROL);
+			if (key == -1)
+				goto err_plugin;
+		ph2w_info->DATA_EN = (key & 0x01);
+		ph2w_info->AP_EN = (key & 0x02) >> 1;
+		ph2w_info->AP_ID = (key & 0x1c) >> 2;
+		}
+		if (ph2w_info->MED_MAXADD >= 2) {
+			key = h2w_readc_cmd(H2W_MED_IN_DATA);
+			if (key == -1)
+				goto err_plugin;
+		}
+	}
+
+	if (ph2w_info->AUDIO_DEVICE)
+		ascr0 = H2W_ASCR_AUDIO_IN | H2W_ASCR_ACT_EN;
+	else
+		ascr0 = H2W_ASCR_ACT_EN;
+
+	if (h2w_writec_cmd(H2W_ASCR0, ascr0) < 0)
+		goto err_plugin;
+	udelay(10);
+
+	ret = 0;
+
+	/* adjust speed */
+	if (ph2w_info->MAX_GP_ADD == 2) {
+		/* Remote control */
+		hi->speed = H2W_250KHz;
+	} else if (ph2w_info->MAX_GP_ADD == 6) {
+		if (ph2w_info->MED_MAXADD >= 1) {
+			key = h2w_readc_cmd(H2W_MED_CONTROL);
+			if (key == -1)
+				goto err_plugin;
+			ph2w_info->DATA_EN   = (key & 0x01);
+			ph2w_info->AP_EN = (key & 0x02) >> 1;
+			ph2w_info->AP_ID = (key & 0x1c) >> 2;
+		}
+	}
+
+err_plugin:
+	h2w_end_command();
+
+	return ret;
+}
+
+static inline void h2w_dev_power_on(int on)
+{
+	if (!hi->vreg_h2w)
+		return;
+
+	if (on)
+		vreg_enable(hi->vreg_h2w);
+	else
+		vreg_disable(hi->vreg_h2w);
+}
+
+static int h2w_dev_detect(void)
+{
+	int ret = -1;
+	int retry_times;
+
+	for (retry_times = 5; retry_times; retry_times--) {
+		/* Enable H2W Power */
+		h2w_dev_power_on(1);
+		msleep(100);
+		memset(&hi->h2w_info, 0, sizeof(H2W_INFO));
+		if (h2w_dev_init(&hi->h2w_info) < 0) {
+			h2w_dev_power_on(0);
+			msleep(100);
+		} else if (hi->h2w_info.MAX_GP_ADD == 2) {
+			ret = 0;
+			break;
+		} else {
+			printk(KERN_INFO "h2w_detect: detect error(%d)\n"
+				, hi->h2w_info.MAX_GP_ADD);
+			h2w_dev_power_on(0);
+			msleep(100);
+		}
+		printk(KERN_INFO "h2w_detect(%d)\n"
+				, hi->h2w_info.MAX_GP_ADD);
+	}
+	H2W_DBG("h2w_detect:(%d)\n", retry_times);
+	return ret;
+}
+
+static void remove_headset(void)
+{
+	unsigned long irq_flags;
+
+	H2W_DBG("");
+
+	mutex_lock(&hi->mutex_lock);
+	switch_set_state(&hi->sdev, switch_get_state(&hi->sdev) &
+			~(BIT_HEADSET | BIT_HEADSET_NO_MIC));
+	mutex_unlock(&hi->mutex_lock);
+	hi->init_cpld();
+
+	/* Disable button */
+	switch (hi->htc_headset_flag) {
+	case H2W_HTC_HEADSET:
+		local_irq_save(irq_flags);
+		disable_irq(hi->irq_btn);
+		local_irq_restore(irq_flags);
+
+		if (atomic_read(&hi->btn_state))
+			button_released();
+		break;
+	case H2W_DEVICE:
+		h2w_dev_power_on(0);
+		set_irq_type(hi->irq_btn, IRQF_TRIGGER_LOW);
+		disable_irq(hi->irq_btn);
+		/* 10ms (5-15 with 10ms tick) */
+		hi->btn_debounce_time = ktime_set(0, 10000000);
+		hi->set_clk_dir(0);
+		hi->set_dat_dir(0);
+		break;
+	}
+
+	hi->htc_headset_flag = 0;
+	hi->debounce_time = ktime_set(0, 100000000);  /* 100 ms */
+
+}
+
+#ifdef CONFIG_MSM_SERIAL_DEBUGGER
+extern void msm_serial_debug_enable(int);
+#endif
+
+static void insert_headset(int type)
+{
+	unsigned long irq_flags;
+	int state;
+
+	H2W_DBG("");
+
+	hi->htc_headset_flag = type;
+	state = BIT_HEADSET | BIT_HEADSET_NO_MIC;
+
+	state = switch_get_state(&hi->sdev);
+	state &= ~(BIT_HEADSET_NO_MIC | BIT_HEADSET);
+	switch (type) {
+	case H2W_HTC_HEADSET:
+		printk(KERN_INFO "insert_headset H2W_HTC_HEADSET\n");
+		state |= BIT_HEADSET;
+		hi->ignore_btn = !gpio_get_value(hi->cable_in2);
+		/* Enable button irq */
+		local_irq_save(irq_flags);
+		enable_irq(hi->irq_btn);
+		local_irq_restore(irq_flags);
+		hi->debounce_time = ktime_set(0, 200000000); /* 20 ms */
+		break;
+	case H2W_DEVICE:
+		if (h2w_dev_detect() < 0) {
+			printk(KERN_INFO "H2W_DEVICE -- Non detect\n");
+			remove_headset();
+		} else {
+			printk(KERN_INFO "H2W_DEVICE -- detect\n");
+			hi->btn_debounce_time = ktime_set(0, 0);
+			local_irq_save(irq_flags);
+			enable_irq(hi->irq_btn);
+			set_irq_type(hi->irq_btn, IRQF_TRIGGER_RISING);
+			local_irq_restore(irq_flags);
+			state |= BIT_HEADSET;
+		}
+		break;
+	case H2W_USB_CRADLE:
+		state |= BIT_HEADSET_NO_MIC;
+		break;
+	case H2W_UART_DEBUG:
+		hi->config_cpld(hi->debug_uart);
+		printk(KERN_INFO "switch to H2W_UART_DEBUG\n");
+	default:
+		return;
+	}
+	mutex_lock(&hi->mutex_lock);
+	switch_set_state(&hi->sdev, state);
+	mutex_unlock(&hi->mutex_lock);
+
+#ifdef CONFIG_MSM_SERIAL_DEBUGGER
+	msm_serial_debug_enable(false);
+#endif
+
+}
+#if 0
+static void remove_headset(void)
+{
+	unsigned long irq_flags;
+
+	H2W_DBG("");
+
+	switch_set_state(&hi->sdev, H2W_NO_DEVICE);
+
+	hi->init_cpld();
+
+	/* Disable button */
+	local_irq_save(irq_flags);
+	disable_irq(hi->irq_btn);
+	local_irq_restore(irq_flags);
+
+	if (atomic_read(&hi->btn_state))
+		button_released();
+
+	hi->debounce_time = ktime_set(0, 100000000);  /* 100 ms */
+}
+#endif
+static int is_accessary_pluged_in(void)
+{
+	int type = 0;
+	int clk1 = 0, dat1 = 0, clk2 = 0, dat2 = 0, clk3 = 0, dat3 = 0;
+
+	/* Step1: save H2W_CLK and H2W_DAT */
+	/* Delay 10ms for pin stable. */
+	msleep(10);
+	clk1 = gpio_get_value(hi->h2w_clk);
+	dat1 = gpio_get_value(hi->h2w_data);
+
+	/*
+	 * Step2: set GPIO_CABLE_IN1 as output high and GPIO_CABLE_IN2 as
+	 * input
+	 */
+	gpio_direction_output(hi->cable_in1, 1);
+	gpio_direction_input(hi->cable_in2);
+	/* Delay 10ms for pin stable. */
+	msleep(10);
+	/* Step 3: save H2W_CLK and H2W_DAT */
+	clk2 = gpio_get_value(hi->h2w_clk);
+	dat2 = gpio_get_value(hi->h2w_data);
+
+	/*
+	 * Step 4: set GPIO_CABLE_IN1 as input and GPIO_CABLE_IN2 as output
+	 * high
+	 */
+	gpio_direction_input(hi->cable_in1);
+	gpio_direction_output(hi->cable_in2, 1);
+	/* Delay 10ms for pin stable. */
+	msleep(10);
+	/* Step 5: save H2W_CLK and H2W_DAT */
+	clk3 = gpio_get_value(hi->h2w_clk);
+	dat3 = gpio_get_value(hi->h2w_data);
+
+	/* Step 6: set both GPIO_CABLE_IN1 and GPIO_CABLE_IN2 as input */
+	gpio_direction_input(hi->cable_in1);
+	gpio_direction_input(hi->cable_in2);
+
+	H2W_DBG("(%d,%d) (%d,%d) (%d,%d)\n",
+		clk1, dat1, clk2, dat2, clk3, dat3);
+
+	if ((clk1 == 0) && (dat1 == 1) &&
+	    (clk2 == 0) && (dat2 == 1) &&
+	    (clk3 == 0) && (dat3 == 1))
+		type = H2W_HTC_HEADSET;
+	else if ((clk1 == 0) && (dat1 == 0) &&
+		 (clk2 == 0) && (dat2 == 0) &&
+		 (clk3 == 0) &&  (dat3 == 0))
+		type = NORMAL_HEARPHONE;
+	else if ((clk1 == 0) && (dat1 == 0) &&
+		 (clk2 == 1) && (dat2 == 0) &&
+		 (clk3 == 0) && (dat3 == 1))
+		type = H2W_DEVICE;
+	else if ((clk1 == 0) && (dat1 == 0) &&
+		 (clk2 == 1) && (dat2 == 1) &&
+		 (clk3 == 1) && (dat3 == 1))
+		type = H2W_USB_CRADLE;
+	else if ((clk1 == 0) && (dat1 == 1) &&
+		 (clk2 == 1) && (dat2 == 1) &&
+		 (clk3 == 0) && (dat3 == 1))
+		type = H2W_UART_DEBUG;
+	else
+		type = H2W_NO_DEVICE;
+
+	return type;
+}
+
+
+static void detection_work(struct work_struct *work)
+{
+	unsigned long irq_flags;
+	int type;
+
+	H2W_DBG("");
+
+	if (gpio_get_value(hi->cable_in1) != 0) {
+		/* Headset not plugged in */
+		if (switch_get_state(&hi->sdev) != H2W_NO_DEVICE)
+			remove_headset();
+		return;
+	}
+
+	/* Something plugged in, lets make sure its a headset */
+
+	/* Switch CPLD to GPIO to do detection */
+	hi->config_cpld(H2W_GPIO);
+
+	/* Disable headset interrupt while detecting.*/
+	local_irq_save(irq_flags);
+	disable_irq(hi->irq);
+	local_irq_restore(irq_flags);
+
+	/* Something plugged in, lets make sure its a headset */
+	type = is_accessary_pluged_in();
+
+	/* Restore IRQs */
+	local_irq_save(irq_flags);
+	enable_irq(hi->irq);
+	local_irq_restore(irq_flags);
+
+	insert_headset(type);
+}
+
+static enum hrtimer_restart button_event_timer_func(struct hrtimer *data)
+{
+	int key, press, keyname, h2w_key = 1;
+
+	H2W_DBG("");
+
+	if (switch_get_state(&hi->sdev) == H2W_HTC_HEADSET) {
+		switch (hi->htc_headset_flag) {
+		case H2W_HTC_HEADSET:
+			if (gpio_get_value(hi->cable_in2)) {
+				if (hi->ignore_btn)
+					hi->ignore_btn = 0;
+				else if (atomic_read(&hi->btn_state))
+					button_released();
+			} else {
+				if (!hi->ignore_btn &&
+				    !atomic_read(&hi->btn_state))
+					button_pressed();
+			}
+			break;
+		case H2W_DEVICE:
+			if ((hi->get_dat() == 1) && (hi->get_clk() == 1)) {
+				/* Don't do anything because H2W pull out. */
+				H2WE("Remote Control pull out.\n");
+			} else {
+				key = h2w_get_fnkey();
+				press = (key > 0x7F) ? 0 : 1;
+				keyname = key & 0x7F;
+				 /* H2WI("key = %d, press = %d,
+					 keyname = %d \n",
+					 key, press, keyname); */
+				switch (keyname) {
+				case H2W_KEY_PLAY:
+					H2WI("H2W_KEY_PLAY");
+					key = KEY_PLAYPAUSE;
+					break;
+				case H2W_KEY_FORWARD:
+					H2WI("H2W_KEY_FORWARD");
+					key = KEY_NEXTSONG;
+					break;
+				case H2W_KEY_BACKWARD:
+					H2WI("H2W_KEY_BACKWARD");
+					key = KEY_PREVIOUSSONG;
+					break;
+				case H2W_KEY_VOLUP:
+					H2WI("H2W_KEY_VOLUP");
+					key = KEY_VOLUMEUP;
+					break;
+				case H2W_KEY_VOLDOWN:
+					H2WI("H2W_KEY_VOLDOWN");
+					key = KEY_VOLUMEDOWN;
+					break;
+				case H2W_KEY_PICKUP:
+					H2WI("H2W_KEY_PICKUP");
+					key = KEY_SEND;
+					break;
+				case H2W_KEY_HANGUP:
+					H2WI("H2W_KEY_HANGUP");
+					key = KEY_END;
+					break;
+				case H2W_KEY_MUTE:
+					H2WI("H2W_KEY_MUTE");
+					key = KEY_MUTE;
+					break;
+				case H2W_KEY_HOLD:
+					H2WI("H2W_KEY_HOLD");
+					break;
+				default:
+				H2WI("default");
+					h2w_key = 0;
+				}
+				if (h2w_key) {
+					if (press)
+						H2WI("Press\n");
+					else
+						H2WI("Release\n");
+					input_report_key(hi->input, key, press);
+				}
+			}
+			break;
+		} /* end switch */
+	}
+
+	return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart detect_event_timer_func(struct hrtimer *data)
+{
+	H2W_DBG("");
+
+	queue_work(g_detection_work_queue, &g_detection_work);
+	return HRTIMER_NORESTART;
+}
+
+static irqreturn_t detect_irq_handler(int irq, void *dev_id)
+{
+	int value1, value2;
+	int retry_limit = 10;
+
+	H2W_DBG("");
+	set_irq_type(hi->irq_btn, IRQF_TRIGGER_LOW);
+	do {
+		value1 = gpio_get_value(hi->cable_in1);
+		set_irq_type(hi->irq, value1 ?
+				IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH);
+		value2 = gpio_get_value(hi->cable_in1);
+	} while (value1 != value2 && retry_limit-- > 0);
+
+	H2W_DBG("value2 = %d (%d retries), device=%d",
+		value2, (10-retry_limit), switch_get_state(&hi->sdev));
+
+	if ((switch_get_state(&hi->sdev) == H2W_NO_DEVICE) ^ value2) {
+		if (switch_get_state(&hi->sdev) == H2W_HTC_HEADSET)
+			hi->ignore_btn = 1;
+		/* Do the rest of the work in timer context */
+		hrtimer_start(&hi->timer, hi->debounce_time, HRTIMER_MODE_REL);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t button_irq_handler(int irq, void *dev_id)
+{
+	int value1, value2;
+	int retry_limit = 10;
+
+	H2W_DBG("");
+	do {
+		value1 = gpio_get_value(hi->cable_in2);
+		if (hi->htc_headset_flag != H2W_DEVICE)
+		set_irq_type(hi->irq_btn, value1 ?
+				IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH);
+		value2 = gpio_get_value(hi->cable_in2);
+	} while (value1 != value2 && retry_limit-- > 0);
+
+	H2W_DBG("value2 = %d (%d retries)", value2, (10-retry_limit));
+
+	hrtimer_start(&hi->btn_timer, hi->btn_debounce_time, HRTIMER_MODE_REL);
+
+	return IRQ_HANDLED;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+static int h2w_debug_set(void *data, u64 val)
+{
+	mutex_lock(&hi->mutex_lock);
+	switch_set_state(&hi->sdev, (int)val);
+	mutex_unlock(&hi->mutex_lock);
+	return 0;
+}
+
+static int h2w_debug_get(void *data, u64 *val)
+{
+	return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(h2w_debug_fops, h2w_debug_get, h2w_debug_set, "%llu\n");
+static int __init h2w_debug_init(void)
+{
+	struct dentry *dent;
+
+	dent = debugfs_create_dir("h2w", 0);
+	if (IS_ERR(dent))
+		return PTR_ERR(dent);
+
+	debugfs_create_file("state", 0644, dent, NULL, &h2w_debug_fops);
+
+	return 0;
+}
+
+device_initcall(h2w_debug_init);
+#endif
+
+static int h2w_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct h2w_platform_data *pdata = pdev->dev.platform_data;
+
+	printk(KERN_INFO "H2W: Registering H2W (headset) driver\n");
+	hi = kzalloc(sizeof(struct h2w_info), GFP_KERNEL);
+	if (!hi)
+		return -ENOMEM;
+
+	atomic_set(&hi->btn_state, 0);
+	hi->ignore_btn = 0;
+
+	hi->debounce_time = ktime_set(0, 100000000);  /* 100 ms */
+	hi->btn_debounce_time = ktime_set(0, 10000000); /* 10 ms */
+
+	hi->htc_headset_flag = 0;
+	hi->cable_in1 = pdata->cable_in1;
+	hi->cable_in2 = pdata->cable_in2;
+	hi->h2w_clk = pdata->h2w_clk;
+	hi->h2w_data = pdata->h2w_data;
+	hi->debug_uart = pdata->debug_uart;
+	hi->config_cpld = pdata->config_cpld;
+	hi->init_cpld = pdata->init_cpld;
+	hi->set_dat = pdata->set_dat;
+	hi->set_clk = pdata->set_clk;
+	hi->set_dat_dir	= pdata->set_dat_dir;
+	hi->set_clk_dir	= pdata->set_clk_dir;
+	hi->get_dat = pdata->get_dat;
+	hi->get_clk = pdata->get_clk;
+	hi->speed = H2W_50KHz;
+	/* obtain needed VREGs */
+	if (pdata->power_name)
+		hi->vreg_h2w = vreg_get(0, pdata->power_name);
+
+	mutex_init(&hi->mutex_lock);
+
+	hi->sdev.name = "h2w";
+	hi->sdev.print_name = h2w_print_name;
+
+	ret = switch_dev_register(&hi->sdev);
+	if (ret < 0)
+		goto err_switch_dev_register;
+
+	g_detection_work_queue = create_workqueue("detection");
+	if (g_detection_work_queue == NULL) {
+		ret = -ENOMEM;
+		goto err_create_work_queue;
+	}
+
+	ret = gpio_request(hi->cable_in1, "h2w_detect");
+	if (ret < 0)
+		goto err_request_detect_gpio;
+
+	ret = gpio_request(hi->cable_in2, "h2w_button");
+	if (ret < 0)
+		goto err_request_button_gpio;
+
+	ret = gpio_direction_input(hi->cable_in1);
+	if (ret < 0)
+		goto err_set_detect_gpio;
+
+	ret = gpio_direction_input(hi->cable_in2);
+	if (ret < 0)
+		goto err_set_button_gpio;
+
+	hi->irq = gpio_to_irq(hi->cable_in1);
+	if (hi->irq < 0) {
+		ret = hi->irq;
+		goto err_get_h2w_detect_irq_num_failed;
+	}
+
+	hi->irq_btn = gpio_to_irq(hi->cable_in2);
+	if (hi->irq_btn < 0) {
+		ret = hi->irq_btn;
+		goto err_get_button_irq_num_failed;
+	}
+
+	/* Set CPLD MUX to H2W <-> CPLD GPIO */
+	hi->init_cpld();
+
+	hrtimer_init(&hi->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	hi->timer.function = detect_event_timer_func;
+	hrtimer_init(&hi->btn_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	hi->btn_timer.function = button_event_timer_func;
+
+	ret = request_irq(hi->irq, detect_irq_handler,
+			  IRQF_TRIGGER_LOW, "h2w_detect", NULL);
+	if (ret < 0)
+		goto err_request_detect_irq;
+
+	/* Disable button until plugged in */
+	set_irq_flags(hi->irq_btn, IRQF_VALID | IRQF_NOAUTOEN);
+	ret = request_irq(hi->irq_btn, button_irq_handler,
+			  IRQF_TRIGGER_LOW, "h2w_button", NULL);
+	if (ret < 0)
+		goto err_request_h2w_headset_button_irq;
+
+	ret = set_irq_wake(hi->irq, 1);
+	if (ret < 0)
+		goto err_request_input_dev;
+
+	ret = set_irq_wake(hi->irq_btn, 1);
+	if (ret < 0)
+		goto err_request_input_dev;
+
+
+
+	hi->input = input_allocate_device();
+	if (!hi->input) {
+		ret = -ENOMEM;
+		goto err_request_input_dev;
+	}
+
+	hi->input->name = "h2w headset";
+	set_bit(EV_SYN, hi->input->evbit);
+	set_bit(EV_KEY, hi->input->evbit);
+	set_bit(KEY_MEDIA, hi->input->keybit);
+	set_bit(KEY_NEXTSONG, hi->input->keybit);
+	set_bit(KEY_PLAYPAUSE, hi->input->keybit);
+	set_bit(KEY_PREVIOUSSONG, hi->input->keybit);
+	set_bit(KEY_MUTE, hi->input->keybit);
+	set_bit(KEY_VOLUMEUP, hi->input->keybit);
+	set_bit(KEY_VOLUMEDOWN, hi->input->keybit);
+	set_bit(KEY_END, hi->input->keybit);
+	set_bit(KEY_SEND, hi->input->keybit);
+
+	ret = input_register_device(hi->input);
+	if (ret < 0)
+		goto err_register_input_dev;
+
+	return 0;
+
+err_register_input_dev:
+	input_free_device(hi->input);
+err_request_input_dev:
+	free_irq(hi->irq_btn, 0);
+err_request_h2w_headset_button_irq:
+	free_irq(hi->irq, 0);
+err_request_detect_irq:
+err_get_button_irq_num_failed:
+err_get_h2w_detect_irq_num_failed:
+err_set_button_gpio:
+err_set_detect_gpio:
+	gpio_free(hi->cable_in2);
+err_request_button_gpio:
+	gpio_free(hi->cable_in1);
+err_request_detect_gpio:
+	destroy_workqueue(g_detection_work_queue);
+err_create_work_queue:
+	switch_dev_unregister(&hi->sdev);
+err_switch_dev_register:
+	printk(KERN_ERR "H2W: Failed to register driver\n");
+
+	return ret;
+}
+
+static int h2w_remove(struct platform_device *pdev)
+{
+	H2W_DBG("");
+	if (switch_get_state(&hi->sdev))
+		remove_headset();
+	input_unregister_device(hi->input);
+	gpio_free(hi->cable_in2);
+	gpio_free(hi->cable_in1);
+	free_irq(hi->irq_btn, 0);
+	free_irq(hi->irq, 0);
+	destroy_workqueue(g_detection_work_queue);
+	switch_dev_unregister(&hi->sdev);
+
+	return 0;
+}
+
+
+static struct platform_driver h2w_driver = {
+	.probe		= h2w_probe,
+	.remove		= h2w_remove,
+	.driver		= {
+		.name		= "h2w",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init h2w_init(void)
+{
+	H2W_DBG("");
+	return platform_driver_register(&h2w_driver);
+}
+
+static void __exit h2w_exit(void)
+{
+	platform_driver_unregister(&h2w_driver);
+}
+
+module_init(h2w_init);
+module_exit(h2w_exit);
+
+MODULE_AUTHOR("Laurence Chen <Laurence_Chen@htc.com>");
+MODULE_DESCRIPTION("HTC 2 Wire detection driver");
+MODULE_LICENSE("GPL");