misc: rt5501: Add HTC RT5501 Amp driver

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

Change-Id: I0b067aebecd6c36aa3bdeb32fad2b30aadc11f49
diff --git a/arch/arm/mach-msm/include/mach/htc_acoustic_pmic.h b/arch/arm/mach-msm/include/mach/htc_acoustic_pmic.h
new file mode 100644
index 0000000..b9b141a
--- /dev/null
+++ b/arch/arm/mach-msm/include/mach/htc_acoustic_pmic.h
@@ -0,0 +1,22 @@
+/* include/asm/mach-msm/htc_acoustic_pmic.h
+ *
+ * Copyright (C) 2012 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.
+ *
+ */
+#ifndef _ARCH_ARM_MACH_MSM_HTC_ACOUSTIC_PMIC_H_
+#define _ARCH_ARM_MACH_MSM_HTC_ACOUSTIC_PMIC_H_
+
+int pm8921_aud_set_s4_auto(void);
+int pm8921_aud_set_s4_pfm(void);
+int pm8921_aud_set_s4_pwm(void);
+#endif
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index ed18acb..bc34a90 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -727,6 +727,18 @@
 	help
 	  NXP TFA9887 Speaker AMP Driver implemented by HTC.
 
+config AMP_RT5501
+	tristate "TI RT5501 AMP Driver"
+	depends on I2C=y
+	help
+	  RichTek RT5501 AMP Driver implemented by HTC.
+
+config AMP_RT5501_DELAY
+	tristate "RichTek RT5501 AMP Delay Workaround"
+	depends on I2C=y
+	help
+	  RichTek RT5501 AMP Delay Workaround for pop noise of headset enable.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 861097a..2fbb839 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -81,3 +81,4 @@
 obj-$(CONFIG_SENSORS_NFC_PN544)	+= pn544.o
 obj-$(CONFIG_AMP_TFA9887)	+= tfa9887.o
 obj-$(CONFIG_AMP_TFA9887L)	+= tfa9887l.o
+obj-$(CONFIG_AMP_RT5501)	+= rt5501.o
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");
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index bee4e19..a382342 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -64,3 +64,4 @@
 obj-$(CONFIG_LTC4088_CHARGER)	+= ltc4088-charger.o
 obj-$(CONFIG_CHARGER_SMB347)	+= smb347-charger.o
 obj-$(CONFIG_BATTERY_BCL)	+= battery_current_limit.o
