misc: rt5501: Add HTC RT5501 Amp driver

* HTC kernel version: m7-kk-3.4.10-17db3b4

Change-Id: I0b067aebecd6c36aa3bdeb32fad2b30aadc11f49
diff --git a/drivers/misc/rt5501.c b/drivers/misc/rt5501.c
new file mode 100644
index 0000000..5cefcf4
--- /dev/null
+++ b/drivers/misc/rt5501.c
@@ -0,0 +1,1202 @@
+/* driver/i2c/chip/tap6185.c
+ *
+ * TI rt5501 Speaker Amp
+ *
+ * Copyright (C) 2010 HTC Corporation
+ *
+ * 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.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/miscdevice.h>
+#include <asm/uaccess.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/freezer.h>
+#include <linux/rt5501.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/mfd/pm8xxx/pm8921.h>
+#include <linux/mfd/pm8xxx/pm8921.h>
+#include <mach/htc_headset_mgr.h>
+#include <linux/wakelock.h>
+#include <mach/htc_acoustic_pmic.h>
+#include <linux/jiffies.h>
+
+#ifdef CONFIG_AMP_RT5501_ON_GPIO
+#define DEBUG (1)
+#else
+#define DEBUG (1)
+#endif
+#define AMP_ON_CMD_LEN 7
+#define RETRY_CNT 5
+
+#define DRIVER_NAME "RT5501"
+
+struct headset_query {
+	struct mutex mlock;
+	struct mutex gpiolock;
+	struct delayed_work hs_imp_detec_work;
+	struct wake_lock hs_wake_lock;
+	struct wake_lock gpio_wake_lock;
+	enum HEADSET_QUERY_STATUS hs_qstatus;
+	enum RT5501_STATUS rt5501_status;
+	enum HEADSET_OM headsetom;
+	enum RT5501_Mode curmode;
+	enum AMP_GPIO_STATUS gpiostatus;
+	enum AMP_S4_STATUS s4status;
+	int action_on;
+	int gpio_off_cancel;
+	struct mutex actionlock;
+	struct delayed_work volume_ramp_work;
+	struct delayed_work gpio_off_work;
+};
+
+static struct i2c_client *this_client;
+static struct rt5501_platform_data *pdata;
+static int rt5501Connect = 0;
+static int MFG_MODE = 0;
+
+struct rt5501_config_data rt5501_config_data;
+static struct mutex hp_amp_lock;
+static int rt5501_opened;
+static int last_spkamp_state;
+struct rt5501_config RT5501_AMP_ON = {6,{{0x1,0x1c},{0x2,0x00},{0x7,0x7f},{0x9,0x1},{0xa,0x0},{0xb,0xc7},}};
+struct rt5501_config RT5501_AMP_INIT = {11,{{0,0xc0},{0x81,0x30},{0x87,0xf6},{0x93,0x8d},{0x95,0x7d},{0xa4,0x52},\
+                                        {0x96,0xae},{0x97,0x13},{0x99,0x35},{0x9b,0x68},{0x9d,0x68},}};
+
+struct rt5501_config RT5501_AMP_MUTE = {1,{{0x1,0xC7},}};;
+struct rt5501_config RT5501_AMP_OFF = {1,{{0x0,0x1},}};
+
+static int rt5501_write_reg(u8 reg, u8 val);
+static int rt5501_i2c_write_for_read(char *txData, int length);
+static int rt5501_i2c_read(char *rxData, int length);
+static void hs_imp_detec_func(struct work_struct *work);
+static int rt5501_i2c_read_addr(char *rxData, unsigned char addr);
+static int rt5501_i2c_write(struct rt5501_reg_data *txData, int length);
+static void set_amp(int on, struct rt5501_config *i2c_command);
+
+struct headset_query rt5501_query;
+static struct workqueue_struct *hs_wq;
+
+static struct workqueue_struct *ramp_wq;
+static struct workqueue_struct *gpio_wq;
+static int high_imp = 0;
+static u64 last_hp_remove = 0;
+#if 0
+static int query_playback(void *pdata)
+{
+    return 0;
+}
+#endif
+
+static int rt5501_headset_detect(int on)
+{
+
+	if(on) {
+		pr_info("%s: headset in ++\n",__func__);
+		mutex_lock(&rt5501_query.mlock);
+
+		if(rt5501_query.headsetom == HEADSET_OM_UNDER_DETECT || \
+			time_after64(get_jiffies_64(),last_hp_remove + msecs_to_jiffies(500))) {
+			rt5501_query.hs_qstatus = RT5501_QUERY_HEADSET;
+			rt5501_query.headsetom = HEADSET_OM_UNDER_DETECT;
+		} else {
+			rt5501_query.hs_qstatus = RT5501_QUERY_FINISH;
+		}
+
+		mutex_unlock(&rt5501_query.mlock);
+
+		cancel_delayed_work_sync(&rt5501_query.hs_imp_detec_work);
+
+		mutex_lock(&rt5501_query.gpiolock);
+		mutex_lock(&rt5501_query.mlock);
+
+		if(rt5501_query.rt5501_status == RT5501_PLAYBACK) {
+
+			if(high_imp) {
+				rt5501_write_reg(1,0x7);
+				rt5501_write_reg(0xb1,0x81);
+			} else {
+				rt5501_write_reg(1,0xc7);
+
+			}
+			last_spkamp_state = 0;
+			pr_info("%s: OFF\n", __func__);
+			rt5501_query.rt5501_status = RT5501_SUSPEND;
+		}
+		pr_info("%s: headset in --\n",__func__);
+		mutex_unlock(&rt5501_query.mlock);
+		mutex_unlock(&rt5501_query.gpiolock);
+
+		queue_delayed_work(hs_wq,&rt5501_query.hs_imp_detec_work,msecs_to_jiffies(5));
+		pr_info("%s: headset in --2\n",__func__);
+
+	} else {
+
+		pr_info("%s: headset remove ++\n",__func__);
+		flush_work_sync(&rt5501_query.volume_ramp_work.work);
+		mutex_lock(&rt5501_query.mlock);
+		rt5501_query.hs_qstatus = RT5501_QUERY_OFF;
+		mutex_unlock(&rt5501_query.mlock);
+
+		cancel_delayed_work_sync(&rt5501_query.hs_imp_detec_work);
+
+		mutex_lock(&rt5501_query.gpiolock);
+		mutex_lock(&rt5501_query.mlock);
+
+
+		if(rt5501_query.rt5501_status == RT5501_PLAYBACK) {
+
+			if(high_imp) {
+				rt5501_write_reg(1,0x7);
+				rt5501_write_reg(0xb1,0x81);
+			} else {
+				rt5501_write_reg(1,0xc7);
+
+			}
+
+			last_spkamp_state = 0;
+			pr_info("%s: OFF\n", __func__);
+
+			rt5501_query.rt5501_status = RT5501_SUSPEND;
+		}
+
+		rt5501_query.curmode = RT5501_MODE_OFF;
+		pr_info("%s: headset remove --1\n",__func__);
+
+		if(high_imp) {
+			int closegpio = 0;
+			if((rt5501_query.gpiostatus == AMP_GPIO_OFF) && pdata->gpio_rt5501_spk_en) {
+
+				if(rt5501_query.s4status == AMP_S4_AUTO) {
+					pm8921_aud_set_s4_pwm();
+					rt5501_query.s4status = AMP_S4_PWM;
+					msleep(1);
+				}
+
+				pr_info("%s: enable gpio %d\n",__func__,pdata->gpio_rt5501_spk_en);
+				gpio_direction_output(pdata->gpio_rt5501_spk_en, 1);
+				rt5501_query.gpiostatus = AMP_GPIO_ON;
+				closegpio = 1;
+				msleep(1);
+			}
+			pr_info("%s: reset rt5501\n",__func__);
+			rt5501_write_reg(0x0,0x4);
+			mdelay(1);
+
+			rt5501_write_reg(0x1,0xc7);
+			high_imp = 0;
+
+			if(closegpio && (rt5501_query.gpiostatus == AMP_GPIO_ON) && pdata->gpio_rt5501_spk_en) {
+				pr_info("%s: disable gpio %d\n",__func__,pdata->gpio_rt5501_spk_en);
+				gpio_direction_output(pdata->gpio_rt5501_spk_en, 0);
+				rt5501_query.gpiostatus = AMP_GPIO_OFF;
+
+				if(rt5501_query.s4status == AMP_S4_PWM) {
+					pm8921_aud_set_s4_auto();
+					rt5501_query.s4status = AMP_S4_AUTO;
+				}
+			}
+		}
+		last_hp_remove = get_jiffies_64();
+		mutex_unlock(&rt5501_query.mlock);
+		mutex_unlock(&rt5501_query.gpiolock);
+
+		pr_info("%s: headset remove --2\n",__func__);
+	}
+
+	return 0;
+}
+
+static int rt5501_write_reg(u8 reg, u8 val)
+{
+	int err;
+	struct i2c_msg msg[1];
+	unsigned char data[2];
+
+	msg->addr = this_client->addr;
+	msg->flags = 0;
+	msg->len = 2;
+	msg->buf = data;
+	data[0] = reg;
+	data[1] = val;
+        pr_info("%s: write reg 0x%x val 0x%x\n",__func__,data[0],data[1]); 
+	err = i2c_transfer(this_client->adapter, msg, 1);
+	if (err >= 0)
+		return 0;
+        else {
+
+            pr_info("%s: write error error %d\n",__func__,err);
+            return err;
+        }
+}
+
+static int rt5501_i2c_write(struct rt5501_reg_data *txData, int length)
+{
+	int i, retry, pass = 0;
+	char buf[2];
+	struct i2c_msg msg[] = {
+		{
+		 .addr = this_client->addr,
+		 .flags = 0,
+		 .len = 2,
+		 .buf = buf,
+		},
+	};
+	for (i = 0; i < length; i++) {
+		
+		
+		buf[0] = txData[i].addr;
+		buf[1] = txData[i].val;
+
+#if DEBUG
+		pr_info("%s:i2c_write addr 0x%x val 0x%x\n", __func__,buf[0], buf[1]);
+#endif
+		msg->buf = buf;
+		retry = RETRY_CNT;
+		pass = 0;
+		while (retry--) {
+			if (i2c_transfer(this_client->adapter, msg, 1) < 0) {
+				pr_err("%s: I2C transfer error %d retry %d\n",
+						__func__, i, retry);
+				msleep(20);
+			} else {
+				pass = 1;
+				break;
+			}
+		}
+		if (pass == 0) {
+			pr_err("I2C transfer error, retry fail\n");
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+static int rt5501_i2c_write_for_read(char *txData, int length)
+{
+	int i, retry, pass = 0;
+	char buf[2];
+	struct i2c_msg msg[] = {
+		{
+		 .addr = this_client->addr,
+		 .flags = 0,
+		 .len = 2,
+		 .buf = buf,
+		},
+	};
+	for (i = 0; i < length; i++) {
+		
+		
+		buf[0] = i;
+		buf[1] = txData[i];
+#if DEBUG
+		pr_info("i2c_write %d=%x\n", i, buf[1]);
+#endif
+		msg->buf = buf;
+		retry = RETRY_CNT;
+		pass = 0;
+		while (retry--) {
+			if (i2c_transfer(this_client->adapter, msg, 1) < 0) {
+				pr_err("%s: I2C transfer error %d retry %d\n",
+						__func__, i, retry);
+				msleep(20);
+			} else {
+				pass = 1;
+				break;
+			}
+		}
+		if (pass == 0) {
+			pr_err("I2C transfer error, retry fail\n");
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+static int rt5501_i2c_read(char *rxData, int length)
+{
+	int rc;
+	struct i2c_msg msgs[] = {
+		{
+		 .addr = this_client->addr,
+		 .flags = I2C_M_RD,
+		 .len = length,
+		 .buf = rxData,
+		},
+	};
+
+	rc = i2c_transfer(this_client->adapter, msgs, 1);
+	if (rc < 0) {
+		pr_err("%s: transfer error %d\n", __func__, rc);
+		return rc;
+	}
+
+	{
+		int i = 0;
+		for (i = 0; i < length; i++)
+			pr_info("i2c_read %s: rx[%d] = 0x%x\n", __func__, i, \
+				rxData[i]);
+	}
+
+	return 0;
+}
+
+static int rt5501_i2c_read_addr(char *rxData, unsigned char addr)
+{
+	int rc;
+	struct i2c_msg msgs[] = {
+		{
+		 .addr = this_client->addr,
+		 .flags = 0,
+		 .len = 1,
+		 .buf = rxData,
+		},
+		{
+		 .addr = this_client->addr,
+		 .flags = I2C_M_RD,
+		 .len = 1,
+		 .buf = rxData,
+		},
+	};
+
+        if(!rxData)
+            return -1;
+
+        *rxData = addr;
+
+	rc = i2c_transfer(this_client->adapter, &msgs[0], 1);
+	if (rc < 0) {
+		pr_err("%s: transfer error %d\n", __func__, rc);
+		return rc;
+	}
+
+	rc = i2c_transfer(this_client->adapter, &msgs[1], 1);
+	if (rc < 0) {
+		pr_err("%s: transfer error %d\n", __func__, rc);
+		return rc;
+	}
+
+        pr_info("%s:i2c_read addr 0x%x value = 0x%x\n", __func__, addr, *rxData);
+	return 0;
+}
+
+static int rt5501_open(struct inode *inode, struct file *file)
+{
+	int rc = 0;
+
+	mutex_lock(&hp_amp_lock);
+
+	if (rt5501_opened) {
+		pr_err("%s: busy\n", __func__);
+		rc = -EBUSY;
+		goto done;
+	}
+	rt5501_opened = 1;
+done:
+	mutex_unlock(&hp_amp_lock);
+	return rc;
+}
+
+static int rt5501_release(struct inode *inode, struct file *file)
+{
+	mutex_lock(&hp_amp_lock);
+	rt5501_opened = 0;
+	mutex_unlock(&hp_amp_lock);
+
+	return 0;
+}
+#if 0
+static int init_rt5501(void)
+{
+    int ret;
+
+    ret = rt5501_i2c_write(RT5501_AMP_INIT.reg, RT5501_AMP_INIT.reg_len);
+
+    if(ret < 0) {
+        pr_err("init rt5501 error %d\n",ret);
+        return ret;
+    }
+#if 0
+    ret = rt5501_i2c_write(RT5501_AMP_ON.reg, RT5501_AMP_ON.reg_len);
+
+    if(ret < 0) {
+        pr_err("init rt5501 to playback error %d\n",ret);
+        return ret;
+    }
+
+    ret = rt5501_i2c_write(RT5501_AMP_MUTE.reg, RT5501_AMP_MUTE.reg_len);
+
+    if(ret < 0) {
+        pr_err("init rt5501 to mute error %d\n",ret);
+        return ret;
+    }
+
+    ret = rt5501_i2c_write(RT5501_AMP_OFF.reg, RT5501_AMP_OFF.reg_len);
+
+    if(ret < 0) {
+        pr_err("init rt5501 to off error %d\n",ret);
+        return ret;
+    }
+#endif
+    return ret;
+}
+#endif
+static void hs_imp_gpio_off(struct work_struct *work)
+{
+    u64 timeout = get_jiffies_64() + 5*HZ;
+    wake_lock(&rt5501_query.gpio_wake_lock);
+
+    while(1) {
+        if(time_after64(get_jiffies_64(),timeout))
+            break;
+        else if(rt5501_query.gpio_off_cancel) {
+            wake_unlock(&rt5501_query.gpio_wake_lock);
+            return;
+        } else
+            msleep(10);
+    }
+
+    mutex_lock(&rt5501_query.gpiolock);
+    pr_info("%s: disable gpio %d\n",__func__,pdata->gpio_rt5501_spk_en);
+    gpio_direction_output(pdata->gpio_rt5501_spk_en, 0);
+    rt5501_query.gpiostatus = AMP_GPIO_OFF;
+
+    if(rt5501_query.s4status == AMP_S4_PWM) {
+        pm8921_aud_set_s4_auto();
+        rt5501_query.s4status = AMP_S4_AUTO;
+    }
+
+    mutex_unlock(&rt5501_query.gpiolock);
+    wake_unlock(&rt5501_query.gpio_wake_lock);
+}
+
+static void hs_imp_detec_func(struct work_struct *work)
+{
+	struct headset_query *hs;
+	char temp[8]={0x1,};
+	int ret;
+	int rt5501_status;
+	pr_info("%s: read rt5501 hs imp \n",__func__);
+
+	hs = container_of(work, struct headset_query, hs_imp_detec_work.work);
+	wake_lock(&hs->hs_wake_lock);
+
+	rt5501_query.gpio_off_cancel = 1;
+	cancel_delayed_work_sync(&rt5501_query.gpio_off_work);
+	mutex_lock(&hs->gpiolock);
+	mutex_lock(&hs->mlock);
+
+	if((hs->gpiostatus == AMP_GPIO_OFF) && pdata->gpio_rt5501_spk_en) {
+
+		if(rt5501_query.s4status == AMP_S4_AUTO) {
+			pm8921_aud_set_s4_pwm();
+			rt5501_query.s4status = AMP_S4_PWM;
+			msleep(1);
+		}
+		pr_info("%s: enable gpio %d\n",__func__,pdata->gpio_rt5501_spk_en);
+		gpio_direction_output(pdata->gpio_rt5501_spk_en, 1);
+		rt5501_query.gpiostatus = AMP_GPIO_ON;
+	}
+
+	msleep(1);
+
+	if(hs->hs_qstatus == RT5501_QUERY_HEADSET) {
+		rt5501_write_reg(0,0x04);
+		rt5501_write_reg(0xa4,0x52);
+
+		rt5501_write_reg(1,0x7);
+		msleep(10);
+		rt5501_write_reg(0x3,0x81);
+
+		msleep(101);
+
+		ret = rt5501_i2c_read_addr(temp,0x4);
+
+		if(ret < 0) {
+			pr_err("%s: read rt5501 status error %d\n",__func__,ret);
+
+			if((hs->gpiostatus == AMP_GPIO_ON) && pdata->gpio_rt5501_spk_en) {
+
+				rt5501_query.gpio_off_cancel = 0;
+				queue_delayed_work(gpio_wq, &rt5501_query.gpio_off_work, msecs_to_jiffies(0));
+			}
+
+			mutex_unlock(&hs->mlock);
+			mutex_unlock(&hs->gpiolock);
+			wake_unlock(&hs->hs_wake_lock);
+			return;
+		}
+
+		temp[1] = 0x4;
+		rt5501_i2c_read_addr(&temp[1],0x6);
+
+		if(temp[0] & RT5501_SENSE_READY) {
+
+			unsigned char om, hsmode;
+			enum HEADSET_OM hsom;
+
+			high_imp = 0;
+			hsmode = (temp[0] & 0x30) >> 4;
+			om = (temp[0] & 0xe) >> 1;
+
+			if((temp[0] == 0xc0 || temp[0] == 0xc1) && (temp[1] == 0)) {
+				
+				hsom = HEADSET_MONO;
+			} else {
+
+				switch(om) {
+					case 0:
+						hsom = HEADSET_8OM;
+						break;
+					case 1:
+						hsom = HEADSET_16OM;
+						break;
+					case 2:
+						hsom = HEADSET_32OM;
+						break;
+					case 3:
+						hsom = HEADSET_64OM;
+						break;
+					case 4:
+						hsom = HEADSET_128OM;
+						break;
+					case 5:
+						hsom = HEADSET_256OM;
+						break;
+					case 6:
+						hsom = HEADSET_500OM;
+						break;
+					case 7:
+						hsom = HEADSET_1KOM;
+						break;
+
+					default:
+						hsom = HEADSET_OM_UNDER_DETECT;
+						break;
+				}
+			}
+
+			hs->hs_qstatus = RT5501_QUERY_FINISH;
+			hs->headsetom = hsom;
+
+			if(om >= HEADSET_256OM && om <= HEADSET_1KOM)
+				high_imp = 1;
+
+			pr_info("rt5501 hs imp value 0x%x hsmode %d om 0x%x hsom %d high_imp %d\n", \
+				temp[0] & 0xf,hsmode,om,hsom,high_imp);
+
+		} else {
+
+			if(hs->hs_qstatus == RT5501_QUERY_HEADSET)
+				queue_delayed_work(hs_wq,&rt5501_query.hs_imp_detec_work,QUERY_LATTER);
+		}
+
+	}
+
+	rt5501_write_reg(0x0,0x4);
+	mdelay(1);
+
+	rt5501_write_reg(0x0,0xc0);
+	rt5501_write_reg(0x81,0x30);
+	
+	rt5501_write_reg(0x90,0xd0);
+	rt5501_write_reg(0x93,0x9d);
+	rt5501_write_reg(0x95,0x7b);
+	rt5501_write_reg(0xa4,0x01);
+	
+	rt5501_write_reg(0x97,0x11);
+	rt5501_write_reg(0x98,0x22);
+	rt5501_write_reg(0x99,0x44);
+	rt5501_write_reg(0x9a,0x55);
+	rt5501_write_reg(0x9b,0x66);
+	rt5501_write_reg(0x9c,0x99);
+	rt5501_write_reg(0x9d,0x66);
+	rt5501_write_reg(0x9e,0x99);
+
+	rt5501_status = hs->rt5501_status;
+
+	if(high_imp) {
+		rt5501_write_reg(0xb1,0x81);
+		rt5501_write_reg(0x80,0x87);
+		rt5501_write_reg(0x83,0xc3);
+		rt5501_write_reg(0x84,0x63);
+		rt5501_write_reg(0x89,0x7);
+		mdelay(9);
+		rt5501_write_reg(0x83,0xcf);
+		rt5501_write_reg(0x89,0x1d);
+		mdelay(1);
+
+		rt5501_write_reg(1,0x7);
+		rt5501_write_reg(0xb1,0x81);
+	} else {
+
+		rt5501_write_reg(1,0xc7);
+	}
+
+
+	if((hs->gpiostatus == AMP_GPIO_ON) && pdata->gpio_rt5501_spk_en) {
+		rt5501_query.gpio_off_cancel = 0;
+		queue_delayed_work(gpio_wq, &rt5501_query.gpio_off_work, msecs_to_jiffies(0));
+	}
+
+	mutex_unlock(&hs->mlock);
+	mutex_unlock(&hs->gpiolock);
+
+	if(rt5501_status == RT5501_SUSPEND)
+		set_rt5501_amp(1);
+
+	wake_unlock(&hs->hs_wake_lock);
+}
+
+static void volume_ramp_func(struct work_struct *work)
+{
+	mutex_lock(&rt5501_query.actionlock);
+	if(rt5501_query.rt5501_status != RT5501_PLAYBACK) {
+		u8 val;
+		pr_info("%s: ramping-------------------------\n",__func__);
+		mdelay(1);
+		
+		if(high_imp)
+			rt5501_write_reg(0xb1,0x80);
+
+		rt5501_write_reg(0x2,0x0);
+		mdelay(1);
+		val = 0x7;
+
+		if (MFG_MODE) {
+			pr_info("Skip volume ramp for MFG build");
+			val += 15;
+			rt5501_write_reg(1,val);
+		} else {
+#if 1
+			int i;
+			for(i=0; i<15; i++) {
+				if(!rt5501_query.action_on) {
+					mutex_unlock(&rt5501_query.actionlock);
+					return;
+				}
+				msleep(1);
+				rt5501_write_reg(1,val);
+				val++;
+			}
+#else
+			for(i=0; i<8; i++) {
+				msleep(10);
+				rt5501_write_reg(1,val);
+				val += 2;
+			}
+
+#endif
+		}
+	}
+
+	set_amp(1, &RT5501_AMP_ON);
+	mutex_unlock(&rt5501_query.actionlock);
+}
+
+static void set_amp(int on, struct rt5501_config *i2c_command)
+{
+        pr_info("%s: %d\n", __func__, on);
+        mutex_lock(&rt5501_query.mlock);
+	mutex_lock(&hp_amp_lock);
+
+        if(rt5501_query.hs_qstatus == RT5501_QUERY_HEADSET)
+            rt5501_query.hs_qstatus = RT5501_QUERY_FINISH;
+
+	if (on) {
+            rt5501_query.rt5501_status = RT5501_PLAYBACK;
+            if (rt5501_i2c_write(i2c_command->reg, i2c_command->reg_len) == 0) {
+                last_spkamp_state = 1;
+                pr_info("%s: ON \n",
+                __func__);
+            }
+
+
+	} else {
+
+             if(high_imp) {
+                rt5501_write_reg(1,0x7);
+                rt5501_write_reg(0xb1,0x81);
+             } else {
+                 rt5501_write_reg(1,0xc7);
+             }
+
+           if(rt5501_query.rt5501_status == RT5501_PLAYBACK) {
+               last_spkamp_state = 0;
+               pr_info("%s: OFF\n", __func__);
+           }
+           rt5501_query.rt5501_status = RT5501_OFF;
+           rt5501_query.curmode = RT5501_MODE_OFF;
+	}
+	mutex_unlock(&hp_amp_lock);
+        mutex_unlock(&rt5501_query.mlock);
+}
+
+int query_rt5501(void)
+{
+    return rt5501Connect;
+}
+
+void set_rt5501_amp(int on)
+{
+    pr_info("%s: %d\n", __func__, on);
+    rt5501_query.gpio_off_cancel = 1;
+    if(!on)
+        rt5501_query.action_on = 0;
+    cancel_delayed_work_sync(&rt5501_query.gpio_off_work);
+    cancel_delayed_work_sync(&rt5501_query.volume_ramp_work);
+    flush_work_sync(&rt5501_query.volume_ramp_work.work);
+    mutex_lock(&rt5501_query.gpiolock);
+
+    if(on) {
+
+        if((rt5501_query.gpiostatus == AMP_GPIO_OFF) && pdata->gpio_rt5501_spk_en) {
+
+            if(rt5501_query.s4status == AMP_S4_AUTO) {
+                pm8921_aud_set_s4_pwm();
+                rt5501_query.s4status = AMP_S4_PWM;
+                msleep(1);
+            }
+
+#ifdef CONFIG_AMP_RT5501_DELAY
+			msleep(50);
+#endif
+            pr_info("%s: enable gpio %d\n",__func__,pdata->gpio_rt5501_spk_en);
+            gpio_direction_output(pdata->gpio_rt5501_spk_en, 1);
+            rt5501_query.gpiostatus = AMP_GPIO_ON;
+            msleep(1);
+        }
+                rt5501_query.action_on = 1;
+		queue_delayed_work(ramp_wq, &rt5501_query.volume_ramp_work, msecs_to_jiffies(0));
+
+    } else {
+        set_amp(0, &RT5501_AMP_ON);
+        if((rt5501_query.gpiostatus == AMP_GPIO_ON) && pdata->gpio_rt5501_spk_en) {
+
+            rt5501_query.gpio_off_cancel = 0;
+	    queue_delayed_work(gpio_wq, &rt5501_query.gpio_off_work, msecs_to_jiffies(0));
+        }
+
+    }
+
+    mutex_unlock(&rt5501_query.gpiolock);
+}
+
+static int update_amp_parameter(int mode)
+{
+	if (mode >= rt5501_config_data.mode_num)
+		return -EINVAL;
+
+        pr_info("%s: set mode %d\n", __func__, mode);
+
+	if (mode == RT5501_MODE_OFF)
+		memcpy(&RT5501_AMP_OFF, &rt5501_config_data.cmd_data[mode].config,
+				sizeof(struct rt5501_config));
+        else if (mode == RT5501_INIT)
+		memcpy(&RT5501_AMP_INIT, &rt5501_config_data.cmd_data[mode].config,
+				sizeof(struct rt5501_config));
+        else if (mode == RT5501_MUTE)
+		memcpy(&RT5501_AMP_MUTE, &rt5501_config_data.cmd_data[mode].config,
+				sizeof(struct rt5501_config));
+	else {
+		memcpy(&RT5501_AMP_ON, &rt5501_config_data.cmd_data[mode].config,
+				sizeof(struct rt5501_config));
+	}
+	return 0;
+}
+
+
+static long rt5501_ioctl(struct file *file, unsigned int cmd,
+	   unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int rc = 0, modeid = 0;
+	unsigned char tmp[7];
+	unsigned char reg_idx[1] = {0x01};
+	struct rt5501_comm_data spk_cfg;
+	unsigned char reg_value[2];
+        int premode = 0;
+        int rt5501_status = 0;
+
+	switch (cmd) {
+	case RT5501_WRITE_REG:
+		pr_info("%s: RT5501_WRITE_REG\n", __func__);
+		mutex_lock(&hp_amp_lock);
+		if (!last_spkamp_state) {
+			
+			mdelay(30);
+		}
+		if (copy_from_user(reg_value, argp, sizeof(reg_value)))
+			goto err1;
+		pr_info("%s: reg_value[0]=%2x, reg_value[1]=%2x\n", __func__,  \
+				reg_value[0], reg_value[1]);
+		rc = rt5501_write_reg(reg_value[0], reg_value[1]);
+
+err1:
+		mutex_unlock(&hp_amp_lock);
+		break;
+	case RT5501_SET_CONFIG:
+		if (copy_from_user(&spk_cfg, argp, sizeof(struct rt5501_comm_data)))
+			return -EFAULT;
+
+		memcpy(&RT5501_AMP_ON, &spk_cfg.config,
+			sizeof(struct rt5501_config));
+		break;
+	case RT5501_READ_CONFIG:
+		mutex_lock(&hp_amp_lock);
+		if (!last_spkamp_state) {
+			
+			mdelay(30);
+		}
+
+		rc = rt5501_i2c_write_for_read(reg_idx, sizeof(reg_idx));
+		if (rc < 0)
+			goto err2;
+
+		rc = rt5501_i2c_read(tmp, sizeof(tmp));
+		if (rc < 0)
+			goto err2;
+
+		if (copy_to_user(argp, &tmp, sizeof(tmp)))
+			rc = -EFAULT;
+err2:
+		mutex_unlock(&hp_amp_lock);
+		break;
+	case RT5501_SET_MODE:
+		if (copy_from_user(&modeid, argp, sizeof(modeid)))
+			return -EFAULT;
+
+		if (modeid >= rt5501_config_data.mode_num || modeid <= 0) {
+			pr_err("unsupported rt5501 mode %d\n", modeid);
+			return -EINVAL;
+		}
+                mutex_lock(&hp_amp_lock);
+                premode = rt5501_query.curmode;
+                rt5501_query.curmode = modeid;
+		rc = update_amp_parameter(modeid);
+                rt5501_status = rt5501_query.rt5501_status;
+                mutex_unlock(&hp_amp_lock);
+		pr_info("%s:set rt5501 mode to %d curstatus %d\n", __func__,modeid,rt5501_status);
+		if(rt5501_status == RT5501_SUSPEND || (rt5501_status == RT5501_PLAYBACK && premode != rt5501_query.curmode)) {
+			flush_work_sync(&rt5501_query.volume_ramp_work.work);
+			mutex_lock(&rt5501_query.actionlock);
+			rt5501_query.action_on = 1;
+			mutex_unlock(&rt5501_query.actionlock);
+			queue_delayed_work(ramp_wq, &rt5501_query.volume_ramp_work, msecs_to_jiffies(280));
+		}
+		break;
+	case RT5501_SET_PARAM:
+		if (copy_from_user(&rt5501_config_data.mode_num, argp, sizeof(unsigned int))) {
+			pr_err("%s: copy from user failed.\n", __func__);
+			return -EFAULT;
+		}
+
+		if (rt5501_config_data.mode_num <= 0) {
+			pr_err("%s: invalid mode number %d\n",
+					__func__, rt5501_config_data.mode_num);
+			return -EINVAL;
+		}
+		if (rt5501_config_data.cmd_data == NULL)
+			rt5501_config_data.cmd_data = kzalloc(sizeof(struct rt5501_comm_data)*rt5501_config_data.mode_num, GFP_KERNEL);
+
+		if (!rt5501_config_data.cmd_data) {
+			pr_err("%s: out of memory\n", __func__);
+			return -ENOMEM;
+		}
+
+		if (copy_from_user(rt5501_config_data.cmd_data, ((struct rt5501_config_data*)argp)->cmd_data \
+                        ,sizeof(struct rt5501_comm_data)*rt5501_config_data.mode_num)) {
+                    pr_err("%s: copy data from user failed.\n", __func__);
+                    kfree(rt5501_config_data.cmd_data);
+                    rt5501_config_data.cmd_data = NULL;
+                    return -EFAULT;
+		}
+
+		pr_info("%s: update rt5501 i2c commands #%d success.\n",
+				__func__, rt5501_config_data.mode_num);
+		
+                mutex_lock(&hp_amp_lock);
+		update_amp_parameter(RT5501_MODE_OFF);
+		update_amp_parameter(RT5501_MUTE);
+		update_amp_parameter(RT5501_INIT);
+		
+                mutex_unlock(&hp_amp_lock);
+		rc = 0;
+		break;
+	case RT5501_QUERY_OM:
+                mutex_lock(&rt5501_query.mlock);
+		rc = rt5501_query.headsetom;
+                mutex_unlock(&rt5501_query.mlock);
+		pr_info("%s: query headset om %d\n", __func__,rc);
+
+		if (copy_to_user(argp, &rc, sizeof(rc)))
+                    rc = -EFAULT;
+                else
+                    rc = 0;
+		break;
+
+	default:
+		pr_err("%s: Invalid command\n", __func__);
+		rc = -EINVAL;
+		break;
+	}
+	return rc;
+}
+
+static struct file_operations rt5501_fops = {
+	.owner = THIS_MODULE,
+	.open = rt5501_open,
+	.release = rt5501_release,
+	.unlocked_ioctl = rt5501_ioctl,
+};
+
+static struct miscdevice rt5501_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "rt5501",
+	.fops = &rt5501_fops,
+};
+
+int rt5501_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	int ret = 0;
+        int err = 0;
+	MFG_MODE = board_mfg_mode();
+	pdata = client->dev.platform_data;
+
+	if (pdata == NULL) {
+		pr_info("%s: platform data null\n", __func__);
+		pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+		if (pdata == NULL) {
+			ret = -ENOMEM;
+			pr_err("%s: platform data is NULL\n", __func__);
+			goto err_alloc_data_failed;
+		}
+	}
+
+	this_client = client;
+
+	if (ret < 0) {
+		pr_err("%s: pmic request aud_spk_en pin failed\n", __func__);
+		goto err_free_gpio_all;
+	}
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		pr_err("%s: i2c check functionality error\n", __func__);
+		ret = -ENODEV;
+		goto err_free_gpio_all;
+	}
+
+	if(pdata->gpio_rt5501_spk_en) {
+		char temp[2];
+
+		err = gpio_request(pdata->gpio_rt5501_spk_en, "hp_en");
+
+		ret = gpio_direction_output(pdata->gpio_rt5501_spk_en, 1);
+
+		if(ret < 0) {
+			pr_err("%s: gpio %d on error %d\n", __func__,pdata->gpio_rt5501_spk_en,ret);
+		}
+
+		mdelay(1);
+
+		ret = rt5501_i2c_read(temp, 2);
+
+		if(ret < 0) {
+			pr_info("rt5501 is not connected\n");
+			rt5501Connect = 0;
+		} else {
+			pr_info("rt5501 is connected\n");
+			rt5501Connect = 1;
+		}
+
+		rt5501_write_reg(0x0,0x4);
+		mdelay(1);
+
+		rt5501_write_reg(0x0,0xc0);
+		rt5501_write_reg(0x81,0x30);
+		
+		rt5501_write_reg(0x90,0xd0);
+		rt5501_write_reg(0x93,0x9d);
+		rt5501_write_reg(0x95,0x7b);
+		rt5501_write_reg(0xa4,0x01);
+		
+		rt5501_write_reg(0x97,0x11);
+		rt5501_write_reg(0x98,0x22);
+		rt5501_write_reg(0x99,0x44);
+		rt5501_write_reg(0x9a,0x55);
+		rt5501_write_reg(0x9b,0x66);
+		rt5501_write_reg(0x9c,0x99);
+		rt5501_write_reg(0x9d,0x66);
+		rt5501_write_reg(0x9e,0x99);
+		rt5501_write_reg(0x1,0xc7);
+
+		gpio_direction_output(pdata->gpio_rt5501_spk_en, 0);
+
+		if(!err)
+			gpio_free(pdata->gpio_rt5501_spk_en);
+
+		if(ret < 0) {
+			pr_err("%s: gpio %d off error %d\n", __func__,pdata->gpio_rt5501_spk_en,ret);
+		}
+
+	}
+
+	if(rt5501Connect) {
+		struct headset_notifier notifier;
+		ret = misc_register(&rt5501_device);
+		if (ret) {
+			pr_err("%s: rt5501_device register failed\n", __func__);
+			goto err_free_gpio_all;
+		}
+
+		hs_wq = create_workqueue("rt5501_hsdetect");
+		INIT_DELAYED_WORK(&rt5501_query.hs_imp_detec_work,hs_imp_detec_func);
+		wake_lock_init(&rt5501_query.hs_wake_lock, WAKE_LOCK_SUSPEND, DRIVER_NAME);
+		wake_lock_init(&rt5501_query.gpio_wake_lock, WAKE_LOCK_SUSPEND, DRIVER_NAME);
+		ramp_wq = create_workqueue("rt5501_volume_ramp");
+		INIT_DELAYED_WORK(&rt5501_query.volume_ramp_work, volume_ramp_func);
+		gpio_wq = create_workqueue("rt5501_gpio_off");
+		INIT_DELAYED_WORK(&rt5501_query.gpio_off_work, hs_imp_gpio_off);
+		notifier.id = HEADSET_REG_HS_INSERT;
+		notifier.func = rt5501_headset_detect;
+		headset_notifier_register(&notifier);
+        }
+	return 0;
+
+err_free_gpio_all:
+        rt5501Connect = 0;
+	return ret;
+err_alloc_data_failed:
+        rt5501Connect = 0;
+	return ret;
+}
+
+static int rt5501_remove(struct i2c_client *client)
+{
+	struct rt5501_platform_data *p6185data = i2c_get_clientdata(client);
+	kfree(p6185data);
+
+        if(rt5501Connect) {
+            misc_deregister(&rt5501_device);
+            cancel_delayed_work_sync(&rt5501_query.hs_imp_detec_work);
+            destroy_workqueue(hs_wq);
+        }
+	return 0;
+}
+
+static void rt5501_shutdown(struct i2c_client *client)
+{
+	rt5501_query.gpio_off_cancel = 1;
+	cancel_delayed_work_sync(&rt5501_query.gpio_off_work);
+	cancel_delayed_work_sync(&rt5501_query.volume_ramp_work);
+
+	mutex_lock(&rt5501_query.gpiolock);
+	mutex_lock(&hp_amp_lock);
+        mutex_lock(&rt5501_query.mlock);
+
+	if((rt5501_query.gpiostatus == AMP_GPIO_OFF) && pdata->gpio_rt5501_spk_en) {
+
+		if(rt5501_query.s4status == AMP_S4_AUTO) {
+			pm8921_aud_set_s4_pwm();
+			rt5501_query.s4status = AMP_S4_PWM;
+			msleep(1);
+		}
+
+		pr_info("%s: enable gpio %d\n",__func__,pdata->gpio_rt5501_spk_en);
+		gpio_direction_output(pdata->gpio_rt5501_spk_en, 1);
+		rt5501_query.gpiostatus = AMP_GPIO_ON;
+		msleep(1);
+	}
+
+	pr_info("%s: reset rt5501\n",__func__);
+	rt5501_write_reg(0x0,0x4);
+	mdelay(1);
+
+	high_imp = 0;
+
+	if((rt5501_query.gpiostatus == AMP_GPIO_ON) && pdata->gpio_rt5501_spk_en) {
+		pr_info("%s: disable gpio %d\n",__func__,pdata->gpio_rt5501_spk_en);
+		gpio_direction_output(pdata->gpio_rt5501_spk_en, 0);
+		rt5501_query.gpiostatus = AMP_GPIO_OFF;
+
+		if(rt5501_query.s4status == AMP_S4_PWM) {
+			pm8921_aud_set_s4_auto();
+			rt5501_query.s4status = AMP_S4_AUTO;
+		}
+	}
+
+        mutex_unlock(&rt5501_query.mlock);
+	mutex_unlock(&hp_amp_lock);
+	mutex_unlock(&rt5501_query.gpiolock);
+
+}
+
+static int rt5501_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return 0;
+}
+
+static int rt5501_resume(struct i2c_client *client)
+{
+	return 0;
+}
+
+static const struct i2c_device_id rt5501_id[] = {
+	{ RT5501_I2C_NAME, 0 },
+	{ }
+};
+
+static struct i2c_driver rt5501_driver = {
+	.probe = rt5501_probe,
+	.remove = rt5501_remove,
+	.shutdown = rt5501_shutdown,
+	.suspend = rt5501_suspend,
+	.resume = rt5501_resume,
+	.id_table = rt5501_id,
+	.driver = {
+		.name = RT5501_I2C_NAME,
+	},
+};
+
+static int __init rt5501_init(void)
+{
+	pr_info("%s\n", __func__);
+	mutex_init(&hp_amp_lock);
+        mutex_init(&rt5501_query.mlock);
+        mutex_init(&rt5501_query.gpiolock);
+		mutex_init(&rt5501_query.actionlock);
+        rt5501_query.rt5501_status = RT5501_OFF;
+        rt5501_query.hs_qstatus = RT5501_QUERY_OFF;
+        rt5501_query.headsetom = HEADSET_8OM;
+        rt5501_query.curmode = RT5501_MODE_OFF;
+        rt5501_query.gpiostatus = AMP_GPIO_OFF;
+        rt5501_query.s4status = AMP_S4_AUTO;
+	return i2c_add_driver(&rt5501_driver);
+}
+
+static void __exit rt5501_exit(void)
+{
+	i2c_del_driver(&rt5501_driver);
+}
+
+module_init(rt5501_init);
+module_exit(rt5501_exit);
+
+MODULE_DESCRIPTION("rt5501 Speaker Amp driver");
+MODULE_LICENSE("GPL");