summaryrefslogtreecommitdiff
path: root/drivers/mfd
diff options
context:
space:
mode:
authorManoj Gangwal <mgangwal@nvidia.com>2012-08-22 15:45:42 +0530
committerSimone Willett <swillett@nvidia.com>2012-08-27 19:09:03 -0700
commit838e65e50a1d0ad52b068ba26f178fac843a1dc3 (patch)
treeb1964032908e614f8391325716816f13ce5b30d5 /drivers/mfd
parente9d6b70e5987491b9ae033e0553ea4f13824ff7e (diff)
drivers: mfd: Add support for TI aic3262 driver
Bug 1034241 Change-Id: I5607d53cf0bdd25c5e2b8447cd7e676b64cd32a2 Signed-off-by: Manoj Gangwal <mgangwal@nvidia.com> Reviewed-on: http://git-master/r/125169 Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Lokesh Pathak <lpathak@nvidia.com>
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig5
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/tlv320aic3262-core.c885
-rw-r--r--drivers/mfd/tlv320aic3262-irq.c204
4 files changed, 1095 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index e31f7710b1e2..10dba0cbda97 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -270,6 +270,11 @@ config TWL6040_CORE
select MFD_CORE
default n
+config AIC3262_CODEC
+ bool
+ select MFD_CORE
+ default n
+
config MFD_STMPE
bool "Support STMicroelectronics STMPE"
depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 482cd278cb8d..c8dc50450219 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o
obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o
obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o
obj-$(CONFIG_TWL6040_CORE) += twl6040-core.o twl6040-irq.o
+obj-$(CONFIG_AIC3262_CODEC) += tlv320aic3262-core.o tlv320aic3262-irq.o
obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
diff --git a/drivers/mfd/tlv320aic3262-core.c b/drivers/mfd/tlv320aic3262-core.c
new file mode 100644
index 000000000000..7b61c7497a45
--- /dev/null
+++ b/drivers/mfd/tlv320aic3262-core.c
@@ -0,0 +1,885 @@
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.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/tlv320aic3262-core.h>
+#include <linux/mfd/tlv320aic3262-registers.h>
+#define DEBUG
+struct aic3262_gpio {
+ unsigned int reg;
+ u8 mask;
+ u8 shift;
+};
+struct aic3262_gpio aic3262_gpio_control[] = {
+ {
+ .reg = AIC3262_GPIO1_IO_CNTL,
+ .mask = AIC3262_GPIO_D6_D2,
+ .shift = AIC3262_GPIO_D2_SHIFT,
+ },
+ {
+ .reg = AIC3262_GPIO2_IO_CNTL,
+ .mask = AIC3262_GPIO_D6_D2,
+ .shift = AIC3262_GPIO_D2_SHIFT,
+ },
+ {
+ .reg = AIC3262_GPI1_EN,
+ .mask = AIC3262_GPI1_D2_D1,
+ .shift = AIC3262_GPIO_D1_SHIFT,
+ },
+ {
+ .reg = AIC3262_GPI2_EN,
+ .mask = AIC3262_GPI2_D5_D4,
+ .shift = AIC3262_GPIO_D4_SHIFT,
+ },
+ {
+ .reg = AIC3262_GPO1_OUT_CNTL,
+ .mask = AIC3262_GPO1_D4_D1,
+ .shift = AIC3262_GPIO_D1_SHIFT,
+ },
+};
+static int aic3262_read(struct aic3262 *aic3262, unsigned int reg,
+ int bytes, void *dest)
+{
+ int ret;
+ int i;
+ u8 *buf = dest;
+
+ BUG_ON(bytes <= 0);
+
+ ret = aic3262->read_dev(aic3262, reg, bytes, dest);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < bytes ; i++) {
+ dev_vdbg(aic3262->dev, "Read %04x from R%d(0x%x)\n",
+ buf[i], reg + i, reg + i);
+ }
+
+ return ret;
+}
+
+/**
+ * aic3262_reg_read: Read a single TLV320AIC3262 register.
+ *
+ * @aic3262: Device to read from.
+ * @reg: Register to read.
+ */
+int aic3262_reg_read(struct aic3262 *aic3262, unsigned int reg)
+{
+ unsigned char val;
+ int ret;
+
+ mutex_lock(&aic3262->io_lock);
+
+ ret = aic3262_read(aic3262, reg, 1, &val);
+
+ mutex_unlock(&aic3262->io_lock);
+
+ if (ret < 0)
+ return ret;
+ else
+ return val;
+}
+EXPORT_SYMBOL_GPL(aic3262_reg_read);
+
+/**
+ * aic3262_bulk_read: Read multiple TLV320AIC3262 registers
+ *
+ * @aic3262: Device to read from
+ * @reg: First register
+ * @count: Number of registers
+ * @buf: Buffer to fill. The data will be returned big endian.
+ */
+int aic3262_bulk_read(struct aic3262 *aic3262, unsigned int reg,
+ int count, u8 *buf)
+{
+ int ret;
+
+ mutex_lock(&aic3262->io_lock);
+
+ ret = aic3262_read(aic3262, reg, count, buf);
+
+ mutex_unlock(&aic3262->io_lock);
+
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(aic3262_bulk_read);
+
+static int aic3262_write(struct aic3262 *aic3262, unsigned int reg,
+ int bytes, const void *src)
+{
+ const u8 *buf = src;
+ int i;
+
+ BUG_ON(bytes <= 0);
+
+ for (i = 0; i < bytes ; i++) {
+ dev_vdbg(aic3262->dev, "Write %04x to R%d(0x%x)\n",
+ buf[i], reg + i, reg + i);
+ }
+
+ return aic3262->write_dev(aic3262, reg, bytes, src);
+}
+
+/**
+ * aic3262_reg_write: Write a single TLV320AIC3262 register.
+ *
+ * @aic3262: Device to write to.
+ * @reg: Register to write to.
+ * @val: Value to write.
+ */
+int aic3262_reg_write(struct aic3262 *aic3262, unsigned int reg,
+ unsigned char val)
+{
+ int ret;
+
+
+ mutex_lock(&aic3262->io_lock);
+
+ dev_dbg(aic3262->dev, "w 30 %x %x", reg, val);
+ ret = aic3262_write(aic3262, reg, 1, &val);
+
+ mutex_unlock(&aic3262->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(aic3262_reg_write);
+
+/**
+ * aic3262_bulk_write: Write multiple TLV320AIC3262 registers
+ *
+ * @aic3262: Device to write to
+ * @reg: First register
+ * @count: Number of registers
+ * @buf: Buffer to write from. Data must be big-endian formatted.
+ */
+int aic3262_bulk_write(struct aic3262 *aic3262, unsigned int reg,
+ int count, const u8 *buf)
+{
+ int ret;
+
+ mutex_lock(&aic3262->io_lock);
+
+ ret = aic3262_write(aic3262, reg, count, buf);
+
+ mutex_unlock(&aic3262->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(aic3262_bulk_write);
+
+/**
+ * aic3262_set_bits: Set the value of a bitfield in a TLV320AIC3262 register
+ *
+ * @aic3262: Device to write to.
+ * @reg: Register to write to.
+ * @mask: Mask of bits to set.
+ * @val: Value to set (unshifted)
+ */
+int aic3262_set_bits(struct aic3262 *aic3262, unsigned int reg,
+ unsigned char mask, unsigned char val)
+{
+ int ret;
+ u8 r;
+
+ mutex_lock(&aic3262->io_lock);
+
+ ret = aic3262_read(aic3262, reg, 1, &r);
+ if (ret < 0)
+ goto out;
+
+
+ r &= ~mask;
+ r |= (val & mask);
+
+ dev_dbg(aic3262->dev, "w 30 %x %x", reg, r);
+ ret = aic3262_write(aic3262, reg, 1, &r);
+
+out:
+ mutex_unlock(&aic3262->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(aic3262_set_bits);
+
+/**
+ * aic3262_wait_bits: wait for a value of a bitfield in a TLV320AIC3262 register
+ *
+ * @aic3262: Device to write to.
+ * @reg: Register to write to.
+ * @mask: Mask of bits to set.
+ * @val: Value to set (unshifted)
+ * @sleep: mdelay value in each iteration in milliseconds
+ * @count: iteration count for timeout
+ */
+int aic3262_wait_bits(struct aic3262 *aic3262, unsigned int reg,
+ unsigned char mask, unsigned char val, int sleep, int counter)
+{
+ int status;
+ int timeout = sleep*counter;
+
+ status = aic3262_reg_read(aic3262, reg);
+ while (((status & mask) != val) && counter) {
+ mdelay(sleep);
+ status = aic3262_reg_read(aic3262, reg);
+ counter--;
+ };
+ if (!counter)
+ dev_err(aic3262->dev,
+ "wait_bits timedout (%d millisecs). lastval 0x%x\n",
+ timeout, status);
+ return counter;
+}
+EXPORT_SYMBOL_GPL(aic3262_wait_bits);
+
+/* to be changed -- Mukund*/
+static struct resource aic3262_codec_resources[] = {
+ {
+ .start = AIC3262_IRQ_HEADSET_DETECT,
+ .end = AIC3262_IRQ_SPEAKER_OVER_TEMP,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource aic3262_gpio_resources[] = {
+ {
+ .start = AIC3262_GPIO1,
+ .end = AIC3262_GPO1,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct mfd_cell aic3262_devs[] = {
+ {
+ .name = "tlv320aic3262-codec",
+ .num_resources = ARRAY_SIZE(aic3262_codec_resources),
+ .resources = aic3262_codec_resources,
+ },
+
+ {
+ .name = "tlv320aic3262-gpio",
+ .num_resources = ARRAY_SIZE(aic3262_gpio_resources),
+ .resources = aic3262_gpio_resources,
+ .pm_runtime_no_callbacks = true,
+ },
+};
+
+
+#ifdef CONFIG_PM
+static int aic3262_suspend(struct device *dev)
+{
+ struct aic3262 *aic3262 = dev_get_drvdata(dev);
+
+ aic3262->suspended = true;
+
+ return 0;
+}
+
+static int aic3262_resume(struct device *dev)
+{
+ struct aic3262 *aic3262 = dev_get_drvdata(dev);
+
+
+ aic3262->suspended = false;
+
+ return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(aic3262_pm_ops, aic3262_suspend, aic3262_resume,
+ NULL);
+#endif
+
+
+/*
+ * Instantiate the generic non-control parts of the device.
+ */
+static int aic3262_device_init(struct aic3262 *aic3262, int irq)
+{
+ struct aic3262_pdata *pdata = aic3262->dev->platform_data;
+ const char *devname;
+ int ret, i;
+ u8 revID, pgID;
+ unsigned int naudint = 0;
+ u8 resetVal = 1;
+
+ mutex_init(&aic3262->io_lock);
+ dev_set_drvdata(aic3262->dev, aic3262);
+ if (pdata) {
+ if (pdata->gpio_reset) {
+ ret = gpio_request(pdata->gpio_reset,
+ "aic3262-reset-pin");
+ if (ret != 0) {
+ dev_err(aic3262->dev,
+ "Failed to reset aic3262 using gpio %d\n",
+ pdata->gpio_reset);
+ goto err_return;
+ }
+ gpio_direction_output(pdata->gpio_reset, 1);
+ mdelay(5);
+ gpio_direction_output(pdata->gpio_reset, 0);
+ mdelay(5);
+ gpio_direction_output(pdata->gpio_reset, 1);
+ mdelay(5);
+ }
+ }
+
+
+ /* run the codec through software reset */
+ ret = aic3262_reg_write(aic3262, AIC3262_RESET_REG, resetVal);
+ if (ret < 0) {
+ dev_err(aic3262->dev, "Could not write to AIC3262 register\n");
+ goto err_return;
+ }
+
+ mdelay(10);
+
+ ret = aic3262_reg_read(aic3262, AIC3262_REV_PG_ID);
+ if (ret < 0) {
+ dev_err(aic3262->dev, "Failed to read ID register\n");
+ goto err_return;
+ }
+ revID = (ret & AIC3262_REV_MASK) >> AIC3262_REV_SHIFT;
+ pgID = (ret & AIC3262_PG_MASK) >> AIC3262_PG_SHIFT;
+ switch (revID) {
+ case 3:
+ devname = "TLV320AIC3262";
+ if (aic3262->type != TLV320AIC3262)
+ dev_warn(aic3262->dev, "Device registered as type %d\n",
+ aic3262->type);
+ aic3262->type = TLV320AIC3262;
+ break;
+ case 1:
+ devname = "TLV320AIC3262";
+ if (aic3262->type != TLV320AIC3262)
+ dev_warn(aic3262->dev, "Device registered as type %d\n",
+ aic3262->type);
+ aic3262->type = TLV320AIC3262;
+ break;
+
+ default:
+ dev_err(aic3262->dev, "Device is not a TLV320AIC3262, ID is %x\n",
+ ret);
+ ret = -EINVAL;
+ goto err_return;
+
+ }
+
+ dev_info(aic3262->dev, "%s revision %c\n", devname, 'D' + ret);
+
+
+ if (pdata) {
+ if (pdata->gpio_irq == 1) {
+ naudint = gpio_to_irq(pdata->naudint_irq);
+ gpio_request(pdata->naudint_irq, "aic3262-gpio-irq");
+ gpio_direction_input(pdata->naudint_irq);
+ } else
+ naudint = pdata->naudint_irq;
+
+ aic3262->irq = naudint;
+ aic3262->irq_base = pdata->irq_base;
+ for (i = 0; i < AIC3262_NUM_GPIO; i++) {
+ if (pdata->gpio[i].used) {
+ /* Direction is input */
+ if (pdata->gpio[i].in) {
+ /* set direction to input for GPIO,
+ and enable for GPI */
+ aic3262_set_bits(aic3262,
+ aic3262_gpio_control[i].reg,
+ aic3262_gpio_control[i].mask,
+ 0x1 <<
+ aic3262_gpio_control[i].shift);
+
+ if (pdata->gpio[i].in_reg)
+ /* Some input modes, does not
+ need extra registers to be
+ written */
+ aic3262_set_bits(aic3262,
+ pdata->gpio[i].in_reg,
+ pdata->gpio[i].
+ in_reg_bitmask,
+ pdata->gpio[i].value <<
+ pdata->gpio[i].
+ in_reg_shift);
+ } else {
+ /* Direction si output */
+ aic3262_set_bits(aic3262,
+ aic3262_gpio_control[i].reg,
+ aic3262_gpio_control[i].mask,
+ pdata->gpio[i].value <<
+ aic3262_gpio_control[i].shift);
+ }
+ } else
+ aic3262_set_bits(aic3262,
+ aic3262_gpio_control[i].reg,
+ aic3262_gpio_control[i].mask, 0x0);
+ }
+ }
+
+ if (naudint) {
+ /* codec interrupt */
+ ret = aic3262_irq_init(aic3262);
+ if (ret)
+ goto err_irq;
+ }
+
+ ret = mfd_add_devices(aic3262->dev, -1,
+ aic3262_devs, ARRAY_SIZE(aic3262_devs),
+ NULL, 0);
+ if (ret != 0) {
+ dev_err(aic3262->dev, "Failed to add children: %d\n", ret);
+ goto err_irq;
+ }
+
+ pm_runtime_enable(aic3262->dev);
+ pm_runtime_resume(aic3262->dev);
+
+ return 0;
+
+err_irq:
+ aic3262_irq_exit(aic3262);
+err_return:
+ kfree(aic3262);
+ return ret;
+}
+
+static void aic3262_device_exit(struct aic3262 *aic3262)
+{
+ pm_runtime_disable(aic3262->dev);
+ mfd_remove_devices(aic3262->dev);
+ aic3262_irq_exit(aic3262);
+ kfree(aic3262);
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+
+static int aic3262_i2c_read_device(struct aic3262 *aic3262, unsigned int reg,
+ int bytes, void *dest)
+{
+ struct i2c_client *i2c = aic3262->control_data;
+ union aic326x_reg_union *aic_reg = (union aic326x_reg_union *) &reg;
+ char *value;
+ int ret;
+ u8 buf[2];
+ u8 page, book, offset;
+ page = aic_reg->aic326x_register.page;
+ book = aic_reg->aic326x_register.book;
+ offset = aic_reg->aic326x_register.offset;
+ if (aic3262->book_no != book) {
+ /* We should change to page 0.
+ Change the book by writing to offset 127 of page 0
+ Change the page back to whatever was set before change page */
+ buf[0] = 0x0;
+ buf[1] = 0x0;
+ ret = i2c_master_send(i2c, (unsigned char *)buf, 2);
+ if (ret < 0)
+ return ret;
+ if (ret != 2)
+ return -EIO;
+ buf[0] = 127;
+ buf[1] = book;
+ ret = i2c_master_send(i2c, (unsigned char *)buf, 2);
+ if (ret < 0)
+ return ret;
+ if (ret != 2)
+ return -EIO;
+ aic3262->book_no = book;
+ aic3262->page_no = 0x0;
+ }
+
+ if (aic3262->page_no != page) {
+ buf[0] = 0x0;
+ buf[1] = page;
+ ret = i2c_master_send(i2c, (unsigned char *) buf, 2);
+
+ if (ret < 0)
+ return ret;
+ if (ret != 2)
+ return -EIO;
+ aic3262->page_no = page;
+ }
+
+ /* Send the required offset */
+ buf[0] = offset ;
+ ret = i2c_master_send(i2c, (unsigned char *)buf, 1);
+ if (ret < 0)
+ return ret;
+ if (ret != 1)
+ return -EIO;
+
+ ret = i2c_master_recv(i2c, dest, bytes);
+ value = dest;
+ if (ret < 0)
+ return ret;
+ if (ret != bytes)
+ return -EIO;
+ return ret;
+}
+
+static int aic3262_i2c_write_device(struct aic3262 *aic3262, unsigned int reg,
+ int bytes, const void *src)
+{
+ struct i2c_client *i2c = aic3262->control_data;
+ int ret;
+
+ union aic326x_reg_union *aic_reg = (union aic326x_reg_union *) &reg;
+
+ u8 buf[2];
+ u8 write_buf[bytes + 1];
+ u8 page, book, offset;
+ page = aic_reg->aic326x_register.page;
+ book = aic_reg->aic326x_register.book;
+ offset = aic_reg->aic326x_register.offset;
+ if (aic3262->book_no != book) {
+ /* We should change to page 0.
+ Change the book by writing to offset 127 of page 0
+ Change the page back to whatever was set before change page*/
+ buf[0] = 0x0;
+ buf[1] = 0x0;
+ ret = i2c_master_send(i2c, (unsigned char *)buf, 2);
+
+ if (ret < 0)
+ return ret;
+ if (ret != 2)
+ return -EIO;
+ buf[0] = 127;
+ buf[1] = book;
+ ret = i2c_master_send(i2c, (unsigned char *)buf, 2);
+ if (ret < 0)
+ return ret;
+ if (ret != 2)
+ return -EIO;
+ aic3262->book_no = book;
+ aic3262->page_no = 0x0;
+ }
+
+ if (aic3262->page_no != page) {
+ buf[0] = 0x0;
+ buf[1] = page;
+ ret = i2c_master_send(i2c, (unsigned char *) buf, 2);
+ if (ret < 0)
+ return ret;
+ if (ret != 2)
+ return -EIO;
+ aic3262->page_no = page;
+ }
+ write_buf[0] = offset;
+ memcpy(&write_buf[1], src, bytes);
+ ret = i2c_master_send(i2c, write_buf, bytes + 1);
+ if (ret < 0)
+ return ret;
+ if (ret != (bytes + 1))
+ return -EIO;
+
+ return 0;
+}
+
+static int aic3262_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct aic3262 *aic3262;
+
+ aic3262 = kzalloc(sizeof(struct aic3262), GFP_KERNEL);
+ if (aic3262 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, aic3262);
+ aic3262->dev = &i2c->dev;
+ aic3262->control_data = i2c;
+ aic3262->read_dev = aic3262_i2c_read_device;
+ aic3262->write_dev = aic3262_i2c_write_device;
+ aic3262->type = id->driver_data;
+ aic3262->book_no = 255;
+ aic3262->page_no = 255;
+
+ return aic3262_device_init(aic3262, i2c->irq);
+}
+
+static int aic3262_i2c_remove(struct i2c_client *i2c)
+{
+ struct aic3262 *aic3262 = i2c_get_clientdata(i2c);
+
+ aic3262_device_exit(aic3262);
+
+ return 0;
+}
+
+static const struct i2c_device_id aic3262_i2c_id[] = {
+ { "tlv320aic3262", TLV320AIC3262 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aic3262_i2c_id);
+
+
+static struct i2c_driver aic3262_i2c_driver = {
+ .driver = {
+ .name = "tlv320aic3262",
+ .owner = THIS_MODULE,
+ .pm = &aic3262_pm_ops,
+ },
+ .probe = aic3262_i2c_probe,
+ .remove = aic3262_i2c_remove,
+ .id_table = aic3262_i2c_id,
+};
+
+static int __init aic3262_i2c_init(void)
+{
+ int ret;
+ ret = i2c_add_driver(&aic3262_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register aic3262 I2C driver: %d\n", ret);
+
+ return ret;
+}
+module_init(aic3262_i2c_init);
+
+static void __exit aic3262_i2c_exit(void)
+{
+ i2c_del_driver(&aic3262_i2c_driver);
+}
+module_exit(aic3262_i2c_exit);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+/* TODO: UGLY
+ * NVidia's CS differs from what TI requires on the SPI bus. So before
+ * we do any write/read we pull down the CS gpio :(
+ * The problem is in spi_read.
+ * Can we set the flag spi_transfer.cs_change during read so that CS is
+ * pulled low until the next transaction occurs
+ * (spi_read requires a spi_write followed by spi_read)
+ */
+#include <linux/gpio.h>
+#include "../../../arch/arm/mach-tegra/gpio-names.h"
+#include <linux/delay.h>
+#define SPI_CS TEGRA_GPIO_PX3
+#define CS(a) gpio_set_value(SPI_CS, a)
+void nvidia_spi_cs_en(bool stop)
+{
+ if (stop) {
+ CS(1);
+ udelay(1);
+ } else {
+ CS(0);
+ udelay(1);
+ }
+ return;
+}
+static int aic3262_spi_read_device(struct aic3262 *aic3262, unsigned int reg,
+ int bytes, void *dest)
+{
+ struct spi_device *spi = aic3262->control_data;
+ union aic326x_reg_union *aic_reg = (union aic326x_reg_union *) &reg;
+ u8 *write_read_buf;
+ unsigned int i;
+ unsigned int time;
+ unsigned int last_count;
+ unsigned int spi_read_bufsize = max(32, SMP_CACHE_BYTES)-1;
+ struct spi_message message;
+ struct spi_transfer x[2];
+ int ret;
+ u8 buf[2];
+ u8 page, book, offset;
+ page = aic_reg->aic326x_register.page;
+ book = aic_reg->aic326x_register.book;
+ offset = aic_reg->aic326x_register.offset;
+ if (aic3262->book_no != book) {
+ /* We should change to page 0.
+ Change the book by writing to offset 127 of page 0
+ Change the page back to whatever was set before change page */
+
+ buf[0] = 0x0;
+ buf[1] = 0x0;
+
+ nvidia_spi_cs_en(0);
+ ret = spi_write(spi, (unsigned char *)buf, 2);
+ nvidia_spi_cs_en(1);
+
+ if (ret < 0)
+ return ret;
+ buf[0] = (127 << 1) ;
+ buf[1] = book;
+
+ nvidia_spi_cs_en(0);
+ ret = spi_write(spi, (unsigned char *)buf, 2);
+ nvidia_spi_cs_en(1);
+
+ if (ret < 0)
+ return ret;
+ aic3262->book_no = book;
+ aic3262->page_no = 0x0;
+ }
+
+ if (aic3262->page_no != page) {
+ buf[0] = 0x0;
+ buf[1] = page;
+
+ nvidia_spi_cs_en(0);
+ ret = spi_write(spi, (unsigned char *)buf, 2);
+ nvidia_spi_cs_en(1);
+
+ if (ret < 0)
+ return ret;
+ aic3262->page_no = page;
+ }
+
+ buf[0] = (offset << 1) | (0x01) ;
+ memset(x, 0, sizeof x);
+ spi_message_init(&message);
+ x[0].len = 1;
+ x[0].tx_buf = buf;
+ x[1].len = bytes;
+ x[1].rx_buf = dest ;
+
+ spi_message_add_tail(&x[0], &message);
+ spi_message_add_tail(&x[1], &message);
+
+ nvidia_spi_cs_en(0);
+ ret = spi_sync(spi, &message);
+ nvidia_spi_cs_en(1);
+ if (ret < 0)
+ return ret;
+
+ return bytes;
+
+}
+/* NVidia's CS differs from what TI requires on the SPI bus. So before
+ * we do any write/read we pull down the CS gpio :(
+ */
+static int aic3262_spi_write_device(struct aic3262 *aic3262, unsigned int reg,
+ int bytes, const void *src)
+{
+ struct spi_device *spi = aic3262->control_data;
+ int ret;
+
+ union aic326x_reg_union *aic_reg = (union aic326x_reg_union *) &reg;
+
+ u8 buf[2];
+ u8 write_buf[bytes + 1];
+ u8 page, book, offset;
+ page = aic_reg->aic326x_register.page;
+ book = aic_reg->aic326x_register.book;
+ offset = aic_reg->aic326x_register.offset;
+ if (aic3262->book_no != book) {
+ /* We should change to page 0.
+ Change the book by writing to offset 127 of page 0
+ Change the page back to whatever was set before change page */
+
+ buf[0] = 0x0;
+ buf[1] = 0x0;
+
+ nvidia_spi_cs_en(0);
+ ret = spi_write(spi, (unsigned char *)buf, 2);
+ nvidia_spi_cs_en(1);
+
+ if (ret < 0)
+ return ret;
+ buf[0] = (127 << 1) ;
+ buf[1] = book;
+
+ nvidia_spi_cs_en(0);
+ ret = spi_write(spi, (unsigned char *)buf, 2);
+ nvidia_spi_cs_en(1);
+
+ if (ret < 0)
+ return ret;
+ aic3262->book_no = book;
+ aic3262->page_no = 0x0;
+ }
+
+ if (aic3262->page_no != page) {
+ buf[0] = 0x0;
+ buf[1] = page;
+ nvidia_spi_cs_en(0);
+ ret = spi_write(spi, (unsigned char *) buf, 2);
+ nvidia_spi_cs_en(1);
+ if (ret < 0)
+ return ret;
+ aic3262->page_no = page;
+ }
+ write_buf[0] = offset << 1 ;
+ memcpy(&write_buf[1], src, bytes);
+ nvidia_spi_cs_en(0);
+ ret = spi_write(spi, write_buf, bytes + 1);
+ nvidia_spi_cs_en(1);
+ if (ret < 0)
+ return ret;
+
+ return bytes;
+}
+
+static int aic3262_spi_probe(struct spi_device *spi)
+{
+ struct aic3262 *aic3262;
+
+ aic3262 = kzalloc(sizeof(struct aic3262), GFP_KERNEL);
+ if (aic3262 == NULL)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, aic3262);
+ aic3262->dev = &spi->dev;
+ aic3262->control_data = spi;
+ aic3262->read_dev = aic3262_spi_read_device;
+ aic3262->write_dev = aic3262_spi_write_device;
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_1;
+ spi->max_speed_hz = 4000*1000;
+ spi_setup(spi);
+
+ if (strcmp(spi->modalias, "tlv320aic3262") == 0)
+ aic3262->type = TLV320AIC3262;
+ aic3262->book_no = 255;
+ aic3262->page_no = 255;
+
+ return aic3262_device_init(aic3262, spi->irq);
+}
+
+static int aic3262_spi_remove(struct spi_device *spi)
+{
+ struct aic3262 *aic3262 = spi_get_drvdata(spi);
+
+ aic3262_device_exit(aic3262);
+
+ return 0;
+}
+
+static struct spi_driver aic3262_spi_driver = {
+ .driver = {
+ .name = "tlv320aic3262",
+ .owner = THIS_MODULE,
+ .pm = &aic3262_pm_ops,
+ },
+ .probe = aic3262_spi_probe,
+ .remove = aic3262_spi_remove,
+};
+
+static int __init aic3262_spi_init(void)
+{
+ int ret;
+ ret = spi_register_driver(&aic3262_spi_driver);
+ if (ret != 0)
+ pr_err("Failed to register aic3262 SPI driver: %d\n", ret);
+
+ return ret;
+}
+module_init(aic3262_spi_init);
+
+static void __exit aic3262_spi_exit(void)
+{
+ spi_unregister_driver(&aic3262_spi_driver);
+}
+module_exit(aic3262_spi_exit);
+#endif
+
+MODULE_DESCRIPTION("Core support for the TLV320AIC3262 audio CODEC");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mukund Navada <navada@ti.com>");
diff --git a/drivers/mfd/tlv320aic3262-irq.c b/drivers/mfd/tlv320aic3262-irq.c
new file mode 100644
index 000000000000..7e7a5499f3e5
--- /dev/null
+++ b/drivers/mfd/tlv320aic3262-irq.c
@@ -0,0 +1,204 @@
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/interrupt.h>
+
+#include <linux/mfd/tlv320aic3262-core.h>
+#include <linux/mfd/tlv320aic3262-registers.h>
+
+#include <linux/delay.h>
+struct aic3262_irq_data {
+ int mask;
+ int status;
+};
+
+static struct aic3262_irq_data aic3262_irqs[] = {
+ {
+ .mask = AIC3262_HEADSET_IN_MASK,
+ .status = AIC3262_HEADSET_PLUG_UNPLUG_INT,
+ },
+ {
+ .mask = AIC3262_BUTTON_PRESS_MASK,
+ .status = AIC3262_BUTTON_PRESS_INT,
+ },
+ {
+ .mask = AIC3262_DAC_DRC_THRES_MASK,
+ .status = AIC3262_LEFT_DRC_THRES_INT | AIC3262_RIGHT_DRC_THRES_INT,
+ },
+ {
+ .mask = AIC3262_AGC_NOISE_MASK,
+ .status = AIC3262_LEFT_AGC_NOISE_INT | AIC3262_RIGHT_AGC_NOISE_INT,
+ },
+ {
+ .mask = AIC3262_OVER_CURRENT_MASK,
+ .status = AIC3262_LEFT_OUTPUT_DRIVER_OVERCURRENT_INT
+ | AIC3262_RIGHT_OUTPUT_DRIVER_OVERCURRENT_INT,
+ },
+ {
+ .mask = AIC3262_OVERFLOW_MASK,
+ .status =
+ AIC3262_LEFT_DAC_OVERFLOW_INT | AIC3262_RIGHT_DAC_OVERFLOW_INT |
+ AIC3262_MINIDSP_D_BARREL_SHIFT_OVERFLOW_INT |
+ AIC3262_LEFT_ADC_OVERFLOW_INT | AIC3262_RIGHT_ADC_OVERFLOW_INT |
+ AIC3262_MINIDSP_D_BARREL_SHIFT_OVERFLOW_INT,
+ },
+ {
+ .mask = AIC3262_SPK_OVERCURRENT_MASK,
+ .status = AIC3262_SPK_OVER_CURRENT_INT,
+ },
+
+};
+
+struct aic3262_gpio_data {
+
+};
+
+static inline struct aic3262_irq_data *irq_to_aic3262_irq(struct aic3262
+ *aic3262, int irq)
+{
+ return &aic3262_irqs[irq - aic3262->irq_base];
+}
+
+static void aic3262_irq_lock(struct irq_data *data)
+{
+ struct aic3262 *aic3262 = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&aic3262->irq_lock);
+}
+
+static void aic3262_irq_sync_unlock(struct irq_data *data)
+{
+ struct aic3262 *aic3262 = irq_data_get_irq_chip_data(data);
+
+ /* write back to hardware any change in irq mask */
+ if (aic3262->irq_masks_cur != aic3262->irq_masks_cache) {
+ aic3262->irq_masks_cache = aic3262->irq_masks_cur;
+ aic3262_reg_write(aic3262, AIC3262_INT1_CNTL,
+ aic3262->irq_masks_cur);
+ }
+
+ mutex_unlock(&aic3262->irq_lock);
+}
+
+static void aic3262_irq_unmask(struct irq_data *data)
+{
+ struct aic3262 *aic3262 = irq_data_get_irq_chip_data(data);
+ struct aic3262_irq_data *irq_data =
+ irq_to_aic3262_irq(aic3262, data->irq);
+
+ aic3262->irq_masks_cur |= irq_data->mask;
+}
+
+static void aic3262_irq_mask(struct irq_data *data)
+{
+ struct aic3262 *aic3262 = irq_data_get_irq_chip_data(data);
+ struct aic3262_irq_data *irq_data =
+ irq_to_aic3262_irq(aic3262, data->irq);
+
+ aic3262->irq_masks_cur &= ~irq_data->mask;
+}
+
+static struct irq_chip aic3262_irq_chip = {
+ .name = "tlv320aic3262",
+ .irq_bus_lock = aic3262_irq_lock,
+ .irq_bus_sync_unlock = aic3262_irq_sync_unlock,
+ .irq_mask = aic3262_irq_mask,
+ .irq_unmask = aic3262_irq_unmask,
+};
+
+static irqreturn_t aic3262_irq_thread(int irq, void *data)
+{
+ struct aic3262 *aic3262 = data;
+ u8 status[4];
+ int i = 0;
+ /* Reading the sticky bit registers acknowledges
+ the interrupt to the device */
+ aic3262_bulk_read(aic3262, AIC3262_INT_STICKY_FLAG1, 4, status);
+
+ /* report */
+ if (status[2] & aic3262_irqs[AIC3262_IRQ_HEADSET_DETECT].status)
+ handle_nested_irq(aic3262->irq_base);
+
+ if (status[2] & aic3262_irqs[AIC3262_IRQ_BUTTON_PRESS].status)
+ handle_nested_irq(aic3262->irq_base + 1);
+ if (status[2] & aic3262_irqs[AIC3262_IRQ_DAC_DRC].status)
+ handle_nested_irq(aic3262->irq_base + 2);
+ if (status[3] & aic3262_irqs[AIC3262_IRQ_AGC_NOISE].status)
+ handle_nested_irq(aic3262->irq_base + 3);
+ if (status[2] & aic3262_irqs[AIC3262_IRQ_OVER_CURRENT].status)
+ handle_nested_irq(aic3262->irq_base + 4);
+ if (status[0] & aic3262_irqs[AIC3262_IRQ_OVERFLOW_EVENT].status)
+ handle_nested_irq(aic3262->irq_base + 5);
+ if (status[3] & aic3262_irqs[AIC3262_IRQ_SPEAKER_OVER_TEMP].status)
+ handle_nested_irq(aic3262->irq_base + 6);
+
+ /* ack unmasked irqs */
+ /* No need to acknowledge the interrupt on AIC3262 */
+
+ return IRQ_HANDLED;
+}
+
+int aic3262_irq_init(struct aic3262 *aic3262)
+{
+ int cur_irq, ret;
+
+ mutex_init(&aic3262->irq_lock);
+
+ /* mask the individual interrupt sources */
+ aic3262->irq_masks_cur = 0x0;
+ aic3262->irq_masks_cache = 0x0;
+ aic3262_reg_write(aic3262, AIC3262_INT1_CNTL, 0x0);
+
+ if (!aic3262->irq) {
+ dev_warn(aic3262->dev,
+ "no interrupt specified, no interrupts\n");
+ aic3262->irq_base = 0;
+ return 0;
+ }
+
+ if (!aic3262->irq_base) {
+ dev_err(aic3262->dev,
+ "no interrupt base specified, no interrupts\n");
+ return 0;
+ }
+
+ /* Register them with genirq */
+ for (cur_irq = aic3262->irq_base;
+ cur_irq < aic3262->irq_base + ARRAY_SIZE(aic3262_irqs);
+ cur_irq++) {
+ irq_set_chip_data(cur_irq, aic3262);
+ irq_set_chip_and_handler(cur_irq, &aic3262_irq_chip,
+ handle_edge_irq);
+ irq_set_nested_thread(cur_irq, 1);
+
+ /* ARM needs us to explicitly flag the IRQ as valid
+ * and will set them noprobe when we do so. */
+#ifdef CONFIG_ARM
+ set_irq_flags(cur_irq, IRQF_VALID);
+#else
+ set_irq_noprobe(cur_irq);
+#endif
+ }
+
+ ret = request_threaded_irq(aic3262->irq, NULL, aic3262_irq_thread,
+ IRQF_TRIGGER_RISING,
+ "tlv320aic3262", aic3262);
+ if (ret) {
+ dev_err(aic3262->dev, "failed to request IRQ %d: %d\n",
+ aic3262->irq, ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(aic3262_irq_init);
+
+void aic3262_irq_exit(struct aic3262 *aic3262)
+{
+ if (aic3262->irq)
+ free_irq(aic3262->irq, aic3262);
+}
+EXPORT_SYMBOL(aic3262_irq_exit);