spi_aic3254 driver
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 6946b066..016a618 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -221,6 +221,13 @@
 	  From MPC8536, 85xx platform uses the controller, and all P10xx,
 	  P20xx, P30xx,P40xx, P50xx uses this controller.
 
+config SPI_AIC3254
+	tristate "TI AIC3254 SPI support"
+        default n
+	depends on ARCH_MSM7X30 || ARCH_MSM8X60
+	help
+	  TI AIC3254 is external codec for audio RX
+
 config SPI_OC_TINY
 	tristate "OpenCores tiny SPI"
 	depends on GENERIC_GPIO
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index a593faa..5615f05 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -64,4 +64,4 @@
 obj-$(CONFIG_SPI_TOPCLIFF_PCH)		+= spi-topcliff-pch.o
 obj-$(CONFIG_SPI_TXX9)			+= spi-txx9.o
 obj-$(CONFIG_SPI_XILINX)		+= spi-xilinx.o
-
+obj-$(CONFIG_SPI_AIC3254)		+= spi_aic3254.o
diff --git a/drivers/spi/spi_aic3254.c b/drivers/spi/spi_aic3254.c
new file mode 100644
index 0000000..9b830ef
--- /dev/null
+++ b/drivers/spi/spi_aic3254.c
@@ -0,0 +1,1061 @@
+/* linux/driver/spi/spi_aic3254.c
+ *
+ *
+ * Copyright (C) 2009 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/kernel.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/spi/spi.h>
+#include <linux/spi_aic3254.h>
+#include <linux/spi_aic3254_reg.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/wakelock.h>
+#include <linux/rtc.h>
+
+static struct spi_device *codec_dev;
+static struct mutex lock;
+static int aic3254_opend;
+static struct _CODEC_SPI_CMD **aic3254_uplink;
+static struct _CODEC_SPI_CMD **aic3254_downlink;
+static struct _CODEC_SPI_CMD **aic3254_minidsp;
+static uint8_t *bulk_tx;
+static int suspend_flag;
+static int aic3254_rx_mode;
+static int aic3254_tx_mode;
+static struct aic3254_ctl_ops default_ctl_ops;
+static struct aic3254_ctl_ops *ctl_ops = &default_ctl_ops;
+spinlock_t	spinlock;
+
+struct ecodec_aic3254_state {
+	int enabled;
+	struct clk *rx_mclk;
+	struct clk *rx_sclk;
+	struct wake_lock idlelock;
+	struct wake_lock wakelock;
+};
+static struct ecodec_aic3254_state codec_clk;
+
+/* function prototype */
+int route_tx_enable(int, int);
+int route_rx_enable(int, int);
+static void spi_aic3254_prevent_sleep(void);
+static void spi_aic3254_allow_sleep(void);
+
+void aic3254_register_ctl_ops(struct aic3254_ctl_ops *ops)
+{
+	ctl_ops = ops;
+}
+
+static int codec_spi_write(unsigned char addr, unsigned char data)
+{
+	unsigned char buffer[2];
+	int rc;
+
+	if (!codec_dev)
+		return 0;
+
+	codec_dev->bits_per_word = 16;
+	buffer[0] = addr << 1;
+	buffer[1] = data;
+	rc = spi_write(codec_dev, buffer, 2);
+
+	return 1;
+}
+
+static int codec_spi_read(unsigned char addr, unsigned char *data)
+{
+	int rc;
+	u8 buffer[2] = {0, 0};
+	u8 result[2] = {0, 0};
+
+	codec_dev->bits_per_word = 16;
+	buffer[0] = addr << 1 | 1;
+	rc = spi_write_and_read(codec_dev, buffer, result, 2);
+	if (rc < 0)
+		return rc;
+
+	*data = result[1];
+	return 0;
+}
+
+static int32_t spi_write_table(CODEC_SPI_CMD *cmds, int num)
+{
+	int i;
+	int status = 0;
+	struct spi_message	m;
+	struct spi_transfer	tx_addr;
+	spi_message_init(&m);
+	memset(bulk_tx, 0, MINIDSP_COL_MAX * 2 * sizeof(uint8_t));
+	memset(&tx_addr, 0, sizeof(struct spi_transfer));
+
+	for (i = 0; i < num ; i++) {
+		bulk_tx[i * 2] = cmds[i].reg << 1;
+		bulk_tx[i * 2 + 1] = cmds[i].data;
+	}
+	tx_addr.tx_buf = bulk_tx;
+	tx_addr.len = num * 2;
+	tx_addr.cs_change = 1;
+	tx_addr.bits_per_word = 16;
+	spi_message_add_tail(&tx_addr, &m);
+	if (codec_dev == NULL)
+		status = -ESHUTDOWN;
+	else
+		status = spi_sync(codec_dev, &m);
+	return status;
+}
+
+static int aic3254_config(CODEC_SPI_CMD *cmds, int size)
+{
+	int i, retry, ret;
+	unsigned char data;
+
+	if (ctl_ops->spibus_enable)
+		ctl_ops->spibus_enable(1);
+
+	if (!codec_dev) {
+		pr_err("[AUD] %s: no spi device\n", __func__);
+		return -EFAULT;
+	}
+
+	if (cmds == NULL) {
+		pr_err("[AUD] %s: invalid spi parameters\n", __func__);
+		return -EINVAL;
+	}
+
+	/* when LCM power is off, spi transmission would fail sometime */
+	if (suspend_flag && ctl_ops->panel_sleep_in) {
+		ret = ctl_ops->panel_sleep_in();
+		suspend_flag = 0;
+		if (ret < 0)
+			pr_err("[AUD] %s: cannot make panel awake,"
+				"it might failed on transmit SPI command\n"
+				, __func__);
+		else
+			pr_info("[AUD] %s: success on invoking panel_sleep_in\n"
+				, __func__);
+	}
+	/* large dsp image use bulk mode to transfer */
+	/* avoid to bulk transfer on spi use ext_gpio_cs project */
+	if (size < 1000) {
+		for (i = 0; i < size; i++) {
+			switch (cmds[i].act) {
+			case 'w':
+				codec_spi_write(cmds[i].reg, cmds[i].data);
+				break;
+			case 'r':
+				for (retry = AIC3254_MAX_RETRY; retry > 0; retry--) {
+					ret = codec_spi_read(cmds[i].reg, &data);
+					if (ret < 0)
+						pr_err("[AUD] %s: read fail %d, retry\n",
+							__func__, ret);
+					else if (data == cmds[i].data)
+						break;
+					msleep(1);
+				}
+				if (retry <= 0)
+					pr_info("[AUD] 3254 power down procedure"
+						" ,flag 0x%02X=0x%02X(0x%02X)\n",
+						cmds[i].reg,
+						ret, cmds[i].data);
+				break;
+			case 'd':
+				msleep(cmds[i].data);
+				break;
+			default:
+				break;
+			}
+		}
+	} else
+          spi_write_table(cmds, size);
+
+	if (ctl_ops->spibus_enable)
+		ctl_ops->spibus_enable(0);
+	return 0;
+}
+
+static int aic3254_config_ex(CODEC_SPI_CMD *cmds, int size)
+{
+	int i = 0;
+	int ret = -EINVAL;
+	struct spi_transfer *spi_t_cmds = NULL;
+	struct spi_message m;
+	unsigned char *buffer = NULL;
+	unsigned char *ptr = NULL;
+
+	if (!codec_dev) {
+		pr_err("[AUD] %s: no spi device\n", __func__);
+		return -EFAULT;
+	}
+
+	if (cmds == NULL || size == 0) {
+		pr_err("[AUD] %s: invalid spi parameters\n", __func__);
+		return -EINVAL;
+	} else {
+		/* pr_info("%s: size = %d", __func__, size); */
+	}
+
+	spi_t_cmds = (struct spi_transfer *) kmalloc(size*sizeof(struct spi_transfer), GFP_KERNEL);
+	if (spi_t_cmds == NULL) {
+		pr_err("[AUD] %s: kmalloc spi transfer struct fail\n", __func__);
+		goto error;
+	} else
+		memset(spi_t_cmds, 0, size*sizeof(struct spi_transfer));
+
+	buffer = (unsigned char *) kmalloc(size * 2 * sizeof(unsigned char), GFP_KERNEL);
+	if (buffer == NULL) {
+		pr_err("[AUD] %s: kmalloc buffer fail\n", __func__);
+		goto error;
+	} else
+		memset(buffer, 0, size*sizeof(CODEC_SPI_CMD)*sizeof(unsigned char));
+
+	if (ctl_ops->spibus_enable)
+		ctl_ops->spibus_enable(1);
+
+	spi_message_init(&m);
+	for (i = 0, ptr = buffer; i < size; i++, ptr += 2) {
+		ptr[0] = cmds[i].reg << 1;
+		ptr[1] = cmds[i].data;
+
+		spi_t_cmds[i].tx_buf = ptr;
+		spi_t_cmds[i].len = 2;
+		spi_message_add_tail(&spi_t_cmds[i], &m);
+	}
+	codec_dev->bits_per_word = 16;
+	ret = spi_sync(codec_dev, &m);
+
+	if (ctl_ops->spibus_enable)
+		ctl_ops->spibus_enable(0);
+
+error:
+	if (buffer)
+		kfree(buffer);
+
+	if (spi_t_cmds)
+		kfree(spi_t_cmds);
+	return ret;
+}
+
+static void aic3254_tx_config(int mode)
+{
+	/* use default setting when tx table doesn't be updated*/
+	if (aic3254_uplink == NULL) {
+		if (mode == UPLINK_OFF)
+			route_tx_enable(mode, 0);
+		else
+			route_tx_enable(mode, 1);
+		return;
+	}
+
+	if (mode != UPLINK_OFF && mode != POWER_OFF) {
+		/* uplink_Wakeup */
+		pr_info("[AUD] uplink wakeup len(%d)\n",
+			(aic3254_uplink[UPLINK_WAKEUP][0].data-1));
+		aic3254_config(
+			&aic3254_uplink[UPLINK_WAKEUP][1],
+			aic3254_uplink[UPLINK_WAKEUP][0].data);
+	}
+
+	/* route tx device */
+	pr_info("[AUD] uplink TX %d len(%d)\n", mode,
+		(aic3254_uplink[mode][0].data-1));
+	aic3254_config(&aic3254_uplink[mode][1],
+			aic3254_uplink[mode][0].data);
+}
+
+static void aic3254_rx_config(int mode)
+{
+	/* use default setting when rx table doesn't be updated*/
+	if (aic3254_downlink == NULL) {
+		if (mode == DOWNLINK_OFF)
+			route_rx_enable(mode, 0);
+		else
+			route_rx_enable(mode, 1);
+		return;
+	}
+
+	if (mode != DOWNLINK_OFF && mode != POWER_OFF) {
+		/* Downlink Wakeup */
+		pr_info("[AUD] downlink wakeup len(%d)\n",
+			(aic3254_downlink[DOWNLINK_WAKEUP][0].data-1));
+		aic3254_config(
+			&aic3254_downlink[DOWNLINK_WAKEUP][1],
+			aic3254_downlink[DOWNLINK_WAKEUP][0].data);
+	}
+
+	/* route rx device */
+	pr_info("[AUD] downlink RX %d len(%d)\n", mode,
+		(aic3254_downlink[mode][0].data-1));
+	aic3254_config(&aic3254_downlink[mode][1],
+				aic3254_downlink[mode][0].data);
+}
+
+static void aic3254_powerdown(void)
+{
+	int64_t t1, t2;
+#if defined(CONFIG_ARCH_MSM7X30)
+	struct ecodec_aic3254_state *drv = &codec_clk;
+#endif
+
+
+	if (aic3254_tx_mode != UPLINK_OFF || aic3254_rx_mode != DOWNLINK_OFF)
+		return;
+
+	t1 = ktime_to_ms(ktime_get());
+
+	spi_aic3254_prevent_sleep();
+	if (aic3254_uplink != NULL) {
+		pr_info("[AUD] power off AIC3254 len(%d)++\n",
+			(aic3254_uplink[POWER_OFF][0].data-1));
+		aic3254_config(&aic3254_uplink[POWER_OFF][1],
+				aic3254_uplink[POWER_OFF][0].data);
+	} else {
+		pr_info("[AUD] power off AIC3254 len(%d)++\n",
+			(ARRAY_SIZE(CODEC_POWER_OFF)));
+		aic3254_config(CODEC_POWER_OFF, ARRAY_SIZE(CODEC_POWER_OFF));
+	}
+
+#if defined(CONFIG_ARCH_MSM7X30)
+	if (drv->enabled) {
+		/* Disable MI2S RX master block */
+		/* Disable MI2S RX bit clock */
+		clk_disable(drv->rx_sclk);
+		clk_disable(drv->rx_mclk);
+		drv->enabled = 0;
+		pr_info("[AUD] %s: disable CLK\n", __func__);
+	}
+#endif
+
+	spi_aic3254_allow_sleep();
+
+	t2 = ktime_to_ms(ktime_get())-t1;
+	pr_info("[AUD] power off AIC3254 %lldms --\n", t2);
+	return;
+}
+
+void aic3254_force_powerdown(void)
+{
+	aic3254_rx_config(DOWNLINK_OFF);
+	aic3254_rx_mode = DOWNLINK_OFF;
+	aic3254_tx_config(UPLINK_OFF);
+	aic3254_tx_mode = UPLINK_OFF;
+	aic3254_powerdown();
+}
+
+static void aic3254_loopback(int mode)
+{
+	if (!(ctl_ops->lb_dsp_init &&
+		ctl_ops->lb_receiver_imic &&
+		ctl_ops->lb_speaker_imic &&
+		ctl_ops->lb_headset_emic)) {
+		pr_info("[AUD] %s: AIC3254 LOOPBACK not supported\n", __func__);
+		return;
+	}
+
+	/* Init AIC3254 A00 */
+	aic3254_config(ctl_ops->lb_dsp_init->data, ctl_ops->lb_dsp_init->len);
+
+	pr_info("[AUD] %s: set AIC3254 in LOOPBACK mode\n", __func__);
+	switch (mode) {
+	case 0:
+		/* receiver v.s. imic */
+		aic3254_config(ctl_ops->lb_receiver_imic->data,
+				ctl_ops->lb_receiver_imic->len);
+		break;
+	case 1:
+		/* speaker v.s. imic */
+		aic3254_config(ctl_ops->lb_speaker_imic->data,
+				ctl_ops->lb_speaker_imic->len);
+		break;
+	case 2:
+		/* headphone v.s emic */
+		aic3254_config(ctl_ops->lb_headset_emic->data,
+				ctl_ops->lb_headset_emic->len);
+		break;
+	case 13:
+		/* receiver v.s 2nd mic */
+		if (ctl_ops->lb_receiver_bmic)
+			aic3254_config(ctl_ops->lb_receiver_bmic->data,
+				ctl_ops->lb_receiver_bmic->len);
+		else
+			pr_info("[AUD] %s: receiver v.s. 2nd mic loopback not supported\n", __func__);
+		break;
+
+	case 14:
+		/* speaker v.s 2nd mic */
+		if (ctl_ops->lb_speaker_bmic)
+			aic3254_config(ctl_ops->lb_speaker_bmic->data,
+				ctl_ops->lb_speaker_bmic->len);
+		else
+			pr_info("[AUD] %s: speaker v.s. 2nd mic loopback not supported\n", __func__);
+		break;
+
+	case 15:
+		/* headphone v.s 2nd mic */
+		if (ctl_ops->lb_headset_bmic)
+			aic3254_config(ctl_ops->lb_headset_bmic->data,
+				ctl_ops->lb_headset_bmic->len);
+		else
+			pr_info("[AUD] %s: headset v.s. 2nd mic loopback not supported\n", __func__);
+		break;
+	default:
+		break;
+	}
+}
+
+int route_rx_enable(int path, int en)
+{
+	pr_info("[AUD] %s: (%d,%d) uses 3254 default setting\n", __func__, path, en);
+	if (en) {
+		/* Downlink_Wakeup */
+		aic3254_config(CODEC_DOWNLINK_ON,
+				ARRAY_SIZE(CODEC_DOWNLINK_ON));
+		/* Path switching */
+		switch (path) {
+		case FM_OUT_HEADSET:
+			/* FM headset */
+			aic3254_config(FM_In_Headphone,
+					ARRAY_SIZE(FM_In_Headphone));
+			aic3254_config(FM_Out_Headphone,
+					ARRAY_SIZE(FM_Out_Headphone));
+			break;
+		case FM_OUT_SPEAKER:
+			/* FM speaker */
+			aic3254_config(FM_In_SPK,
+					ARRAY_SIZE(FM_In_SPK));
+			aic3254_config(FM_Out_SPK,
+					ARRAY_SIZE(FM_Out_SPK));
+			break;
+		default:
+			/* By pass */
+			aic3254_config(Downlink_IMIC_Receiver,
+					ARRAY_SIZE(Downlink_IMIC_Receiver));
+			break;
+		}
+	} else {
+		/* Downlink_Off */
+		aic3254_config(CODEC_DOWNLINK_OFF,
+				ARRAY_SIZE(CODEC_DOWNLINK_OFF));
+	}
+
+	return 0;
+}
+
+int route_tx_enable(int path, int en)
+{
+	pr_info("[AUD] %s: (%d,%d) uses 3254 default setting\n", __func__, path, en);
+	if (en) {
+		/* Uplink_Wakeup */
+		aic3254_config(CODEC_UPLINK_ON, ARRAY_SIZE(CODEC_UPLINK_ON));
+		/* Path switching */
+		switch (path) {
+		case CALL_UPLINK_IMIC_RECEIVER:
+		case CALL_UPLINK_IMIC_HEADSET:
+		case CALL_UPLINK_IMIC_SPEAKER:
+		case VOICERECORD_IMIC:
+			/* By pass */
+			aic3254_config(MECHA_Uplink_IMIC,
+					ARRAY_SIZE(MECHA_Uplink_IMIC));
+			break;
+		case CALL_UPLINK_EMIC_HEADSET:
+		case VOICERECORD_EMIC:
+			aic3254_config(Uplink_EMIC,
+					ARRAY_SIZE(Uplink_EMIC));
+			break;
+		}
+	} else {
+		/* Uplink_Off */
+		aic3254_config(CODEC_UPLINK_OFF, ARRAY_SIZE(CODEC_UPLINK_OFF));
+	}
+
+	return 0;
+}
+
+
+void aic3254_set_mic_bias(int en)
+{
+	if (en)
+		aic3254_config(CODEC_MICBIAS_ON, ARRAY_SIZE(CODEC_MICBIAS_ON));
+	else
+		aic3254_config(CODEC_MICBIAS_OFF, ARRAY_SIZE(CODEC_MICBIAS_OFF));
+}
+
+static int aic3254_set_config(int config_tbl, int idx, int en)
+{
+	int rc = 0, len = 0;
+	int64_t t1, t2;
+#if defined(CONFIG_ARCH_MSM7X30)
+	struct ecodec_aic3254_state *drv = &codec_clk;
+#endif
+
+	mutex_lock(&lock);
+	spi_aic3254_prevent_sleep();
+
+#if defined(CONFIG_ARCH_MSM7X30)
+	if (drv->enabled == 0) {
+		/* enable MI2S RX master block */
+		/* enable MI2S RX bit clock */
+		clk_enable(drv->rx_mclk);
+		clk_enable(drv->rx_sclk);
+		pr_info("[AUD] %s: enable CLK\n", __func__);
+		drv->enabled = 1;
+	}
+#endif
+
+	switch (config_tbl) {
+	case AIC3254_CONFIG_TX:
+		/* TX */
+		pr_info("[AUD] %s: enable tx\n", __func__);
+		if (en) {
+			if (ctl_ops->tx_amp_enable)
+				ctl_ops->tx_amp_enable(0);
+
+			aic3254_tx_config(idx);
+			aic3254_tx_mode = idx;
+
+			if (ctl_ops->tx_amp_enable)
+				ctl_ops->tx_amp_enable(1);
+		} else {
+			aic3254_tx_config(UPLINK_OFF);
+			aic3254_tx_mode = UPLINK_OFF;
+		}
+		break;
+	case AIC3254_CONFIG_RX:
+		/* RX */
+		pr_info("[AUD] %s: enable rx\n", __func__);
+		if (en) {
+			if (ctl_ops->rx_amp_enable)
+				ctl_ops->rx_amp_enable(0);
+
+			aic3254_rx_config(idx);
+			aic3254_rx_mode = idx;
+
+			if (ctl_ops->rx_amp_enable)
+				ctl_ops->rx_amp_enable(1);
+		} else {
+			aic3254_rx_config(DOWNLINK_OFF);
+			aic3254_rx_mode = DOWNLINK_OFF;
+		}
+		break;
+	case AIC3254_CONFIG_MEDIA:
+		if (aic3254_minidsp == NULL) {
+			rc = -EFAULT;
+			break;
+		}
+
+		len = (aic3254_minidsp[idx][0].reg << 8)
+			| aic3254_minidsp[idx][0].data;
+
+		pr_info("[AUD] %s: configure miniDSP index(%d) len = %d ++\n",
+			__func__, idx, len);
+		pr_info("[AUD] %s: rx mode %d, tx mode %d\n",
+			__func__, aic3254_rx_mode, aic3254_tx_mode);
+
+		t1 = ktime_to_ms(ktime_get());
+
+		if (ctl_ops->rx_amp_enable)
+			ctl_ops->rx_amp_enable(0);
+
+		/* step 1: power off first */
+		if (aic3254_rx_mode != DOWNLINK_OFF)
+			aic3254_rx_config(DOWNLINK_OFF);
+
+		/* step 2: config DSP */
+		aic3254_config(&aic3254_minidsp[idx][1], len);
+
+		/* step 3: switch back to original path */
+		if (aic3254_rx_mode != DOWNLINK_OFF)
+			aic3254_rx_config(aic3254_rx_mode);
+		if (aic3254_tx_mode != UPLINK_OFF)
+			aic3254_tx_config(aic3254_tx_mode);
+
+		t2 = ktime_to_ms(ktime_get())-t1;
+
+		if (ctl_ops->rx_amp_enable)
+			ctl_ops->rx_amp_enable(1);
+
+		pr_info("[AUD] %s: configure miniDSP index(%d) time: %lldms --\n",
+			__func__, idx, (t2));
+		break;
+	}
+
+	spi_aic3254_allow_sleep();
+	mutex_unlock(&lock);
+	return rc;
+}
+
+static int aic3254_open(struct inode *inode, struct file *pfile)
+{
+	int ret = 0;
+
+	mutex_lock(&lock);
+	if (aic3254_opend) {
+		pr_err("[AUD] %s: busy\n", __func__);
+		ret = -EBUSY;
+	} else
+		aic3254_opend = 1;
+	mutex_unlock(&lock);
+
+	return ret;
+}
+
+static int aic3254_release(struct inode *inode, struct file *pfile)
+{
+	mutex_lock(&lock);
+	aic3254_opend = 0;
+	mutex_unlock(&lock);
+
+	return 0;
+}
+
+void aic3254_set_mode(int config, int mode)
+{
+	mutex_lock(&lock);
+	spi_aic3254_prevent_sleep();
+
+	switch (config) {
+	case AIC3254_CONFIG_TX:
+		/* TX */
+		pr_info("[AUD] %s: AIC3254_CONFIG_TX mode = %d\n",
+			__func__, mode);
+		aic3254_tx_config(mode);
+		aic3254_tx_mode = mode;
+		break;
+	case AIC3254_CONFIG_RX:
+		/* RX */
+		pr_info("[AUD] %s: AIC3254_CONFIG_RX mode = %d\n",
+			__func__, mode);
+		aic3254_rx_config(mode);
+		if (mode == FM_OUT_SPEAKER)
+			aic3254_tx_config(FM_IN_SPEAKER);
+		else if (mode == FM_OUT_HEADSET)
+			aic3254_tx_config(FM_IN_HEADSET);
+		aic3254_rx_mode = mode;
+		break;
+	}
+
+	aic3254_powerdown();
+
+	spi_aic3254_allow_sleep();
+	mutex_unlock(&lock);
+}
+
+static long aic3254_ioctl(struct file *file, unsigned int cmd,
+	   unsigned long argc)
+{
+	struct AIC3254_PARAM para;
+	void *table;
+	int ret = 0, i = 0, mem_size, volume = 0;
+	CODEC_SPI_CMD reg[2];
+	unsigned char data;
+
+	if (aic3254_uplink == NULL ||
+		aic3254_downlink == NULL ||
+		aic3254_minidsp == NULL) {
+		pr_err("[AUD] %s: cmd 0x%x, invalid pointers\n", __func__, cmd);
+		return -EFAULT;
+	}
+
+	switch (cmd) {
+	case AIC3254_SET_TX_PARAM:
+	case AIC3254_SET_RX_PARAM:
+		if (copy_from_user(&para, (void *)argc, sizeof(para))) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+
+		pr_info("[AUD] %s: parameters(%d, %d, %p)\n", __func__,
+				para.row_num, para.col_num, para.cmd_data);
+		if (cmd == AIC3254_SET_TX_PARAM)
+			table = aic3254_uplink[0];
+		else
+			table = aic3254_downlink[0];
+
+		/* confirm indicated size doesn't exceed the allocated one */
+		if (para.row_num > IO_CTL_ROW_MAX
+				|| para.col_num != IO_CTL_COL_MAX) {
+			pr_err("[AUD] %s: data size mismatch with allocated"
+					" memory (%d,%d)\n", __func__,
+					IO_CTL_ROW_MAX, IO_CTL_COL_MAX);
+			return -EFAULT;
+		}
+
+		mem_size = para.row_num * para.col_num * sizeof(CODEC_SPI_CMD);
+		if (copy_from_user(table, para.cmd_data, mem_size)) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+
+		/* invoking initialization procedure of AIC3254 */
+		if (cmd == AIC3254_SET_TX_PARAM)
+			aic3254_tx_config(INITIAL);
+
+		pr_info("[AUD] %s: update table(%d,%d) successfully\n",
+				__func__, para.row_num, para.col_num);
+			break;
+	case AIC3254_SET_DSP_PARAM:
+		if (copy_from_user(&para, (void *)argc, sizeof(para))) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+
+		pr_info("[AUD] %s: parameters(%d, %d, %p)\n", __func__,
+				para.row_num, para.col_num, para.cmd_data);
+
+		table = aic3254_minidsp[0];
+
+		/* confirm indicated size doesn't exceed the allocated one */
+		if (para.row_num > MINIDSP_ROW_MAX
+				|| para.col_num != MINIDSP_COL_MAX) {
+			pr_err("[AUD] %s: data size mismatch with allocated"
+					" memory (%d,%d)\n", __func__,
+					MINIDSP_ROW_MAX, MINIDSP_COL_MAX);
+			return -EFAULT;
+			}
+
+		mem_size = para.row_num * para.col_num * sizeof(CODEC_SPI_CMD);
+		if (copy_from_user(table, para.cmd_data, mem_size)) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+
+		pr_info("[AUD] %s: update table(%d,%d) successfully\n",
+				__func__, para.row_num, para.col_num);
+		break;
+	case AIC3254_CONFIG_TX:
+	case AIC3254_CONFIG_RX:
+	case AIC3254_CONFIG_MEDIA:
+		if (copy_from_user(&i, (void *)argc, sizeof(int))) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+		ret = aic3254_set_config(cmd, i, 1);
+		if (ret < 0)
+			pr_err("[AUD] %s: configure(%d) error %d\n",
+				__func__, i, ret);
+		break;
+	case AIC3254_CONFIG_VOLUME_L:
+		if (copy_from_user(&volume, (void *)argc, sizeof(int))) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+
+		if (volume < -127 || volume > 48) {
+			pr_err("[AUD] %s: volume out of range\n", __func__);
+			return -EFAULT;
+		}
+
+		pr_info("[AUD] %s: AIC3254 config left volume %d\n",
+				__func__, volume);
+
+		CODEC_SET_VOLUME_L[1].data = volume;
+		aic3254_config_ex(CODEC_SET_VOLUME_L, ARRAY_SIZE(CODEC_SET_VOLUME_L));
+		break;
+	case AIC3254_CONFIG_VOLUME_R:
+		if (copy_from_user(&volume, (void *)argc, sizeof(int))) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+
+		if (volume < -127 || volume > 48) {
+			pr_err("[AUD] %s: volume out of range\n", __func__);
+			return -EFAULT;
+		}
+
+		pr_info("[AUD] %s: AIC3254 config right volume %d\n",
+				__func__, volume);
+
+		CODEC_SET_VOLUME_R[1].data = volume;
+		aic3254_config_ex(CODEC_SET_VOLUME_R, ARRAY_SIZE(CODEC_SET_VOLUME_R));
+		break;
+	case AIC3254_DUMP_PAGES:
+		if (copy_from_user(&i, (void *)argc, sizeof(int))) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+		if (i > AIC3254_MAX_PAGES) {
+			pr_err("[AUD] %s: invalid page number %d\n", __func__, i);
+			return -EINVAL;
+		}
+
+		pr_info("[AUD] ========== %s: dump page %d ==========\n",
+				__func__, i);
+		/* indicated page number to AIC3254 */
+		if (ctl_ops->rx_amp_enable)
+			ctl_ops->rx_amp_enable(1);
+		codec_spi_write(0x00, i);
+		for (i = 0; i < AIC3254_MAX_REGS; i++) {
+			ret = codec_spi_read(i, &data);
+			if (ret < 0)
+				pr_err("[AUD] read fail on register 0x%X\n", i);
+			else
+				pr_info("[AUD] (0x%02X, 0x%02X)\n", i, data);
+		}
+		if (ctl_ops->rx_amp_enable)
+			ctl_ops->rx_amp_enable(0);
+		pr_info("[AUD] =============================================\n");
+		break;
+	case AIC3254_WRITE_REG:
+		if (copy_from_user(&reg, (void *)argc,
+					sizeof(CODEC_SPI_CMD)*2)) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+		pr_info("[AUD] %s: command list (%c,%02X,%02X) (%c,%02X,%02X)\n",
+				__func__, reg[0].act, reg[0].reg, reg[0].data,
+				reg[1].act, reg[1].reg, reg[1].data);
+		aic3254_config_ex(reg, 2);
+		break;
+	case AIC3254_READ_REG:
+		if (copy_from_user(&reg, (void *)argc,
+					sizeof(CODEC_SPI_CMD)*2)) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+		if (ctl_ops->spibus_enable)
+			ctl_ops->spibus_enable(1);
+		for (i = 0; i < 2; i++) {
+			if (reg[i].act == 'r' || reg[i].act == 'R')
+				codec_spi_read(reg[i].reg, &reg[i].data);
+			else if (reg[i].act == 'w' || reg[i].act == 'W')
+				codec_spi_write(reg[i].reg, reg[i].data);
+			else
+				return -EINVAL;
+		}
+		if (ctl_ops->spibus_enable)
+			ctl_ops->spibus_enable(0);
+		if (copy_to_user((void *)argc, &reg, sizeof(CODEC_SPI_CMD)*2)) {
+			pr_err("[AUD] %s: failed on copy_to_user\n", __func__);
+			return -EFAULT;
+		}
+		break;
+	case AIC3254_POWERDOWN:
+		mutex_lock(&lock);
+		aic3254_powerdown();
+		mutex_unlock(&lock);
+		break;
+	case AIC3254_LOOPBACK:
+		if (copy_from_user(&i, (void *)argc, sizeof(int))) {
+			pr_err("[AUD] %s: failed on copy_from_user\n", __func__);
+			return -EFAULT;
+		}
+		pr_info("[AUD] %s: index %d for LOOPBACK\n", __func__, i);
+		aic3254_loopback(i);
+		break;
+	default:
+		pr_err("[AUD] %s: invalid command %d\n", __func__, _IOC_NR(cmd));
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct file_operations aic3254_fops = {
+	.owner = THIS_MODULE,
+	.open = aic3254_open,
+	.release = aic3254_release,
+	.unlocked_ioctl = aic3254_ioctl,
+};
+
+static struct miscdevice aic3254_misc = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "codec_aic3254",
+	.fops = &aic3254_fops,
+};
+
+static  CODEC_SPI_CMD **init_2d_array(int row_sz, int col_sz)
+{
+	CODEC_SPI_CMD *table = NULL;
+	CODEC_SPI_CMD **table_ptr = NULL;
+	int i = 0;
+
+	table_ptr = kzalloc(row_sz * sizeof(CODEC_SPI_CMD *), GFP_KERNEL);
+	table = kzalloc(row_sz * col_sz * sizeof(CODEC_SPI_CMD), GFP_KERNEL);
+	if (table_ptr == NULL || table == NULL) {
+		pr_err("[AUD] %s: out of memory\n", __func__);
+		if (table != NULL)
+			kfree(table);
+		if (table_ptr != NULL)
+			kfree(table_ptr);
+	} else
+		for (i = 0; i < row_sz; i++)
+			table_ptr[i] = (CODEC_SPI_CMD *)table + i * col_sz;
+
+	return table_ptr;
+}
+
+static int spi_aic3254_probe(struct spi_device *aic3254)
+{
+	pr_info("[AUD] %s\n", __func__);
+
+	codec_dev = aic3254;
+
+	/* Boot up */
+#if 0
+	aic3254_config(CODEC_INIT_REG, ARRAY_SIZE(CODEC_INIT_REG));
+	aic3254_config(CODEC_DOWNLINK_OFF, ARRAY_SIZE(CODEC_DOWNLINK_OFF));
+	aic3254_config(CODEC_UPLINK_OFF, ARRAY_SIZE(CODEC_UPLINK_OFF));
+	aic3254_config(CODEC_POWER_OFF, ARRAY_SIZE(CODEC_POWER_OFF));
+#endif
+
+	aic3254_tx_mode = UPLINK_OFF;
+	aic3254_rx_mode = DOWNLINK_OFF;
+
+	/* request space for firmware data of AIC3254 */
+	aic3254_uplink = init_2d_array(IO_CTL_ROW_MAX, IO_CTL_COL_MAX);
+	aic3254_downlink = init_2d_array(IO_CTL_ROW_MAX, IO_CTL_COL_MAX);
+	aic3254_minidsp = init_2d_array(MINIDSP_ROW_MAX, MINIDSP_COL_MAX);
+	bulk_tx = kcalloc(MINIDSP_COL_MAX * 2 , sizeof(uint8_t), GFP_KERNEL);
+	spin_lock_init(&spinlock);
+	return 0;
+}
+
+
+static int spi_aic3254_suspend(struct spi_device *aic3254, pm_message_t pmsg)
+{
+	pr_info("[AUD] %s\n", __func__);
+	suspend_flag = 1;
+	return 0;
+}
+
+static int spi_aic3254_resume(struct spi_device *aic3254)
+{
+	pr_info("[AUD] %s\n", __func__);
+	return 0;
+}
+
+static int spi_aic3254_remove(struct spi_device *aic3254)
+{
+	pr_info("[AUD] %s\n", __func__);
+
+	/* release allocated memory in this driver */
+	if (aic3254_uplink != NULL) {
+		kfree(aic3254_uplink[0]);
+		kfree(aic3254_uplink);
+		aic3254_uplink = NULL;
+	}
+	if (aic3254_downlink != NULL) {
+		kfree(aic3254_downlink[0]);
+		kfree(aic3254_downlink);
+		aic3254_downlink = NULL;
+	}
+	if (aic3254_minidsp != NULL) {
+		kfree(aic3254_minidsp[0]);
+		kfree(aic3254_minidsp);
+		aic3254_minidsp = NULL;
+	}
+	if (bulk_tx != NULL) {
+		kfree(bulk_tx);
+		bulk_tx = NULL;
+	}
+	return 0;
+}
+
+static void spi_aic3254_prevent_sleep(void)
+{
+	struct ecodec_aic3254_state *codec_drv = &codec_clk;
+
+	wake_lock(&codec_drv->wakelock);
+	wake_lock(&codec_drv->idlelock);
+}
+
+static void spi_aic3254_allow_sleep(void)
+{
+	struct ecodec_aic3254_state *codec_drv = &codec_clk;
+
+	wake_unlock(&codec_drv->idlelock);
+	wake_unlock(&codec_drv->wakelock);
+}
+
+static struct spi_driver spi_aic3254 = {
+	.driver = {
+		.name = "spi_aic3254",
+		.owner = THIS_MODULE,
+	},
+	.probe = spi_aic3254_probe,
+	.suspend = spi_aic3254_suspend,
+	.resume = spi_aic3254_resume,
+	.remove = spi_aic3254_remove,
+};
+
+static int __init spi_aic3254_init(void)
+{
+	int ret = 0;
+	struct ecodec_aic3254_state *codec_drv =  &codec_clk;
+
+	pr_info("[AUD] %s\n", __func__);
+	mutex_init(&lock);
+
+	ret = spi_register_driver(&spi_aic3254);
+	if (ret < 0) {
+		pr_err("[AUD] %s:failed to register spi driver(%d)\n", __func__, ret);
+		return ret;
+	}
+
+	ret = misc_register(&aic3254_misc);
+	if (ret < 0) {
+		pr_err("[AUD] %s:failed to register misc device\n", __func__);
+		spi_unregister_driver(&spi_aic3254);
+		return ret;
+	}
+
+#if defined(CONFIG_ARCH_MSM7X30)
+	codec_drv->rx_mclk = clk_get(NULL, "mi2s_codec_rx_m_clk");
+	if (IS_ERR(codec_drv->rx_mclk)) {
+		pr_err("[AUD] %s:failed to get mi2s mclk\n", __func__);
+		misc_deregister(&aic3254_misc);
+		spi_unregister_driver(&spi_aic3254);
+		return -ENODEV;
+	}
+	codec_drv->rx_sclk = clk_get(NULL, "mi2s_codec_rx_s_clk");
+	if (IS_ERR(codec_drv->rx_sclk)) {
+		pr_err("[AUD] %s:failed to get mi2s sclk\n", __func__);
+		misc_deregister(&aic3254_misc);
+		spi_unregister_driver(&spi_aic3254);
+		clk_put(codec_drv->rx_mclk);
+		return -ENODEV;
+	}
+#endif
+
+	wake_lock_init(&codec_drv->wakelock, WAKE_LOCK_SUSPEND,
+			"aic3254_suspend_lock");
+
+	return 0;
+}
+module_init(spi_aic3254_init);
+
+static void __exit spi_aic3254_exit(void)
+{
+	struct ecodec_aic3254_state *codec_drv =  &codec_clk;
+
+	spi_unregister_driver(&spi_aic3254);
+	misc_deregister(&aic3254_misc);
+
+	wake_lock_destroy(&codec_drv->wakelock);
+	wake_lock_destroy(&codec_drv->idlelock);
+
+#if defined(CONFIG_ARCH_MSM7X30)
+	clk_put(codec_drv->rx_mclk);
+	clk_put(codec_drv->rx_sclk);
+#endif
+	return;
+}
+module_exit(spi_aic3254_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/spi_aic3254.h b/include/linux/spi_aic3254.h
new file mode 100644
index 0000000..1718770
--- /dev/null
+++ b/include/linux/spi_aic3254.h
@@ -0,0 +1,159 @@
+/* linux/driver/spi/spi_aic3254.h
+ *
+ * Copyright (C) 2009 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 __SPI_AIC3254_H__
+#define __SPI_AIC3254_H__
+
+#include <linux/ioctl.h>
+
+typedef struct _CODEC_SPI_CMD {
+	unsigned char act;
+	unsigned char reg;
+	unsigned char data;
+} CODEC_SPI_CMD;
+
+typedef struct _CODEC_SPI_CMD_PARAM {
+	CODEC_SPI_CMD *data;
+	unsigned int len;
+} CODEC_SPI_CMD_PARAM;
+
+struct AIC3254_PARAM {
+	unsigned int row_num;
+	unsigned int col_num;
+	void *cmd_data;
+};
+
+struct CODEC_CFG {
+	unsigned char tb_idx;
+	unsigned char index;
+};
+
+/* IO CONTROL definition of AIC3254 */
+#define AIC3254_IOCTL_MAGIC     's'
+#define AIC3254_SET_TX_PARAM     _IOW(AIC3254_IOCTL_MAGIC, 0x10, unsigned)
+#define AIC3254_SET_RX_PARAM     _IOW(AIC3254_IOCTL_MAGIC, 0x11, unsigned)
+#define AIC3254_CONFIG_TX        _IOW(AIC3254_IOCTL_MAGIC, 0x12, unsigned int)
+#define AIC3254_CONFIG_RX        _IOW(AIC3254_IOCTL_MAGIC, 0x13, unsigned int)
+#define AIC3254_SET_DSP_PARAM    _IOW(AIC3254_IOCTL_MAGIC, 0x20, unsigned)
+#define AIC3254_CONFIG_MEDIA     _IOW(AIC3254_IOCTL_MAGIC, 0x21, unsigned int)
+#define AIC3254_CONFIG_VOICE     _IOW(AIC3254_IOCTL_MAGIC, 0x22, unsigned int)
+#define AIC3254_CONFIG_VOLUME_L  _IOW(AIC3254_IOCTL_MAGIC, 0x23, unsigned int)
+#define AIC3254_CONFIG_VOLUME_R  _IOW(AIC3254_IOCTL_MAGIC, 0x24, unsigned int)
+#define AIC3254_POWERDOWN        _IOW(AIC3254_IOCTL_MAGIC, 0x25, unsigned int)
+#define AIC3254_LOOPBACK         _IOW(AIC3254_IOCTL_MAGIC, 0x26, unsigned int)
+#define AIC3254_DUMP_PAGES       _IOW(AIC3254_IOCTL_MAGIC, 0x30, unsigned int)
+#define AIC3254_READ_REG         _IOWR(AIC3254_IOCTL_MAGIC, 0x31, unsigned)
+#define AIC3254_WRITE_REG        _IOW(AIC3254_IOCTL_MAGIC, 0x32, unsigned)
+#define AIC3254_RESET        _IOW(AIC3254_IOCTL_MAGIC, 0x33, unsigned int)
+
+#define AIC3254_MAX_PAGES	255
+#define AIC3254_MAX_REGS        128
+#define AIC3254_MAX_RETRY	10
+
+#define IO_CTL_ROW_MAX		64
+#define IO_CTL_COL_MAX		1024
+#define MINIDSP_ROW_MAX		32
+#define MINIDSP_COL_MAX		16384
+
+enum aic3254_uplink_mode {
+	INITIAL = 0,
+	CALL_UPLINK_IMIC_RECEIVER = 1,
+	CALL_UPLINK_EMIC_HEADSET,
+	CALL_UPLINK_IMIC_HEADSET,
+	CALL_UPLINK_IMIC_SPEAKER,
+	CALL_UPLINK_IMIC_RECEIVER_DUALMIC,
+	CALL_UPLINK_EMIC_HEADSET_DUALMIC,
+	CALL_UPLINK_IMIC_SPEAKER_DUALMIC,
+	CALL_UPLINK_IMIC_RECIVER_TESTSIM,
+	CALL_UPLINK_EMIC_HEADSET_TESTSIM,
+	CALL_UPLINK_IMIC_SPEAKER_TESTSIM,
+	VOICERECORD_IMIC = 15,
+	VOICERECORD_EMIC,
+	VIDEORECORD_IMIC,
+	VIDEORECORD_EMIC,
+	VOICERECOGNITION_IMIC,
+	VOICERECOGNITION_EMIC,
+	FM_IN_SPEAKER,
+	FM_IN_HEADSET,
+	TTY_IN_HCO,
+	TTY_IN_VCO,
+	TTY_IN_FULL,
+	UPLINK_OFF = 29,
+	UPLINK_WAKEUP,
+	POWER_OFF,
+	SLEEP_WITH_HP_IN,
+	VOICERECORD_IMIC_PLAYBACK_SPEAKER,
+	VOICERECORD_EMIC_PLAYBACK_HEADSET,
+	VOICERECORD_IMIC_PLAYBACK_HEADSET,
+};
+
+enum aic3254_downlink_mode {
+	CALL_DOWNLINK_IMIC_RECEIVER = 1,
+	CALL_DOWNLINK_EMIC_HEADSET,
+	CALL_DOWNLINK_IMIC_HEADSET,
+	CALL_DOWNLINK_IMIC_SPEAKER,
+	CALL_DOWNLINK_IMIC_RECEIVER_DUALMIC,
+	CALL_DOWNLINK_EMIC_HEADSET_DUALMIC,
+	CALL_DOWNLINK_IMIC_SPEAKER_DUALMIC,
+	CALL_DOWNLINK_IMIC_RECIVER_TESTSIM,
+	CALL_DOWNLINK_EMIC_HEADSET_TESTSIM,
+	CALL_DOWNLINK_IMIC_SPEAKER_TESTSIM,
+	PLAYBACK_RECEIVER,
+	PLAYBACK_HEADSET,
+	PLAYBACK_SPEAKER = 13,
+	RING_HEADSET_SPEAKER,
+	PLAYBACK_SPEAKER_ALT,
+	USB_AUDIO,
+	FM_OUT_SPEAKER = 21,
+	FM_OUT_HEADSET,
+	TTY_OUT_HCO,
+	TTY_OUT_VCO,
+	TTY_OUT_FULL,
+	MUSE,
+	HAC,
+	LPM_IMIC_RECEIVER,
+	DOWNLINK_OFF = 29,
+	DOWNLINK_WAKEUP,
+};
+
+struct aic3254_ctl_ops {
+	void (*tx_amp_enable)(int en);
+	void (*rx_amp_enable)(int en);
+	int (*panel_sleep_in)(void);
+	void (*reset_3254)(void);
+	void (*spibus_enable)(int en);
+	CODEC_SPI_CMD_PARAM *downlink_off;
+	CODEC_SPI_CMD_PARAM *uplink_off;
+	CODEC_SPI_CMD_PARAM *downlink_on;
+	CODEC_SPI_CMD_PARAM *uplink_on;
+	CODEC_SPI_CMD_PARAM *lb_dsp_init;
+	CODEC_SPI_CMD_PARAM *lb_downlink_receiver;
+	CODEC_SPI_CMD_PARAM *lb_downlink_speaker;
+	CODEC_SPI_CMD_PARAM *lb_downlink_headset;
+	CODEC_SPI_CMD_PARAM *lb_uplink_imic;
+	CODEC_SPI_CMD_PARAM *lb_uplink_emic;
+	CODEC_SPI_CMD_PARAM *lb_receiver_imic;
+	CODEC_SPI_CMD_PARAM *lb_speaker_imic;
+	CODEC_SPI_CMD_PARAM *lb_headset_emic;
+	CODEC_SPI_CMD_PARAM *lb_receiver_bmic;
+	CODEC_SPI_CMD_PARAM *lb_speaker_bmic;
+	CODEC_SPI_CMD_PARAM *lb_headset_bmic;
+};
+
+void aic3254_register_ctl_ops(struct aic3254_ctl_ops *ops);
+void aic3254_set_mode(int config, int mode);
+void aic3254_set_mic_bias(int en);
+void aic3254_force_powerdown(void);
+#endif /* __SPI_AIC3254_H__*/
diff --git a/include/linux/spi_aic3254_reg.h b/include/linux/spi_aic3254_reg.h
new file mode 100644
index 0000000..2639b84
--- /dev/null
+++ b/include/linux/spi_aic3254_reg.h
@@ -0,0 +1,314 @@
+/* linux/driver/spi/spi_aic3254_reg.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __SPI_AIC3254_REG_H__
+#define __SPI_AIC3254_REG_H__
+/* The default setting for TI AIC3254 audio codec */
+#if 0
+static CODEC_SPI_CMD CODEC_INIT_REG[] = {
+	/* A00,Initial */
+	{'w', 0x00, 0x01},
+	{'w', 0x01, 0x00},
+	{'w', 0x02, 0x21},
+	{'w', 0x7B, 0x01},
+	{'w', 0x00, 0x00},
+	{'w', 0x06, 0x18},
+	{'w', 0x05, 0x51},
+	{'w', 0x04, 0x03},
+	{'w', 0x05, 0xD1},
+	{'w', 0x0B, 0x83},
+	{'w', 0x0C, 0x85},
+	{'w', 0x12, 0x83},
+	{'w', 0x13, 0x85},
+	{'w', 0x00, 0x01},
+	{'w', 0x0A, 0x3B},
+	{'w', 0x0C, 0x08},
+	{'w', 0x0D, 0x08},
+	{'w', 0x0E, 0x08},
+	{'w', 0x0F, 0x08},
+	{'w', 0x10, 0x00},
+	{'w', 0x11, 0x00},
+	{'w', 0x12, 0x00},
+	{'w', 0x13, 0x00},
+	{'w', 0x00, 0x00},
+	{'w', 0x40, 0x00},
+	{'w', 0x52, 0x00},
+	{'w', 0x1B, 0x00},
+	{'w', 0x1E, 0x00}
+};
+#endif
+
+static CODEC_SPI_CMD Uplink_EMIC[] = {
+	{'w', 0x00, 0x01},
+	{'w', 0x0F, 0x02},
+	{'w', 0x09, 0x0D},
+	{'w', 0x37, 0x08},
+	{'w', 0x39, 0x80},
+	{'w', 0x3B, 0x28},
+	{'w', 0x3C, 0x28},
+	{'w', 0x00, 0x00},
+	{'w', 0x51, 0x40},
+	{'w', 0x53, 0x00},
+	{'w', 0x54, 0x00}
+};
+
+#if 0
+static CODEC_SPI_CMD Uplink_IMIC[] = {
+	/* A01,Call_Uplink_IMIC_Receiver */
+	{'w', 0x00, 0x01},
+	{'w', 0x34, 0x08},
+	{'w', 0x36, 0x80},
+	{'w', 0x3B, 0x28},
+	{'w', 0x3C, 0x28},
+	{'w', 0x00, 0x00},
+	{'w', 0x51, 0x80},
+	{'w', 0x53, 0x00},
+	{'w', 0x54, 0x00}
+};
+
+static CODEC_SPI_CMD Uplink_IMIC_LineOut[] = {
+	/* B02 */
+	{'w', 0x00, 0x01},
+	{'w', 0x0E, 0x02},
+	{'w', 0x09, 0x3F},
+	{'w', 0x12, 0x3E},
+	{'w', 0x13, 0x3E},
+	{'w', 0x00, 0x00},
+	{'w', 0x41, 0x00},
+	{'w', 0x42, 0x00},
+	{'w', 0x3F, 0xF6}
+};
+
+static CODEC_SPI_CMD Uplink_EMIC_LineOut[] = {
+	{'w', 0x00, 0x01},
+	{'w', 0x0E, 0x02},
+	{'w', 0x09, 0x3D},
+	{'w', 0x12, 0x03},
+	{'w', 0x13, 0x3E},
+	{'w', 0x00, 0x00},
+	{'w', 0x41, 0x00},
+	{'w', 0x42, 0x00},
+	{'w', 0x3F, 0xD6}
+};
+#endif
+
+static CODEC_SPI_CMD Downlink_IMIC_Receiver[] = {
+	/* B08 */
+	{'w', 0x00, 0x01},
+	{'w', 0x09, 0x3F},
+	{'w', 0x10, 0x00},
+	{'w', 0x11, 0x00},
+	{'w', 0x0D, 0x10},
+	{'w', 0x00, 0x00},
+	{'w', 0x41, 0x0C},
+	{'w', 0x42, 0x0C},
+	{'w', 0x3F, 0xF6},
+	{'w', 0x40, 0x80},
+};
+
+static CODEC_SPI_CMD CODEC_UPLINK_OFF[] = {
+	/* A29,Uplink_Off */
+	{'w', 0x00, 0x01},
+	{'w', 0x34, 0x00},
+	{'w', 0x36, 0x00},
+	{'w', 0x39, 0x00},
+	{'w', 0x37, 0x00},
+	{'w', 0x3B, 0x80},
+	{'w', 0x3C, 0x80},
+	{'w', 0x00, 0x00},
+	{'w', 0x52, 0x88},
+	{'w', 0x53, 0x00},
+	{'w', 0x54, 0x00},
+	{'w', 0x54, 0x00}
+};
+
+static CODEC_SPI_CMD CODEC_UPLINK_ON[] = {
+	/* A30, Uplink_Wakeup */
+	{'w', 0x00, 0x01},
+	{'w', 0x01, 0x08},
+	{'w', 0x02, 0x21},
+	{'w', 0x3D, 0x00},
+	{'w', 0x47, 0x32},
+	{'w', 0x00, 0x00},
+	{'w', 0x3D, 0x01},
+	{'w', 0x52, 0x00},
+	{'w', 0x05, 0xD1},
+	{'w', 0x12, 0x83},
+	{'w', 0x13, 0x85},
+	{'w', 0x1E, 0x94},
+};
+
+static CODEC_SPI_CMD CODEC_DOWNLINK_OFF[] = {
+	/* B29,Downlink_Off */
+	{'w', 0x00, 0x01},
+	{'w', 0x09, 0x00},
+	{'w', 0x10, 0x40},
+	{'w', 0x11, 0x40},
+	{'w', 0x12, 0x40},
+	{'w', 0x13, 0x40},
+	{'w', 0x00, 0x00},
+	{'w', 0x3F, 0x16},
+	{'w', 0x40, 0x0C},
+	{'w', 0x41, 0x00},
+	{'w', 0x42, 0x00},
+	{'w', 0x40, 0x00},
+	{'w', 0x0B, 0x03},
+	{'w', 0x0C, 0x05},
+};
+
+static CODEC_SPI_CMD CODEC_DOWNLINK_ON[] = {
+	/* B30, Downlink_Wakeup */
+	{'w', 0x00, 0x01},
+	{'w', 0x01, 0x08},
+	{'w', 0x02, 0x21},
+	{'w', 0x14, 0x25},
+	{'w', 0x47, 0x32},
+	{'w', 0x00, 0x00},
+	{'w', 0x3C, 0x08},
+	{'w', 0x40, 0x00},
+	{'w', 0x05, 0xD1},
+	{'w', 0x0B, 0x83},
+	{'w', 0x0C, 0x85},
+	{'w', 0x1E, 0x94}
+};
+
+static CODEC_SPI_CMD CODEC_POWER_OFF[] = {
+	/* A31, Power_Off */
+	{'w', 0x00, 0x00},
+	{'w', 0x1E, 0x14},
+	{'w', 0x0B, 0x03},
+	{'w', 0x0C, 0x05},
+	{'w', 0x12, 0x03},
+	{'w', 0x13, 0x05},
+	{'w', 0x05, 0x51},
+	{'w', 0x51, 0x00},
+	{'w', 0x3F, 0x16},
+	{'w', 0x00, 0x01},
+	{'w', 0x33, 0x00},
+	{'w', 0x01, 0x00},
+	{'w', 0x02, 0x28}
+};
+
+static CODEC_SPI_CMD FM_In_Headphone[] = {
+	/* A22 */
+	{'w', 0x00, 0x01},
+	{'w', 0x0C, 0x02},
+	{'w', 0x0D, 0x02},
+	{'w', 0x09, 0x33},
+	{'w', 0x34, 0x20},
+	{'w', 0x36, 0x80},
+	{'w', 0x37, 0x20},
+	{'w', 0x39, 0x80},
+	{'w', 0x3B, 0x28},
+	{'w', 0x3C, 0x28},
+	{'w', 0x00, 0x00},
+	{'w', 0x51, 0xC0},
+	{'w', 0x53, 0x00},
+	{'w', 0x54, 0x00},
+};
+
+static CODEC_SPI_CMD FM_Out_Headphone[] = {
+	/* B22 */
+	{'w', 0x00, 0x01},
+	{'w', 0x09, 0x33},
+	{'w', 0x10, 0x3E},
+	{'w', 0x11, 0x3E},
+	{'w', 0x00, 0x00},
+	{'w', 0x41, 0x00},
+	{'w', 0x42, 0x00},
+	{'w', 0x3F, 0xD6},
+};
+
+static CODEC_SPI_CMD FM_In_SPK[] = {
+	/* A21 */
+	{'w', 0x00, 0x01},
+	{'w', 0x0C, 0x02},
+	{'w', 0x0D, 0x02},
+	{'w', 0x09, 0x33},
+	{'w', 0x34, 0x20},
+	{'w', 0x36, 0x80},
+	{'w', 0x37, 0x20},
+	{'w', 0x39, 0x80},
+	{'w', 0x3B, 0x28},
+	{'w', 0x3C, 0x28},
+	{'w', 0x00, 0x00},
+	{'w', 0x51, 0xC0},
+	{'w', 0x53, 0x00},
+	{'w', 0x54, 0x00},
+};
+
+static CODEC_SPI_CMD FM_Out_SPK[] = {
+	/* B21 */
+	{'w', 0x00, 0x01},
+	{'w', 0x09, 0x33},
+	{'w', 0x10, 0x00},
+	{'w', 0x11, 0x00},
+	{'w', 0x00, 0x00},
+	{'w', 0x41, 0x00},
+	{'w', 0x42, 0x00},
+	{'w', 0x3F, 0x96},
+	{'w', 0x40, 0x80},
+};
+
+static CODEC_SPI_CMD MECHA_Uplink_IMIC[] = {
+	{'w', 0x00, 0x01},
+	{'w', 0x34, 0x20},
+	{'w', 0x36, 0x20},
+	{'w', 0x37, 0x00},
+	{'w', 0x39, 0x00},
+	{'w', 0x3B, 0x28},
+
+	{'w', 0x3C, 0xA8},
+	{'w', 0x00, 0x00},
+	{'w', 0x51, 0x80},
+	{'w', 0x52, 0x08},
+	{'w', 0x53, 0x00},
+	{'w', 0x54, 0x00},
+	{'w', 0x00, 0x01},
+	{'w', 0x0E, 0x02},
+	{'w', 0x0F, 0x02},
+	{'w', 0x09, 0x3F},
+	{'w', 0x12, 0x3E},
+	{'w', 0x13, 0x3E},
+	{'w', 0x00, 0x00},
+	{'w', 0x40, 0x0C},
+	{'w', 0x41, 0x00},
+	{'w', 0x42, 0x00},
+	{'w', 0x3F, 0xD6},
+};
+
+static CODEC_SPI_CMD CODEC_SET_VOLUME_L[] = {
+	{'w', 0x00, 0x00},
+	{'w', 0x41, 0x00}
+};
+
+static CODEC_SPI_CMD CODEC_SET_VOLUME_R[] = {
+	{'w', 0x00, 0x00},
+	{'w', 0x42, 0x00}
+};
+
+static CODEC_SPI_CMD CODEC_MICBIAS_ON[] = {
+	{'w', 0x00, 0x01},
+	{'w', 0x01, 0x08},
+	{'w', 0x02, 0x21},
+	{'w', 0x33, 0x68}
+};
+
+static CODEC_SPI_CMD CODEC_MICBIAS_OFF[] = {
+	{'w', 0x00, 0x01},
+	{'w', 0x33, 0x28}
+};
+
+#endif /* __SPI_AIC3254_REG_H__*/