ASoC: tpa2051d3: Add TI tpa2051d3 amp for HTC devices
Change-Id: I96c1713e80e408a8f5efdd4385db1d711697ae52
diff --git a/include/sound/tpa2051d3.h b/include/sound/tpa2051d3.h
new file mode 100644
index 0000000..b12a990
--- /dev/null
+++ b/include/sound/tpa2051d3.h
@@ -0,0 +1,58 @@
+/*
+ * Definitions for tpa2051d3 speaker amp chip.
+ */
+#ifndef TPA2051D3_H
+#define TPA2051D3_H
+
+#include <linux/ioctl.h>
+
+#define TPA2051D3_I2C_NAME "tpa2051d3"
+#define SPKR_OUTPUT 0
+#define HEADSET_OUTPUT 1
+#define DUAL_OUTPUT 2
+#define HANDSET_OUTPUT 3
+#define LINEOUT_OUTPUT 4
+#define MODE_CMD_LEM 9
+struct tpa2051d3_platform_data {
+ uint32_t gpio_tpa2051_spk_en;
+ unsigned char spkr_cmd[7];
+ unsigned char hsed_cmd[7];
+ unsigned char rece_cmd[7];
+ /* for spk enable gpio on cpu */
+ uint32_t gpio_tpa2051_spk_en_cpu;
+};
+
+struct tpa2051_config_data {
+ unsigned int data_len;
+ unsigned int mode_num;
+ unsigned char *cmd_data; /* [mode][mode_kind][reserve][cmds..] */
+};
+
+enum TPA2051_Mode {
+ TPA2051_MODE_OFF,
+ TPA2051_MODE_PLAYBACK_SPKR,
+ TPA2051_MODE_PLAYBACK_HEADSET,
+ TPA2051_MODE_RING,
+ TPA2051_MODE_VOICECALL_SPKR,
+ TPA2051_MODE_VOICECALL_HEADSET,
+ TPA2051_MODE_FM_SPKR,
+ TPA2051_MODE_FM_HEADSET,
+ TPA2051_MODE_PLAYBACK_HANDSET,
+ TPA2051_MODE_VOICECALL_HANDSET,
+ TPA2051_MODE_LINEOUT,
+ TPA2051_MAX_MODE
+};
+#define TPA2051_IOCTL_MAGIC 'a'
+#define TPA2051_SET_CONFIG _IOW(TPA2051_IOCTL_MAGIC, 0x01, unsigned)
+#define TPA2051_READ_CONFIG _IOW(TPA2051_IOCTL_MAGIC, 0x02, unsigned)
+#define TPA2051_SET_MODE _IOW(TPA2051_IOCTL_MAGIC, 0x03, unsigned)
+#define TPA2051_SET_PARAM _IOW(TPA2051_IOCTL_MAGIC, 0x04, unsigned)
+#define TPA2051_WRITE_REG _IOW(TPA2051_IOCTL_MAGIC, 0x07, unsigned)
+
+void set_speaker_amp(int on);
+void set_headset_amp(int on);
+void set_speaker_headset_amp(int on);
+void set_handset_amp(int on);
+void set_usb_audio_amp(int on);
+#endif
+
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 0e99137..bb883af 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -454,3 +454,9 @@
default n
help
Texas Instruments 3W Mono Class-D Audio Amplifier
+
+config SND_SOC_TPA2051D3
+ tristate "TI TPA2051D3 Speaker AMP Driver"
+ depends on I2C=y
+ help
+ TI TPA2051D3 Speaker AMP Driver implemented by HTC.
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index ec05d3c..91a5542 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -213,3 +213,4 @@
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
+obj-$(CONFIG_SND_SOC_TPA2051D3) += tpa2051d3.o
diff --git a/sound/soc/codecs/tpa2051d3.c b/sound/soc/codecs/tpa2051d3.c
new file mode 100644
index 0000000..2f73b3e
--- /dev/null
+++ b/sound/soc/codecs/tpa2051d3.c
@@ -0,0 +1,523 @@
+/* driver/i2c/chip/tap2051d3.c
+ *
+ * TI tpa2051d3 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/module.h>
+#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 <sound/tpa2051d3.h>
+#include <linux/mutex.h>
+
+#include <linux/gpio.h>
+#include <linux/mfd/pm8xxx/pm8921.h>
+
+
+#ifdef CONFIG_AMP_TPA2051D3_ON_GPIO
+#define DEBUG (1)
+#else
+#define DEBUG (0)
+#endif
+#define AMP_ON_CMD_LEN 7
+#define RETRY_CNT 5
+static struct i2c_client *this_client;
+static struct tpa2051d3_platform_data *pdata;
+static char *config_data;
+static int tpa2051_mode_cnt;
+struct mutex spk_amp_lock;
+static int tpa2051d3_opened;
+static int last_spkamp_state;
+static char SPK_AMP_ON[] =
+ {0x00, 0x82, 0x25, 0x57, 0x2D, 0xCD, 0x0D};
+static char HEADSET_AMP_ON[] =
+ {0x00, 0x8C, 0x25, 0x57, 0x73, 0x4D, 0x0D};
+static char RING_AMP_ON[] =
+ {0x00, 0x8E, 0x25, 0x57, 0x8D, 0xCD, 0x0D};
+static char HANDSET_AMP_ON[] =
+ {0x00, 0x82, 0x25, 0x57, 0x13, 0xCD, 0x0D};
+static char LINEOUT_AMP_ON[] =
+ {0x00, 0x8C, 0x25, 0x57, 0x73, 0x4D, 0x0D};
+static char AMP_0FF[] = {0x00, 0x90};
+
+static int tpa2051_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;
+
+ err = i2c_transfer(this_client->adapter, msg, 1);
+ if (err >= 0)
+ return 0;
+
+ return err;
+}
+
+static int tpa2051_i2c_write(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++) {
+ if (i == 2) /* According to tpa2051d3 Spec */
+ mdelay(1);
+ 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 tpa2051_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++) {
+ if (i == 2) /* According to tpa2051 Spec */
+ mdelay(1);
+ 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 tpa2051_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;
+ }
+
+#if DEBUG
+ {
+ int i = 0;
+ for (i = 0; i < length; i++)
+ pr_info("i2c_read %s: rx[%d] = %2x\n", __func__, i, \
+ rxData[i]);
+ }
+#endif
+
+ return 0;
+}
+
+static int tpa2051d3_open(struct inode *inode, struct file *file)
+{
+ int rc = 0;
+
+ mutex_lock(&spk_amp_lock);
+
+ if (tpa2051d3_opened) {
+ pr_err("%s: busy\n", __func__);
+ rc = -EBUSY;
+ goto done;
+ }
+ tpa2051d3_opened = 1;
+done:
+ mutex_unlock(&spk_amp_lock);
+ return rc;
+}
+
+static int tpa2051d3_release(struct inode *inode, struct file *file)
+{
+ mutex_lock(&spk_amp_lock);
+ tpa2051d3_opened = 0;
+ mutex_unlock(&spk_amp_lock);
+
+ return 0;
+}
+void set_amp(int on, char *i2c_command)
+{
+ pr_info("%s: %d\n", __func__, on);
+ mutex_lock(&spk_amp_lock);
+ if (on && !last_spkamp_state) {
+ if (tpa2051_i2c_write(i2c_command, AMP_ON_CMD_LEN) == 0) {
+ last_spkamp_state = 1;
+ pr_info("%s: ON reg1=%x, reg2=%x\n",
+ __func__, i2c_command[1], i2c_command[2]);
+ }
+ } else if (!on && last_spkamp_state) {
+ if (tpa2051_i2c_write(AMP_0FF, sizeof(AMP_0FF)) == 0) {
+ last_spkamp_state = 0;
+ pr_debug("%s: OFF\n", __func__);
+ }
+ }
+ mutex_unlock(&spk_amp_lock);
+}
+
+void set_speaker_amp(int on)
+{
+ set_amp(on, SPK_AMP_ON);
+}
+
+void set_headset_amp(int on)
+{
+ set_amp(on, HEADSET_AMP_ON);
+}
+
+void set_speaker_headset_amp(int on)
+{
+ set_amp(on, RING_AMP_ON);
+}
+
+void set_handset_amp(int on)
+{
+ set_amp(on, HANDSET_AMP_ON);
+}
+
+void set_usb_audio_amp(int on)
+{
+ set_amp(on, LINEOUT_AMP_ON);
+}
+
+int update_amp_parameter(int mode)
+{
+ if (mode > tpa2051_mode_cnt)
+ return EINVAL;
+ if (*(config_data + mode * MODE_CMD_LEM + 1) == SPKR_OUTPUT)
+ memcpy(SPK_AMP_ON, config_data + mode * MODE_CMD_LEM + 2,
+ sizeof(SPK_AMP_ON));
+ else if (*(config_data + mode * MODE_CMD_LEM + 1) == HEADSET_OUTPUT)
+ memcpy(HEADSET_AMP_ON, config_data + mode * MODE_CMD_LEM + 2,
+ sizeof(HEADSET_AMP_ON));
+ else if (*(config_data + mode * MODE_CMD_LEM + 1) == DUAL_OUTPUT)
+ memcpy(RING_AMP_ON, config_data + mode * MODE_CMD_LEM + 2,
+ sizeof(RING_AMP_ON));
+ else if (*(config_data + mode * MODE_CMD_LEM + 1) == HANDSET_OUTPUT)
+ memcpy(HANDSET_AMP_ON, config_data + mode * MODE_CMD_LEM + 2,
+ sizeof(HANDSET_AMP_ON));
+ else if (*(config_data + mode * MODE_CMD_LEM + 1) == LINEOUT_OUTPUT)
+ memcpy(LINEOUT_AMP_ON, config_data + mode * MODE_CMD_LEM + 2,
+ sizeof(LINEOUT_AMP_ON));
+ else {
+ pr_info("[AUD] wrong mode id %d\n", mode);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+static long tpa2051d3_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};
+ unsigned char spk_cfg[8];
+ unsigned char reg_value[2];
+ struct tpa2051_config_data cfg;
+
+ switch (cmd) {
+ case TPA2051_WRITE_REG:
+ pr_info("%s: TPA2051_WRITE_REG\n", __func__);
+ mutex_lock(&spk_amp_lock);
+ if (!last_spkamp_state) {
+ /* According to tpa2051d3 Spec */
+ 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 = tpa2051_write_reg(reg_value[0], reg_value[1]);
+
+err1:
+ mutex_unlock(&spk_amp_lock);
+ break;
+ case TPA2051_SET_CONFIG:
+ if (copy_from_user(spk_cfg, argp, sizeof(spk_cfg)))
+ return -EFAULT;
+ if (spk_cfg[0] == SPKR_OUTPUT)
+ memcpy(SPK_AMP_ON, spk_cfg + 1,
+ sizeof(SPK_AMP_ON));
+ else if (spk_cfg[0] == HEADSET_OUTPUT)
+ memcpy(HEADSET_AMP_ON, spk_cfg + 1,
+ sizeof(HEADSET_AMP_ON));
+ else if (spk_cfg[0] == DUAL_OUTPUT)
+ memcpy(RING_AMP_ON, spk_cfg + 1,
+ sizeof(RING_AMP_ON));
+ else if (spk_cfg[0] == LINEOUT_OUTPUT)
+ memcpy(LINEOUT_AMP_ON, spk_cfg + 1,
+ sizeof(LINEOUT_AMP_ON));
+ else
+ return -EINVAL;
+ break;
+ case TPA2051_READ_CONFIG:
+ mutex_lock(&spk_amp_lock);
+ if (!last_spkamp_state) {
+ /* According to tpa2051d3 Spec */
+ mdelay(30);
+ }
+
+ rc = tpa2051_i2c_write_for_read(reg_idx, sizeof(reg_idx));
+ if (rc < 0)
+ goto err2;
+
+ rc = tpa2051_i2c_read(tmp, sizeof(tmp));
+ if (rc < 0)
+ goto err2;
+
+ if (copy_to_user(argp, &tmp, sizeof(tmp)))
+ rc = -EFAULT;
+err2:
+ mutex_unlock(&spk_amp_lock);
+ break;
+ case TPA2051_SET_MODE:
+ if (copy_from_user(&modeid, argp, sizeof(modeid)))
+ return -EFAULT;
+
+ if (modeid > tpa2051_mode_cnt || modeid <= 0) {
+ pr_err("unsupported tpa2051 mode %d\n", modeid);
+ return -EINVAL;
+ }
+ rc = update_amp_parameter(modeid);
+ pr_info("set tpa2051 mode to %d\n", modeid);
+ break;
+ case TPA2051_SET_PARAM:
+ cfg.cmd_data = 0;
+ tpa2051_mode_cnt = 0;
+ if (copy_from_user(&cfg, argp, sizeof(cfg))) {
+ pr_err("%s: copy from user failed.\n", __func__);
+ return -EFAULT;
+ }
+
+ if (cfg.data_len <= 0) {
+ pr_err("%s: invalid data length %d\n",
+ __func__, cfg.data_len);
+ return -EINVAL;
+ }
+ if (config_data == NULL)
+ config_data = kmalloc(cfg.data_len, GFP_KERNEL);
+ if (!config_data) {
+ pr_err("%s: out of memory\n", __func__);
+ return -ENOMEM;
+ }
+ if (copy_from_user(config_data, cfg.cmd_data, cfg.data_len)) {
+ pr_err("%s: copy data from user failed.\n", __func__);
+ kfree(config_data);
+ config_data = NULL;
+ return -EFAULT;
+ }
+ tpa2051_mode_cnt = cfg.mode_num;
+ pr_info("%s: update tpa2051 i2c commands #%d success.\n",
+ __func__, cfg.data_len);
+ /* update default paramater from csv*/
+ update_amp_parameter(TPA2051_MODE_PLAYBACK_SPKR);
+ update_amp_parameter(TPA2051_MODE_PLAYBACK_HEADSET);
+ update_amp_parameter(TPA2051_MODE_RING);
+ update_amp_parameter(TPA2051_MODE_PLAYBACK_HANDSET);
+ update_amp_parameter(TPA2051_MODE_LINEOUT);
+ rc = 0;
+ break;
+ default:
+ pr_err("%s: Invalid command\n", __func__);
+ rc = -EINVAL;
+ break;
+ }
+ return rc;
+}
+
+static struct file_operations tpa2051d3_fops = {
+ .owner = THIS_MODULE,
+ .open = tpa2051d3_open,
+ .release = tpa2051d3_release,
+ .unlocked_ioctl = tpa2051d3_ioctl,
+};
+
+static struct miscdevice tpa2051d3_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "tpa2051d3",
+ .fops = &tpa2051d3_fops,
+};
+
+int tpa2051d3_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ int ret = 0;
+
+ pdata = client->dev.platform_data;
+
+ if (pdata == NULL) {
+ 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;
+ }
+
+ ret = misc_register(&tpa2051d3_device);
+ if (ret) {
+ pr_err("%s: tpa2051d3_device register failed\n", __func__);
+ goto err_free_gpio_all;
+ }
+
+ if (pdata->spkr_cmd[1] != 0) /* path id != 0 */
+ memcpy(SPK_AMP_ON, pdata->spkr_cmd, sizeof(SPK_AMP_ON));
+ if (pdata->hsed_cmd[1] != 0)
+ memcpy(HEADSET_AMP_ON, pdata->hsed_cmd, sizeof(HEADSET_AMP_ON));
+ if (pdata->rece_cmd[1] != 0)
+ memcpy(HANDSET_AMP_ON, pdata->rece_cmd, sizeof(HANDSET_AMP_ON));
+
+ return 0;
+
+err_free_gpio_all:
+ return ret;
+err_alloc_data_failed:
+ return ret;
+}
+
+static int tpa2051d3_remove(struct i2c_client *client)
+{
+ struct tpa2051d3_platform_data *p2051data = i2c_get_clientdata(client);
+ kfree(p2051data);
+
+ return 0;
+}
+
+static int tpa2051d3_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ return 0;
+}
+
+static int tpa2051d3_resume(struct i2c_client *client)
+{
+ return 0;
+}
+
+static const struct i2c_device_id tpa2051d3_id[] = {
+ { TPA2051D3_I2C_NAME, 0 },
+ { }
+};
+
+static struct i2c_driver tpa2051d3_driver = {
+ .probe = tpa2051d3_probe,
+ .remove = tpa2051d3_remove,
+ .suspend = tpa2051d3_suspend,
+ .resume = tpa2051d3_resume,
+ .id_table = tpa2051d3_id,
+ .driver = {
+ .name = TPA2051D3_I2C_NAME,
+ },
+};
+
+static int __init tpa2051d3_init(void)
+{
+ pr_info("%s\n", __func__);
+ mutex_init(&spk_amp_lock);
+ return i2c_add_driver(&tpa2051d3_driver);
+}
+
+static void __exit tpa2051d3_exit(void)
+{
+ i2c_del_driver(&tpa2051d3_driver);
+}
+
+module_init(tpa2051d3_init);
+module_exit(tpa2051d3_exit);
+
+MODULE_DESCRIPTION("tpa2051d3 Speaker Amp driver");
+MODULE_LICENSE("GPL");