summaryrefslogtreecommitdiff
path: root/drivers/mfd/tlv320aic3256-core.c
diff options
context:
space:
mode:
authorRavindra Lokhande <rlokhande@nvidia.com>2013-03-19 18:56:02 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2013-09-14 13:04:36 -0700
commit3900262fa3136605796b70ca6fa5ba2f1ee53d39 (patch)
treeeb2bbe362a34b9976b0aca533a39d43bfe6c6273 /drivers/mfd/tlv320aic3256-core.c
parentc327b463ace7b079339cb273e2ebe61026e76f53 (diff)
drivers: mfd: Add support for TI aic325x codec
AIC3033 is register compatible with aic3206 which is strip down version of aic3256. This code is from TI Change-Id: I15b6ac2ae52f5b39e5036a5028cf537c93b66134 Signed-off-by: Ravindra Lokhande <rlokhande@nvidia.com> Reviewed-on: http://git-master/r/210808 GVS: Gerrit_Virtual_Submit Reviewed-by: Scott Peterson <speterson@nvidia.com>
Diffstat (limited to 'drivers/mfd/tlv320aic3256-core.c')
-rw-r--r--drivers/mfd/tlv320aic3256-core.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/drivers/mfd/tlv320aic3256-core.c b/drivers/mfd/tlv320aic3256-core.c
new file mode 100644
index 000000000000..50dbfc85e756
--- /dev/null
+++ b/drivers/mfd/tlv320aic3256-core.c
@@ -0,0 +1,462 @@
+/*
+ * tlv320aic325x-core.c -- driver for TLV320AIC3XXX
+ *
+ * Author: Mukund Navada <navada@ti.com>
+ * Mehar Bajwa <mehar.bajwa@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/mfd/core.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/machine.h>
+#include <linux/gpio.h>
+
+#include <linux/mfd/tlv320aic325x-core.h>
+#include <linux/mfd/tlv320aic325x-registers.h>
+#include <linux/mfd/tlv320aic3256-registers.h>
+
+
+/**
+ * set_aic325x_book: change book which we have to write/read to.
+ *
+ * @aic325x: Device to write/read to.
+ * @book: Book to write/read to.
+ */
+int set_aic325x_book(struct aic325x *aic325x, int book)
+{
+ int ret = 0;
+ u8 page_buf[] = { 0x0, 0x0 };
+ u8 book_buf[] = { 0x7f, 0x0 };
+
+ ret = regmap_write(aic325x->regmap, page_buf[0], page_buf[1]);
+
+ if (ret < 0)
+ return ret;
+ book_buf[1] = book;
+ ret = regmap_write(aic325x->regmap, book_buf[0], book_buf[1]);
+
+ if (ret < 0)
+ return ret;
+ aic325x->book_no = book;
+ aic325x->page_no = 0;
+
+ return ret;
+}
+
+/**
+ * set_aic325x_page: change page which we have to write/read to.
+ *
+ * @aic325x: Device to write/read to.
+ * @page: Book to write/read to.
+ */
+int set_aic325x_page(struct aic325x *aic325x, int page)
+{
+ int ret = 0;
+ u8 page_buf[] = { 0x0, 0x0 };
+
+ page_buf[1] = page;
+ ret = regmap_write(aic325x->regmap, page_buf[0], page_buf[1]);
+
+ if (ret < 0)
+ return ret;
+ aic325x->page_no = page;
+ return ret;
+}
+/**
+ * aic325x_reg_read: Read a single TLV320AIC3xxx register.
+ *
+ * @aic325x: Device to read from.
+ * @reg: Register to read.
+ */
+int aic325x_reg_read(struct aic325x *aic325x, unsigned int reg)
+{
+ unsigned int val;
+ int ret;
+ union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) &reg;
+ u8 book, page, offset;
+
+ page = aic_reg->aic325x_register.page;
+ book = aic_reg->aic325x_register.book;
+ offset = aic_reg->aic325x_register.offset;
+
+ mutex_lock(&aic325x->io_lock);
+ if (aic325x->book_no != book) {
+ ret = set_aic325x_book(aic325x, book);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+
+ if (aic325x->page_no != page) {
+ ret = set_aic325x_page(aic325x, page);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ ret = regmap_read(aic325x->regmap, offset, &val);
+ mutex_unlock(&aic325x->io_lock);
+
+ if (ret < 0)
+ return ret;
+ else
+ return val;
+}
+EXPORT_SYMBOL_GPL(aic325x_reg_read);
+
+/**
+ * aic325x_bulk_read: Read multiple TLV320AIC3XXX registers
+ *
+ * @aic325x: Device to read from
+ * @reg: First register
+ * @count: Number of registers
+ * @buf: Buffer to fill. The data will be returned big endian.
+ */
+int aic325x_bulk_read(struct aic325x *aic325x, unsigned int reg,
+ int count, u8 *buf)
+{
+ int ret;
+ union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) &reg;
+ u8 book, page, offset;
+
+ page = aic_reg->aic325x_register.page;
+ book = aic_reg->aic325x_register.book;
+ offset = aic_reg->aic325x_register.offset;
+
+ mutex_lock(&aic325x->io_lock);
+ if (aic325x->book_no != book) {
+ ret = set_aic325x_book(aic325x, book);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+
+ if (aic325x->page_no != page) {
+ ret = set_aic325x_page(aic325x, page);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ ret = regmap_bulk_read(aic325x->regmap, offset, buf, count);
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(aic325x_bulk_read);
+
+/**
+ * aic325x_reg_write: Write a single TLV320AIC3XXX register.
+ *
+ * @aic325x: Device to write to.
+ * @reg: Register to write to.
+ * @val: Value to write.
+ */
+int aic325x_reg_write(struct aic325x *aic325x, unsigned int reg,
+ unsigned char val)
+{
+ union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) &reg;
+ int ret = 0;
+ u8 page, book, offset;
+
+ page = aic_reg->aic325x_register.page;
+ book = aic_reg->aic325x_register.book;
+ offset = aic_reg->aic325x_register.offset;
+
+ mutex_lock(&aic325x->io_lock);
+ if (book != aic325x->book_no) {
+ ret = set_aic325x_book(aic325x, book);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ if (page != aic325x->page_no) {
+ ret = set_aic325x_page(aic325x, page);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ ret = regmap_write(aic325x->regmap, offset, val);
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+
+}
+EXPORT_SYMBOL_GPL(aic325x_reg_write);
+
+/**
+ * aic325x_bulk_write: Write multiple TLV320AIC3XXX registers
+ *
+ * @aic325x: Device to write to
+ * @reg: First register
+ * @count: Number of registers
+ * @buf: Buffer to write from. Data must be big-endian formatted.
+ */
+int aic325x_bulk_write(struct aic325x *aic325x, unsigned int reg,
+ int count, const u8 *buf)
+{
+ union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) &reg;
+ int ret = 0;
+ u8 page, book, offset;
+
+ page = aic_reg->aic325x_register.page;
+ book = aic_reg->aic325x_register.book;
+ offset = aic_reg->aic325x_register.offset;
+
+ mutex_lock(&aic325x->io_lock);
+ if (book != aic325x->book_no) {
+ ret = set_aic325x_book(aic325x, book);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ if (page != aic325x->page_no) {
+ ret = set_aic325x_page(aic325x, page);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ ret = regmap_raw_write(aic325x->regmap, offset, buf, count);
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(aic325x_bulk_write);
+
+/**
+ * aic325x_set_bits: Set the value of a bitfield in a TLV320AIC3XXX register
+ *
+ * @aic325x: Device to write to.
+ * @reg: Register to write to.
+ * @mask: Mask of bits to set.
+ * @val: Value to set (unshifted)
+ */
+int aic325x_set_bits(struct aic325x *aic325x, unsigned int reg,
+ unsigned char mask, unsigned char val)
+{
+ union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) &reg;
+ int ret = 0;
+ u8 page, book, offset;
+
+ page = aic_reg->aic325x_register.page;
+ book = aic_reg->aic325x_register.book;
+ offset = aic_reg->aic325x_register.offset;
+
+ mutex_lock(&aic325x->io_lock);
+ if (book != aic325x->book_no) {
+ ret = set_aic325x_book(aic325x, book);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ if (page != aic325x->page_no) {
+ ret = set_aic325x_page(aic325x, page);
+ if (ret < 0) {
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+ }
+ }
+ ret = regmap_update_bits(aic325x->regmap, offset, mask, val);
+ mutex_unlock(&aic325x->io_lock);
+ return ret;
+
+}
+EXPORT_SYMBOL_GPL(aic325x_set_bits);
+
+/**
+ * aic325x_wait_bits: wait for a value of a bitfield in a TLV320AIC3XXX register
+ *
+ * @aic325x: Device to write to.
+ * @reg: Register to write to.
+ * @mask: Mask of bits to set.
+ * @val: Value to set (unshifted)
+ * @sleep: delay value in each iteration in micro seconds
+ * @count: iteration count for timeout
+ */
+int aic325x_wait_bits(struct aic325x *aic325x, unsigned int reg,
+ unsigned char mask, unsigned char val, int sleep,
+ int counter)
+{
+ int status;
+ int timeout = sleep * counter;
+
+ status = aic325x_reg_read(aic325x, reg);
+ while (((status & mask) != val) && counter) {
+ usleep_range(sleep, sleep + 500);
+ status = aic325x_reg_read(aic325x, reg);
+ counter--;
+ };
+ if (!counter)
+ dev_err(aic325x->dev,
+ "wait_bits timedout (%d millisecs). lastval 0x%x\n",
+ timeout, status);
+ return counter;
+}
+EXPORT_SYMBOL_GPL(aic325x_wait_bits);
+
+static struct mfd_cell aic3262_devs[] = {
+ {
+ .name = "tlv320aic3262-codec",
+ },
+ {
+ .name = "tlv320aic3262-gpio",
+ },
+};
+static struct mfd_cell aic3256_devs[] = {
+ {
+ .name = "tlv320aic325x-codec",
+ },
+ {
+ .name = "tlv320aic3256-gpio",
+ },
+};
+
+/**
+ * Instantiate the generic non-control parts of the device.
+ */
+int aic325x_device_init(struct aic325x *aic325x)
+{
+ const char *devname;
+ int ret, i;
+ u8 reset = 1;
+
+ dev_info(aic325x->dev, "aic325x_device_init beginning\n");
+ mutex_init(&aic325x->io_lock);
+ dev_set_drvdata(aic325x->dev, aic325x);
+
+ if (dev_get_platdata(aic325x->dev))
+ memcpy(&aic325x->pdata, dev_get_platdata(aic325x->dev),
+ sizeof(aic325x->pdata));
+
+ /* GPIO reset for TLV320AIC3xxx codec */
+ if (aic325x->pdata.gpio_reset) {
+ ret = gpio_request_one(aic325x->pdata.gpio_reset,
+ GPIOF_DIR_OUT | GPIOF_INIT_LOW,
+ "aic325x-reset-pin");
+ if (ret != 0) {
+ dev_err(aic325x->dev, "not able to acquire gpio\n");
+ goto err_return;
+ }
+ }
+
+ /* run the codec through software reset */
+ ret = aic325x_reg_write(aic325x, AIC3XXX_RESET, reset);
+ if (ret < 0) {
+ dev_err(aic325x->dev, "Could not write to AIC3XXX register\n");
+ goto err_return;
+ }
+
+ usleep_range(10000, 10500);
+
+ ret = aic325x_reg_read(aic325x, AIC3XXX_DEVICE_ID);
+ if (ret < 0) {
+ dev_err(aic325x->dev, "Failed to read ID register\n");
+ goto err_return;
+ }
+ devname = "TLV320AIC3256";
+ aic325x->type = TLV320AIC3256;
+
+ /*If naudint is gpio convert it to irq number */
+ if (aic325x->pdata.gpio_irq == 1) {
+ aic325x->irq = gpio_to_irq(aic325x->pdata.naudint_irq);
+ gpio_request(aic325x->pdata.naudint_irq, "aic325x-gpio-irq");
+ gpio_direction_input(aic325x->pdata.naudint_irq);
+ } else {
+ aic325x->irq = aic325x->pdata.naudint_irq;
+ }
+
+ for (i = 0; i < aic325x->pdata.num_gpios; i++) {
+ aic325x_reg_write(aic325x, aic325x->pdata.gpio_defaults[i].reg,
+ aic325x->pdata.gpio_defaults[i].value);
+ }
+
+ if (aic325x->irq) {
+ ret = aic325x_irq_init(aic325x);
+ if (ret < 0)
+ goto err_irq;
+ }
+
+
+ dev_info(aic325x->dev, "%s revision %c\n", devname, 'D' + ret);
+
+ switch (aic325x->type) {
+ case TLV320AIC3266:
+ case TLV320AIC3262:
+ ret = mfd_add_devices(aic325x->dev, -1, aic3262_devs,
+ ARRAY_SIZE(aic3262_devs), NULL, 0, NULL);
+ break;
+ case TLV320AIC3256:
+ ret = mfd_add_devices(aic325x->dev, -1, aic3256_devs,
+ ARRAY_SIZE(aic3256_devs), NULL, 0, NULL);
+ break;
+ default:
+ dev_err(aic325x->dev, "unable to recognize codec\n");
+ break;
+ }
+ if (ret != 0) {
+ dev_err(aic325x->dev, "Failed to add children: %d\n", ret);
+ goto err_mfd;
+ }
+ dev_info(aic325x->dev, "aic325x_device_init added mfd devices\n");
+
+ return 0;
+
+err_mfd:
+
+ aic325x_irq_exit(aic325x);
+err_irq:
+
+ if (aic325x->pdata.gpio_irq)
+ gpio_free(aic325x->pdata.naudint_irq);
+err_return:
+
+ if (aic325x->pdata.gpio_reset)
+ gpio_free(aic325x->pdata.gpio_reset);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(aic325x_device_init);
+
+void aic325x_device_exit(struct aic325x *aic325x)
+{
+
+ mfd_remove_devices(aic325x->dev);
+ aic325x_irq_exit(aic325x);
+
+ if (aic325x->pdata.gpio_irq)
+ gpio_free(aic325x->pdata.naudint_irq);
+ if (aic325x->pdata.gpio_reset)
+ gpio_free(aic325x->pdata.gpio_reset);
+
+}
+EXPORT_SYMBOL_GPL(aic325x_device_exit);
+
+MODULE_AUTHOR("Mukund Navada <navada@ti.comm>");
+MODULE_AUTHOR("Mehar Bajwa <mehar.bajwa@ti.com>");
+MODULE_DESCRIPTION("Core support for the TLV320AIC3XXX audio CODEC");
+MODULE_LICENSE("GPL");