audio : enable tpa2028d

Change-Id: Id35a8bf66b1b730d32d1d8cbc68cd3d7f607e72e
diff --git a/arch/arm/configs/mako-perf_defconfig b/arch/arm/configs/mako-perf_defconfig
index 405e8ff..172e953 100644
--- a/arch/arm/configs/mako-perf_defconfig
+++ b/arch/arm/configs/mako-perf_defconfig
@@ -353,6 +353,7 @@
 CONFIG_SND_SOC=y
 CONFIG_SND_SOC_MSM8960=y
 CONFIG_SND_SOC_DUAL_AMIC=y
+CONFIG_SND_SOC_TPA2028D=y
 CONFIG_HID_APPLE=y
 CONFIG_HID_MAGICMOUSE=y
 CONFIG_HID_MICROSOFT=y
diff --git a/arch/arm/mach-msm/lge/mako/board-mako-sound.c b/arch/arm/mach-msm/lge/mako/board-mako-sound.c
index 9099a02..6f364ca 100644
--- a/arch/arm/mach-msm/lge/mako/board-mako-sound.c
+++ b/arch/arm/mach-msm/lge/mako/board-mako-sound.c
@@ -24,7 +24,7 @@
 #include "../../../../sound/soc/codecs/wcd9310.h"
 #endif
 
-#ifdef CONFIG_LGE_AUDIO_TPA2028D
+#ifdef CONFIG_SND_SOC_TPA2028D
 #include <sound/tpa2028d.h>
 #endif
 
@@ -77,12 +77,7 @@
 	unsigned int latency_for_detection;
 };
 
