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(¬ifier);
+ }
+ 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, ®);
+ 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
+