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);