-#ifdef CONFIG_LGE_AUDIO_TPA2028D
-int amp_power(bool on)
-{
-	return 0;
-}
-
+#ifdef CONFIG_SND_SOC_TPA2028D
 int amp_enable(int on_state)
 {
 	int err = 0;
@@ -135,7 +130,6 @@
 
 static struct audio_amp_platform_data amp_platform_data =  {
 	.enable = amp_enable,
-	.power = amp_power,
 	.agc_compression_rate = AGC_COMPRESIION_RATE,
 	.agc_output_limiter_disable = AGC_OUTPUT_LIMITER_DISABLE,
 	.agc_fixed_gain = AGC_FIXED_GAIN,
@@ -143,7 +137,7 @@
 #endif
 
 static struct i2c_board_info msm_i2c_audiosubsystem_info[] = {
-#ifdef CONFIG_LGE_AUDIO_TPA2028D
+#ifdef CONFIG_SND_SOC_TPA2028D
 	{
 		I2C_BOARD_INFO("tpa2028d_amp", TPA2028D_ADDRESS),
 		.platform_data = &amp_platform_data,
diff --git a/include/sound/tpa2028d.h b/include/sound/tpa2028d.h
new file mode 100644
index 0000000..43d9941
--- /dev/null
+++ b/include/sound/tpa2028d.h
@@ -0,0 +1,50 @@
+/* include/sound/tpa2028d.h
+ *
+ * Copyright (C) 2009 LGE, Inc.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Revision 0.1 -- BJB -- 3/18/10 -- Original Version
+ * Revision 0.2 -- BJB -- 3/19/10 -- Corrected dB to DB in Register 0x01, Added function prototypes.
+ * Revision 0.3 -- AME -- 5/17/10 -- No changes to TPA2055D3_FDK.h file.
+ *
+ */
+
+/* ---- I2C ADDR -----*/
+#define IC_CONTROL		(char) 0x01
+#define AGC_ATTACK_CONTROL	(char) 0x02
+#define AGC_RELEASE_CONTROL	(char) 0x03
+#define AGC_HOLD_TIME_CONTROL	(char) 0x04
+#define AGC_FIXED_GAIN_CONTROL	(char) 0x05
+#define AGC1_CONTROL		(char) 0x06
+#define AGC2_CONTROL		(char) 0x07
+
+struct amp_cal {
+	u8 dev_type;
+	u8 gain_type;
+	u8 data;
+};
+
+void set_amp_gain(int num);
+
+struct audio_amp_platform_data {
+	int (*enable)(int);
+	int (*power)(bool);
+	char agc_compression_rate;
+	char agc_output_limiter_disable;
+	char agc_fixed_gain;
+};
+
+/* SPK FUNCTION */
+#define SPK_ON 1
+#define SPK_OFF 0
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 80204f5..0d4f4b4 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -445,3 +445,9 @@
 
 config SND_SOC_MSM_STUB
 	tristate
+
+config SND_SOC_TPA2028D
+	bool "Audio subsystem TPA2028D"
+	default y
+	---help---
+		Texas Instruments 3W Mono Class-D Audio Amplifier
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 965d6a1..2c8f730 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -210,3 +210,4 @@
 # Amp
 obj-$(CONFIG_SND_SOC_MAX9877)	+= snd-soc-max9877.o
 obj-$(CONFIG_SND_SOC_TPA6130A2)	+= snd-soc-tpa6130a2.o
+obj-$(CONFIG_SND_SOC_TPA2028D)	+= tpa2028d.o
diff --git a/sound/soc/codecs/tpa2028d.c b/sound/soc/codecs/tpa2028d.c
new file mode 100644
index 0000000..38c8c96
--- /dev/null
+++ b/sound/soc/codecs/tpa2028d.c
@@ -0,0 +1,321 @@
+/* sound/soc/codecs/tpa2028d.c
+ *
+ * Copyright (C) 2009 LGE, Inc.
+ *
+ * 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/platform_device.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <asm/system.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <asm/ioctls.h>
+#include <mach/debug_mm.h>
+#include <linux/slab.h>
+
+#include <sound/tpa2028d.h>
+
+#define MODULE_NAME "tpa2028d"
+
+#undef  AMP_DEBUG_PRINT
+#define AMP_DEBUG_PRINT
+
+#define AMP_IOCTL_MAGIC 't'
+#define AMP_SET_DATA	_IOW(AMP_IOCTL_MAGIC, 0, struct amp_cal *)
+#define AMP_GET_DATA	_IOW(AMP_IOCTL_MAGIC, 1, struct amp_cal *)
+
+static uint32_t msm_snd_debug = 1;
+module_param_named(debug_mask, msm_snd_debug, uint, 0664);
+
+#if defined (AMP_DEBUG_PRINT)
+#define D(fmt, args...) printk(KERN_INFO "[%s:%5d]" fmt, __func__, __LINE__, ##args)
+#else
+#define D(fmt, args...) do {} while (0)
+#endif
+
+struct amp_config {
+	struct i2c_client *client;
+	struct audio_amp_platform_data *pdata;
+};
+
+static struct amp_config *amp_data = NULL;
+
+int ReadI2C(char reg, char *ret)
+{
+
+	unsigned int err;
+	unsigned char buf = reg;
+
+	struct i2c_msg msg[2] = {
+		{ amp_data->client->addr, 0, 1, &buf },
+		{ amp_data->client->addr, I2C_M_RD, 1, ret}
+	};
+	err = i2c_transfer(amp_data->client->adapter, msg, 2);
+	if (err < 0)
+		D("i2c read error:%x\n", reg);
+	else
+		D("i2c read ok:%x\n", reg);
+	return 0;
+
+}
+
+int WriteI2C(char reg, char val)
+{
+
+	int err;
+	unsigned char buf[2];
+	struct i2c_msg msg = {amp_data->client->addr, 0, 2, buf };
+
+	buf[0] = reg;
+	buf[1] = val;
+	err = i2c_transfer(amp_data->client->adapter, &msg, 1);
+
+	if (err < 0) {
+		D("i2c write error:%x:%x\n", reg, val);
+		return -EIO;
+	} else {
+		return 0;
+	}
+}
+
+int tpa2028d_poweron(void)
+{
+	int fail = 0;
+	char agc_compression_rate = amp_data->pdata->agc_compression_rate;
+	char agc_output_limiter_disable = amp_data->pdata->agc_output_limiter_disable;
+	char agc_fixed_gain = amp_data->pdata->agc_fixed_gain;
+
+	agc_output_limiter_disable = (agc_output_limiter_disable<<7);
+
+	fail |= WriteI2C(IC_CONTROL, 0xE3); /*Tuen On*/
+	fail |= WriteI2C(AGC_ATTACK_CONTROL, 0x05); /*Tuen On*/
+	fail |= WriteI2C(AGC_RELEASE_CONTROL, 0x0B); /*Tuen On*/
+	fail |= WriteI2C(AGC_HOLD_TIME_CONTROL, 0x00); /*Tuen On*/
+	fail |= WriteI2C(AGC_FIXED_GAIN_CONTROL, agc_fixed_gain); /*Tuen On*/
+	fail |= WriteI2C(AGC1_CONTROL, 0x3A|agc_output_limiter_disable); /*Tuen On*/
+	fail |= WriteI2C(AGC2_CONTROL, 0xC0|agc_compression_rate); /*Tuen On*/
+	fail |= WriteI2C(IC_CONTROL, 0xC3); /*Tuen On*/
+
+	return fail;
+}
+
+int tpa2028d_powerdown(void)
+{
+	int fail = 0;
+	return fail;
+}
+
+inline void set_amp_gain(int amp_state)
+{
+	int fail = 0;
+
+	switch (amp_state) {
+	case SPK_ON:
+		/* if the delay time is need for chip ready,
+		 * should be added to each power or enable function.
+		 */
+		if (amp_data->pdata->power)
+			fail = amp_data->pdata->power(1);
+		/*need 5 msec for prevent mute issue*/
+		msleep(5);
+		if (amp_data->pdata->enable)
+			fail = amp_data->pdata->enable(1);
+		/*need 10 msec for chip ready*/
+		msleep(10);
+		fail = tpa2028d_poweron();
+		break;
+	case SPK_OFF:
+		if (amp_data->pdata->enable)
+			fail = amp_data->pdata->enable(2);
+		fail = tpa2028d_powerdown();
+		if (amp_data->pdata->enable)
+			fail = amp_data->pdata->enable(0);
+		if (amp_data->pdata->power)
+			fail = amp_data->pdata->power(0);
+		break;
+	default:
+		D("Amp_state [%d] does not support \n", amp_state);
+	}
+}
+EXPORT_SYMBOL(set_amp_gain);
+
+static ssize_t
+tpa2028d_comp_rate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct audio_amp_platform_data *pdata = amp_data->pdata;
+	int val;
+
+	if (sscanf(buf, "%d", &val) != 1)
+		return -EINVAL;
+	if (val > 3 || val < 0)
+		return -EINVAL;
+
+	pdata->agc_compression_rate = val;
+	return count;
+}
+
+static ssize_t
+tpa2028d_comp_rate_show(struct device *dev, struct device_attribute *attr,   char *buf)
+{
+	struct audio_amp_platform_data *pdata = amp_data->pdata;
+	char val = 0;
+
+	ReadI2C(AGC2_CONTROL, &val);
+
+	D("[tpa2028d_comp_rate_show] val : %x \n",val);
+
+	return sprintf(buf, "AGC2_CONTROL : %x, pdata->agc_compression_rate : %d\n", val, pdata->agc_compression_rate);
+}
+
+static ssize_t
+tpa2028d_out_lim_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct audio_amp_platform_data *pdata = amp_data->pdata;
+	int val;
+
+	if (sscanf(buf, "%d", &val) != 1)
+		return -EINVAL;
+	pdata->agc_output_limiter_disable = val&0x0001;
+	return count;
+}
+
+static ssize_t
+tpa2028d_out_lim_show(struct device *dev, struct device_attribute *attr,   char *buf)
+{
+	struct audio_amp_platform_data *pdata = amp_data->pdata;
+	char val=0;
+
+	ReadI2C(AGC1_CONTROL, &val);
+
+	D("[tpa2028d_out_lim_show] val : %x \n",val);
+
+	return sprintf(buf, "AGC1_CONTROL : %x, pdata->agc_output_limiter_disable : %d\n", val, pdata->agc_output_limiter_disable);
+}
+
+static ssize_t
+tpa2028d_fixed_gain_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct audio_amp_platform_data *pdata = amp_data->pdata;
+	int val;
+
+	if (sscanf(buf, "%d", &val) != 1)
+		return -EINVAL;
+
+	pdata->agc_fixed_gain = val;
+	return count;
+}
+
+static ssize_t
+tpa2028d_fixed_gain_show(struct device *dev, struct device_attribute *attr,   char *buf)
+{
+	struct audio_amp_platform_data *pdata = amp_data->pdata;
+	char val=0;
+
+	ReadI2C(AGC_FIXED_GAIN_CONTROL, &val);
+
+	D("[tpa2028d_fixed_gain_show] val : %x \n",val);
+
+	return sprintf(buf, "fixed_gain : %x, pdata->agc_fixed_gain : %d\n", val, pdata->agc_fixed_gain);
+}
+
+static struct device_attribute tpa2028d_device_attrs[] = {
+	__ATTR(comp_rate,  S_IRUGO | S_IWUSR, tpa2028d_comp_rate_show, tpa2028d_comp_rate_store),
+	__ATTR(out_lim, S_IRUGO | S_IWUSR, tpa2028d_out_lim_show, tpa2028d_out_lim_store),
+	__ATTR(fixed_gain, S_IRUGO | S_IWUSR, tpa2028d_fixed_gain_show, tpa2028d_fixed_gain_store),
+};
+
+static int tpa2028d_amp_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct amp_config *data;
+	struct audio_amp_platform_data *pdata;
+	struct i2c_adapter *adapter = client->adapter;
+	int err, i;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
+		err = -EOPNOTSUPP;
+		return err;
+	}
+
+	pdata = client->dev.platform_data;
+	if (pdata == NULL) {
+		D("platform data is null\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(struct amp_config), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	amp_data = data;
+	amp_data->pdata = pdata;
+	data->client = client;
+	i2c_set_clientdata(client, data);
+
+	set_amp_gain(SPK_OFF);
+
+	for (i = 0; i < ARRAY_SIZE(tpa2028d_device_attrs); i++) {
+		err = device_create_file(&client->dev, &tpa2028d_device_attrs[i]);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+static int tpa2028d_amp_remove(struct i2c_client *client)
+{
+	struct amp_config *data = i2c_get_clientdata(client);
+	kfree(data);
+
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+
+static struct i2c_device_id tpa2028d_amp_idtable[] = {
+	{ "tpa2028d_amp", 1 },
+};
+
+static struct i2c_driver tpa2028d_amp_driver = {
+	.probe = tpa2028d_amp_probe,
+	.remove = tpa2028d_amp_remove,
+	.id_table = tpa2028d_amp_idtable,
+	.driver = {
+		.name = MODULE_NAME,
+	},
+};
+
+
+static int __init tpa2028d_amp_init(void)
+{
+	return i2c_add_driver(&tpa2028d_amp_driver);
+}
+
+static void __exit tpa2028d_amp_exit(void)
+{
+	return i2c_del_driver(&tpa2028d_amp_driver);
+}
+
+module_init(tpa2028d_amp_init);
+module_exit(tpa2028d_amp_exit);
+
+MODULE_DESCRIPTION("TPA2028D Amp Control");
+MODULE_AUTHOR("Kim EunHye <ehgrace.kim@lge.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/msm/apq8064.c b/sound/soc/msm/apq8064.c
index 2c90cb1..00e038a 100644
--- a/sound/soc/msm/apq8064.c
+++ b/sound/soc/msm/apq8064.c
@@ -28,6 +28,10 @@
 #include "msm-pcm-routing.h"
 #include "../codecs/wcd9310.h"
 
+#ifdef CONFIG_SND_SOC_TPA2028D
+#include <sound/tpa2028d.h>
+#endif
+
 /* 8064 machine driver */
 
 #define PM8921_GPIO_BASE		NR_GPIO_IRQS
@@ -224,10 +228,14 @@
 			(msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) ||
 				(msm_ext_top_spk_pamp & TOP_SPK_AMP)) {
 
+#ifdef CONFIG_SND_SOC_TPA2028D
+			set_amp_gain(SPK_ON);
+#else
 			msm_enable_ext_spk_amp_gpio(top_spk_pamp_gpio);
 			pr_debug("%s: sleeping 4 ms after turning on "
 				" external Top Speaker Ampl\n", __func__);
 			usleep_range(4000, 4000);
+#endif
 		}
 	} else  {
 
@@ -272,6 +280,10 @@
 		if (msm_ext_top_spk_pamp)
 			return;
 
+#ifdef CONFIG_SND_SOC_TPA2028D
+		set_amp_gain(SPK_OFF);
+		msm_ext_top_spk_pamp = 0;
+#else
 		gpio_direction_output(top_spk_pamp_gpio, 0);
 		gpio_free(top_spk_pamp_gpio);
 		msm_ext_top_spk_pamp = 0;
@@ -280,6 +292,7 @@
 				__func__);
 
 		usleep_range(4000, 4000);
+#endif
 	} else  {
 
 		pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n",
@@ -298,11 +311,17 @@
 		snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg");
 		snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos");
 		snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg");
+#ifdef CONFIG_SND_SOC_TPA2028D
+		snd_soc_dapm_enable_pin(dapm, "Ext Spk Top");
+#endif
 	} else {
 		snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos");
 		snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg");
 		snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos");
 		snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg");
+#ifdef CONFIG_SND_SOC_TPA2028D
+		snd_soc_dapm_disable_pin(dapm, "Ext Spk Top");
+#endif
 	}
 
 	snd_soc_dapm_sync(dapm);
@@ -495,13 +514,16 @@
 	{"HEADPHONE", NULL, "LDO_H"},
 
 	/* Speaker path */
+#ifdef CONFIG_SND_SOC_TPA2028D
+	{"Ext Spk Top", NULL, "LINEOUT1"},
+#else
 	{"Ext Spk Bottom Pos", NULL, "LINEOUT1"},
 	{"Ext Spk Bottom Neg", NULL, "LINEOUT3"},
 
 	{"Ext Spk Top Pos", NULL, "LINEOUT2"},
 	{"Ext Spk Top Neg", NULL, "LINEOUT4"},
 	{"Ext Spk Top", NULL, "LINEOUT5"},
-
+#endif
 	/************   Analog MIC Paths  ************/
 #ifdef CONFIG_SND_SOC_DUAL_AMIC
 	/* Handset Mic */
@@ -1149,10 +1171,14 @@
 			ARRAY_SIZE(apq8064_liquid_cdp_audio_map));
 	}
 
+#ifdef CONFIG_SND_SOC_TPA2028D
+	snd_soc_dapm_enable_pin(dapm, "Ext Spk Top");
+#else
 	snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos");
 	snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg");
 	snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos");
 	snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg");
+#endif
 
 	snd_soc_dapm_sync(dapm);