mfd: pm8xxx-spk: Add support for class D speaker amplifier driver

The driver adds support for configuring the following parameters for
external pmic speaker amp driver
1. Gain
2. Mute/Unmute
3. Speaker enable/Disable
The above operations are supported by driver by exported apis
from kernel space.The Machine driver from ALSA would use these
to configure speaker.

Change-Id: I9817f5d5c2952ca423b84f35162a842123e4d413
Signed-off-by: Asish Bhattacharya <asishb@codeaurora.org>
diff --git a/drivers/mfd/pm8xxx-spk.c b/drivers/mfd/pm8xxx-spk.c
new file mode 100644
index 0000000..297ddfa
--- /dev/null
+++ b/drivers/mfd/pm8xxx-spk.c
@@ -0,0 +1,279 @@
+/* Copyright (c) 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/stddef.h>
+#include <linux/debugfs.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/mfd/pm8xxx/spk.h>
+
+#define PM8XXX_SPK_CTL1_REG_OFF		0
+#define PM8XXX_SPK_TEST_REG_1_OFF	1
+#define PM8XXX_SPK_TEST_REG_2_OFF	2
+
+#define PM8XXX_SPK_BANK_SEL		4
+#define PM8XXX_SPK_BANK_WRITE		0x80
+#define PM8XXX_SPK_BANK_VAL_MASK	0xF
+
+#define BOOST_6DB_GAIN_EN_MASK		0x8
+#define VSEL_LD0_1P1			0x0
+#define VSEL_LD0_1P2			0x2
+#define VSEL_LD0_1P0			0x4
+
+#define PWM_EN_MASK			0xF
+#define PM8XXX_SPK_TEST_REG_1_BANKS	8
+#define PM8XXX_SPK_TEST_REG_2_BANKS	2
+
+#define PM8XXX_SPK_GAIN			0x5
+#define PM8XXX_ADD_EN			0x1
+
+struct pm8xxx_spk_chip {
+	struct list_head                        link;
+	struct pm8xxx_spk_platform_data		pdata;
+	struct device                           *dev;
+	enum pm8xxx_version                     version;
+	struct mutex				spk_mutex;
+	u16					base;
+	u16					end;
+};
+
+static struct pm8xxx_spk_chip *the_spk_chip;
+
+static inline bool spk_defined(void)
+{
+	if (the_spk_chip == NULL || IS_ERR(the_spk_chip))
+		return false;
+	return true;
+}
+
+static int pm8xxx_spk_bank_write(u16 reg, u16 bank, u8 val)
+{
+	int rc = 0;
+	u8 bank_val = PM8XXX_SPK_BANK_WRITE | (bank << PM8XXX_SPK_BANK_SEL);
+
+	bank_val |= (val & PM8XXX_SPK_BANK_VAL_MASK);
+	mutex_lock(&the_spk_chip->spk_mutex);
+	rc = pm8xxx_writeb(the_spk_chip->dev->parent, reg, bank_val);
+	if (rc)
+		pr_err("pm8xxx_writeb(): rc=%d\n", rc);
+	mutex_unlock(&the_spk_chip->spk_mutex);
+	return rc;
+}
+
+
+static int pm8xxx_spk_read(u16 addr)
+{
+	int rc = 0;
+	u8 val = 0;
+
+	mutex_lock(&the_spk_chip->spk_mutex);
+	rc = pm8xxx_readb(the_spk_chip->dev->parent,
+			the_spk_chip->base + addr, &val);
+	if (rc) {
+		pr_err("pm8xxx_spk_readb() failed: rc=%d\n", rc);
+		val = rc;
+	}
+	mutex_unlock(&the_spk_chip->spk_mutex);
+
+	return val;
+}
+
+static int pm8xxx_spk_write(u16 addr, u8 val)
+{
+	int rc = 0;
+
+	mutex_lock(&the_spk_chip->spk_mutex);
+	rc = pm8xxx_writeb(the_spk_chip->dev->parent,
+			the_spk_chip->base + addr, val);
+	if (rc)
+		pr_err("pm8xxx_writeb() failed: rc=%d\n", rc);
+	mutex_unlock(&the_spk_chip->spk_mutex);
+	return rc;
+}
+
+int pm8xxx_spk_mute(bool mute)
+{
+	u8 val = 0;
+	int ret = 0;
+	if (spk_defined() == false) {
+		pr_err("Invalid spk handle or no spk_chip\n");
+		return -ENODEV;
+	}
+
+	val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
+	if (val < 0)
+		return val;
+	val |= mute << 2;
+	ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_spk_mute);
+
+int pm8xxx_spk_gain(u8 gain)
+{
+	u8 val;
+	int ret = 0;
+
+	if (spk_defined() == false) {
+		pr_err("Invalid spk handle or no spk_chip\n");
+		return -ENODEV;
+	}
+
+	val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
+	if (val < 0)
+		return val;
+	val |= (gain << 4);
+	ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
+	if (!ret) {
+		pm8xxx_spk_bank_write(the_spk_chip->base
+			+ PM8XXX_SPK_TEST_REG_1_OFF,
+			0, BOOST_6DB_GAIN_EN_MASK | VSEL_LD0_1P2);
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_spk_gain);
+
+int pm8xxx_spk_enable(int enable)
+{
+	int val = 0;
+	u16 addr;
+	int ret = 0;
+
+	if (spk_defined() == false) {
+		pr_err("Invalid spk handle or no spk_chip\n");
+		return -ENODEV;
+	}
+
+	addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF;
+	val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
+	if (val < 0)
+		return val;
+	val |= (enable << 3);
+	ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
+	if (!ret)
+		ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_spk_enable);
+
+static int pm8xxx_spk_config(void)
+{
+	u16 addr;
+	int ret = 0;
+
+	if (spk_defined() == false) {
+		pr_err("Invalid spk handle or no spk_chip\n");
+		return -ENODEV;
+	}
+
+	addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF;
+	ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK & 0);
+	if (!ret)
+		ret = pm8xxx_spk_gain(PM8XXX_SPK_GAIN);
+	return ret;
+}
+
+static int __devinit pm8xxx_spk_probe(struct platform_device *pdev)
+{
+	const struct pm8xxx_spk_platform_data *pdata = pdev->dev.platform_data;
+	int ret = 0;
+
+	if (!pdata) {
+		pr_err("missing platform data\n");
+		return -EINVAL;
+	}
+
+	the_spk_chip = kzalloc(sizeof(struct pm8xxx_spk_chip), GFP_KERNEL);
+	if (the_spk_chip == NULL) {
+		pr_err("kzalloc() failed.\n");
+		return -ENOMEM;
+	}
+
+	mutex_init(&the_spk_chip->spk_mutex);
+
+	the_spk_chip->dev = &pdev->dev;
+	the_spk_chip->version = pm8xxx_get_version(the_spk_chip->dev->parent);
+	switch (pm8xxx_get_version(the_spk_chip->dev->parent)) {
+	case PM8XXX_VERSION_8038:
+		break;
+	default:
+		ret = -ENODEV;
+		goto err_handle;
+	}
+
+	memcpy(&(the_spk_chip->pdata), pdata,
+			sizeof(struct pm8xxx_spk_platform_data));
+
+	the_spk_chip->base = pdev->resource[0].start;
+	the_spk_chip->end = pdev->resource[0].end;
+
+	if (the_spk_chip->pdata.spk_add_enable) {
+		int val;
+		val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
+		if (val < 0) {
+			ret = val;
+			goto err_handle;
+		}
+		val |= (the_spk_chip->pdata.spk_add_enable & PM8XXX_ADD_EN);
+		ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
+		if (ret < 0)
+			goto err_handle;
+	}
+	return pm8xxx_spk_config();
+err_handle:
+	pr_err("pm8xxx_spk_probe failed."
+			"Audio unavailable on speaker.\n");
+	mutex_destroy(&the_spk_chip->spk_mutex);
+	kfree(the_spk_chip);
+	return ret;
+}
+
+static int __devexit pm8xxx_spk_remove(struct platform_device *pdev)
+{
+	if (spk_defined() == false) {
+		pr_err("Invalid spk handle or no spk_chip\n");
+		return -ENODEV;
+	}
+	mutex_destroy(&the_spk_chip->spk_mutex);
+	kfree(the_spk_chip);
+	return 0;
+}
+
+static struct platform_driver pm8xxx_spk_driver = {
+	.probe		= pm8xxx_spk_probe,
+	.remove		= __devexit_p(pm8xxx_spk_remove),
+	.driver		= {
+		.name = PM8XXX_SPK_DEV_NAME,
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init pm8xxx_spk_init(void)
+{
+	return platform_driver_register(&pm8xxx_spk_driver);
+}
+subsys_initcall(pm8xxx_spk_init);
+
+static void __exit pm8xxx_spk_exit(void)
+{
+	platform_driver_unregister(&pm8xxx_spk_driver);
+}
+module_exit(pm8xxx_spk_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PM8XXX SPK driver");
+MODULE_ALIAS("platform:" PM8XXX_SPK_DEV_NAME);