+obj-$(CONFIG_AMP_RT5501)	+= pm8921-audio.o
diff --git a/drivers/power/pm8921-audio.c b/drivers/power/pm8921-audio.c
new file mode 100644
index 0000000..ab57add
--- /dev/null
+++ b/drivers/power/pm8921-audio.c
@@ -0,0 +1,167 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/mfd/pm8xxx/pm8921-charger-htc.h>
+#include <linux/mfd/pm8xxx/pm8921-bms-htc.h>
+#include <linux/mfd/pm8xxx/pm8xxx-adc.h>
+#include <linux/mfd/pm8xxx/ccadc.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+
+#include <mach/board_htc.h>
+
+#include <mach/htc_gauge.h>
+#include <mach/htc_charger.h>
+#include <mach/htc_battery_cell.h>
+
+#define PM8921_AUDIO_DEV_NAME "pm8921-audio"
+
+#define S4_CTRL_ADDR 0x011
+#define S4_MODE_MASK 0x30
+#define S4_AUTO      0x30
+#define S4_PFM       0x20
+#define S4_PWM       0x10
+
+struct pm8921_audio_chip {
+	struct device			*dev;
+};
+
+static struct pm8921_audio_chip *the_chip;
+
+static int pm_chg_masked_write(struct pm8921_audio_chip *chip, u16 addr,
+							u8 mask, u8 val)
+{
+	int rc;
+	u8 reg;
+
+	rc = pm8xxx_readb(chip->dev->parent, addr, &reg);
+	if (rc) {
+		pr_err("pm8xxx_readb failed: addr=0x%03X, rc=%d\n", addr, rc);
+		return rc;
+	}
+	reg &= ~mask;
+	reg |= val & mask;
+	rc = pm8xxx_writeb(chip->dev->parent, addr, reg);
+	if (rc) {
+		pr_err("pm8xxx_writeb failed: addr=0x%03X, rc=%d\n", addr, rc);
+		return rc;
+	}
+	return 0;
+}
+
+
+int pm8921_aud_set_s4_auto(void)
+{
+    int ret = 0;
+    if(!the_chip)
+        return -1;
+
+    pr_info("%s: set s4 to audo mode ++\n",__func__);
+    ret = pm_chg_masked_write(the_chip, S4_CTRL_ADDR, S4_MODE_MASK, S4_AUTO);
+    pr_info("%s: set s4 to audo mode --\n",__func__);
+
+    return ret;
+}
+
+int pm8921_aud_set_s4_pfm(void)
+{
+    int ret = 0;
+    if(!the_chip)
+        return -1;
+
+    pr_info("%s: set s4 to pfm mode ++\n",__func__);
+    ret = pm_chg_masked_write(the_chip, S4_CTRL_ADDR, S4_MODE_MASK, S4_PFM);
+    pr_info("%s: set s4 to pfm mode --\n",__func__);
+
+    return ret;
+}
+
+
+int pm8921_aud_set_s4_pwm(void)
+{
+    int ret = 0;
+    if(!the_chip)
+        return -1;
+
+    pr_info("%s: set s4 to pwm mode ++\n",__func__);
+    ret = pm_chg_masked_write(the_chip, S4_CTRL_ADDR, S4_MODE_MASK, S4_PWM);
+    pr_info("%s: set s4 to pwm mode --\n",__func__);
+
+    return ret;
+}
+
+static int __devinit pm8921_audio_probe(struct platform_device *pdev)
+{
+	struct pm8921_audio_chip *chip;
+        pr_info("%s\n",__func__);
+	chip = kzalloc(sizeof(struct pm8921_audio_chip),
+					GFP_KERNEL);
+	if (!chip) {
+		pr_err("Cannot allocate pm_audio_chip\n");
+		return -ENOMEM;
+	}
+
+	chip->dev = &pdev->dev;
+        platform_set_drvdata(pdev, chip);
+        the_chip = chip;
+
+	return 0;
+
+}
+
+static int __devexit pm8921_audio_remove(struct platform_device *pdev)
+{
+	struct pm8921_audio_chip *chip = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+	the_chip = NULL;
+	kfree(chip);
+	return 0;
+}
+
+static struct platform_driver pm8921_audio_driver = {
+	.probe	= pm8921_audio_probe,
+	.remove	= __devexit_p(pm8921_audio_remove),
+	.driver	= {
+		.name	= PM8921_AUDIO_DEV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init pm8921_audio_init(void)
+{
+
+	return platform_driver_register(&pm8921_audio_driver);
+}
+
+static void __exit pm8921_audio_exit(void)
+{
+	platform_driver_unregister(&pm8921_audio_driver);
+}
+
+late_initcall(pm8921_audio_init);
+module_exit(pm8921_audio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8921 audio driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" PM8921_AUDIO_DEV_NAME);
diff --git a/include/linux/rt5501.h b/include/linux/rt5501.h
new file mode 100644
index 0000000..2b97b9f
--- /dev/null
+++ b/include/linux/rt5501.h
@@ -0,0 +1,128 @@
+#ifndef RT5501_H
+#define RT5501_H
+
+#include <linux/ioctl.h>
+#include <linux/wakelock.h>
+
+#define RT5501_I2C_NAME "rt5501"
+#define SPKR_OUTPUT 0
+#define HEADSET_OUTPUT 1
+#define DUAL_OUTPUT 2
+#define HANDSET_OUTPUT 3
+#define LINEOUT_OUTPUT 4
+#define NO_OUTPUT 5
+#define MODE_CMD_LEM 9
+#define MAX_REG_DATA 15
+
+struct rt5501_platform_data {
+	uint32_t gpio_rt5501_spk_en;
+	unsigned char spkr_cmd[7];
+	unsigned char hsed_cmd[7];
+	unsigned char rece_cmd[7];
+	
+	uint32_t gpio_rt5501_spk_en_cpu;
+};
+
+struct rt5501_reg_data {
+	unsigned char addr;
+	unsigned char val;
+};
+
+struct rt5501_config {
+	unsigned int reg_len;
+        struct rt5501_reg_data reg[MAX_REG_DATA];
+};
+
+struct rt5501_comm_data {
+	unsigned int out_mode;
+        struct rt5501_config config;
+};
+
+struct rt5501_config_data {
+	unsigned int mode_num;
+	struct rt5501_comm_data *cmd_data;  
+};
+
+enum {
+        RT5501_INIT = 0,
+        RT5501_MUTE,
+        RT5501_MAX_FUNC
+};
+
+enum RT5501_Mode {
+	RT5501_MODE_OFF = RT5501_MAX_FUNC,
+	RT5501_MODE_PLAYBACK,
+	RT5501_MODE_PLAYBACK8OH,
+	RT5501_MODE_PLAYBACK16OH,
+	RT5501_MODE_PLAYBACK32OH,
+	RT5501_MODE_PLAYBACK64OH,
+	RT5501_MODE_PLAYBACK128OH,
+	RT5501_MODE_PLAYBACK256OH,
+	RT5501_MODE_PLAYBACK500OH,
+	RT5501_MODE_PLAYBACK1KOH,
+	RT5501_MODE_VOICE,
+	RT5501_MODE_TTY,
+	RT5501_MODE_FM,
+	RT5501_MODE_RING,
+	RT5501_MODE_MFG,
+	RT5501_MODE_BEATS_8_64,
+	RT5501_MODE_BEATS_128_500,
+	RT5501_MODE_MONO,
+	RT5501_MODE_MONO_BEATS,
+	RT5501_MAX_MODE
+};
+
+enum HEADSET_QUERY_STATUS{
+    RT5501_QUERY_OFF = 0,
+    RT5501_QUERY_HEADSET,
+    RT5501_QUERY_FINISH,
+};
+
+
+enum RT5501_STATUS{
+    RT5501_OFF = 0,
+    RT5501_PLAYBACK,
+    RT5501_SUSPEND,
+
+};
+
+enum HEADSET_OM {
+    HEADSET_8OM = 0,
+    HEADSET_16OM,
+    HEADSET_32OM,
+    HEADSET_64OM,
+    HEADSET_128OM,
+    HEADSET_256OM,
+    HEADSET_500OM,
+    HEADSET_1KOM,
+    HEADSET_MONO,
+    HEADSET_OM_UNDER_DETECT,
+};
+
+enum AMP_GPIO_STATUS {
+     AMP_GPIO_OFF = 0,
+     AMP_GPIO_ON,
+     AMP_GPIO_QUERRTY_ON,
+};
+
+enum AMP_S4_STATUS {
+     AMP_S4_AUTO = 0,
+     AMP_S4_PWM,
+};
+
+#define QUERY_IMMED           msecs_to_jiffies(0)
+#define QUERY_LATTER          msecs_to_jiffies(200)
+#define RT5501_SENSE_READY    0x80
+
+#define RT5501_IOCTL_MAGIC 'g'
+#define RT5501_SET_CONFIG	_IOW(RT5501_IOCTL_MAGIC, 0x01,	unsigned)
+#define RT5501_READ_CONFIG	_IOW(RT5501_IOCTL_MAGIC, 0x02, unsigned)
+#define RT5501_SET_MODE        _IOW(RT5501_IOCTL_MAGIC, 0x03, unsigned)
+#define RT5501_SET_PARAM       _IOW(RT5501_IOCTL_MAGIC, 0x04,  unsigned)
+#define RT5501_WRITE_REG       _IOW(RT5501_IOCTL_MAGIC, 0x07,  unsigned)
+#define RT5501_QUERY_OM       _IOW(RT5501_IOCTL_MAGIC, 0x08,  unsigned)
+
+int query_rt5501(void);
+void set_rt5501_amp(int on);
+#endif
+