linux-omap.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/3] mfd: twl6040: Add initial support
@ 2011-08-02 11:28 Peter Ujfalusi
  2011-08-02 11:28 ` [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator" Peter Ujfalusi
                   ` (6 more replies)
  0 siblings, 7 replies; 11+ messages in thread
From: Peter Ujfalusi @ 2011-08-02 11:28 UTC (permalink / raw)
  To: Liam Girdwood, Tony Lindgren, Mark Brown
  Cc: linux-omap, alsa-devel, Misael Lopez Cruz, Benoit Cousson,
	Sebastien Guiriec

From: Misael Lopez Cruz <misael.lopez@ti.com>

TWL6040 IC provides analog high-end audio codec functions for
handset applications. It contains several audio analog inputs
and outputs as well as vibrator support. It's connected to the
host processor via PDM interface for audio data communication.
The audio modules are controlled by internal registers that
can be accessed by I2C and PDM interface.

TWL6040 MFD will be registered as a child of TWL-CORE, and will
have two children of its own: twl6040-codec and twl6040-vibra.

This driver is based on TWL4030 and WM8350 MFD drivers.

Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com>
Signed-off-by: Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
Signed-off-by: Margarita Olaya Cabrera <magi.olaya@ti.com>
---
 arch/arm/plat-omap/include/plat/irqs.h |   12 +-
 drivers/mfd/Kconfig                    |    6 +
 drivers/mfd/Makefile                   |    1 +
 drivers/mfd/twl-core.c                 |    4 +-
 drivers/mfd/twl6040-codec.c            |  587 ++++++++++++++++++++++++++++++++
 drivers/mfd/twl6040-irq.c              |  205 +++++++++++
 include/linux/i2c/twl.h                |    1 +
 include/linux/mfd/twl6040-codec.h      |  260 ++++++++++++++
 8 files changed, 1072 insertions(+), 4 deletions(-)
 create mode 100644 drivers/mfd/twl6040-codec.c
 create mode 100644 drivers/mfd/twl6040-irq.c
 create mode 100644 include/linux/mfd/twl6040-codec.h

diff --git a/arch/arm/plat-omap/include/plat/irqs.h b/arch/arm/plat-omap/include/plat/irqs.h
index 5a25098..2cfba51 100644
--- a/arch/arm/plat-omap/include/plat/irqs.h
+++ b/arch/arm/plat-omap/include/plat/irqs.h
@@ -407,11 +407,19 @@
 #endif
 #define TWL6030_IRQ_END		(TWL6030_IRQ_BASE + TWL6030_BASE_NR_IRQS)
 
+#define TWL6040_CODEC_IRQ_BASE	TWL6030_IRQ_END
+#ifdef CONFIG_TWL6040_CODEC
+#define TWL6040_CODEC_NR_IRQS	6
+#else
+#define TWL6040_CODEC_NR_IRQS	0
+#endif
+#define TWL6040_CODEC_IRQ_END	(TWL6040_CODEC_IRQ_BASE + TWL6040_CODEC_NR_IRQS)
+
 /* Total number of interrupts depends on the enabled blocks above */
-#if (TWL4030_GPIO_IRQ_END > TWL6030_IRQ_END)
+#if (TWL4030_GPIO_IRQ_END > TWL6040_CODEC_IRQ_END)
 #define TWL_IRQ_END 		TWL4030_GPIO_IRQ_END
 #else
-#define TWL_IRQ_END		TWL6030_IRQ_END
+#define TWL_IRQ_END		TWL6040_CODEC_IRQ_END
 #endif
 
 /* GPMC related */
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 2b4cae0..c05ec17 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -233,6 +233,12 @@ config TWL6030_PWM
 	  Say yes here if you want support for TWL6030 PWM.
 	  This is used to control charging LED brightness.
 
+config TWL6040_CODEC
+	bool
+	depends on TWL4030_CORE && GENERIC_HARDIRQS
+	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 f4afcc8..1a04029 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_TWL4030_MADC)      += twl4030-madc.o
 obj-$(CONFIG_TWL4030_POWER)    += twl4030-power.o
 obj-$(CONFIG_TWL4030_CODEC)	+= twl4030-codec.o
 obj-$(CONFIG_TWL6030_PWM)	+= twl6030-pwm.o
+obj-$(CONFIG_TWL6040_CODEC)	+= twl6040-codec.o twl6040-irq.o
 
 obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
 
diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c
index 2bd9e06..cb37c08 100644
--- a/drivers/mfd/twl-core.c
+++ b/drivers/mfd/twl-core.c
@@ -110,7 +110,7 @@
 #endif
 
 #if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\
-	defined(CONFIG_SND_SOC_TWL6040) || defined(CONFIG_SND_SOC_TWL6040_MODULE)
+	defined(CONFIG_TWL6040_CODEC) || defined(CONFIG_TWL6040_CODEC_MODULE)
 #define twl_has_codec()	true
 #else
 #define twl_has_codec()	false
@@ -801,7 +801,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
 	/* Phoenix codec driver is probed directly atm */
 	if (twl_has_codec() && pdata->codec && twl_class_is_6030()) {
 		sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;
-		child = add_child(sub_chip_id, "twl6040-codec",
+		child = add_child(sub_chip_id, "twl6040-audio",
 				pdata->codec, sizeof(*pdata->codec),
 				false, 0, 0);
 		if (IS_ERR(child))
diff --git a/drivers/mfd/twl6040-codec.c b/drivers/mfd/twl6040-codec.c
new file mode 100644
index 0000000..a40cd07
--- /dev/null
+++ b/drivers/mfd/twl6040-codec.c
@@ -0,0 +1,587 @@
+/*
+ * MFD driver for TWL6040 codec submodule
+ *
+ * Authors:	Misael Lopez Cruz <misael.lopez@ti.com>
+ *		Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
+ *
+ * Copyright:	(C) 2011 Texas Instruments, Inc.
+ *
+ * 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/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/i2c/twl.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/twl6040-codec.h>
+
+static struct platform_device *twl6040_dev;
+
+int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg)
+{
+	int ret;
+	u8 val = 0;
+
+	mutex_lock(&twl6040->io_mutex);
+	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
+	if (ret < 0) {
+		mutex_unlock(&twl6040->io_mutex);
+		return ret;
+	}
+	mutex_unlock(&twl6040->io_mutex);
+
+	return val;
+}
+EXPORT_SYMBOL(twl6040_reg_read);
+
+int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&twl6040->io_mutex);
+	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
+	mutex_unlock(&twl6040->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(twl6040_reg_write);
+
+int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
+{
+	int ret;
+	u8 val;
+
+	mutex_lock(&twl6040->io_mutex);
+	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
+	if (ret)
+		goto out;
+
+	val |= mask;
+	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
+out:
+	mutex_unlock(&twl6040->io_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(twl6040_set_bits);
+
+int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
+{
+	int ret;
+	u8 val;
+
+	mutex_lock(&twl6040->io_mutex);
+	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
+	if (ret)
+		goto out;
+
+	val &= ~mask;
+	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
+out:
+	mutex_unlock(&twl6040->io_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(twl6040_clear_bits);
+
+/* twl6040 codec manual power-up sequence */
+static int twl6040_power_up(struct twl6040 *twl6040)
+{
+	u8 ldoctl, ncpctl, lppllctl;
+	int ret;
+
+	/* enable high-side LDO, reference system and internal oscillator */
+	ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA;
+	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+	if (ret)
+		return ret;
+	usleep_range(10000, 10500);
+
+	/* enable negative charge pump */
+	ncpctl = TWL6040_NCPENA;
+	ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
+	if (ret)
+		goto ncp_err;
+	usleep_range(1000, 1500);
+
+	/* enable low-side LDO */
+	ldoctl |= TWL6040_LSLDOENA;
+	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+	if (ret)
+		goto lsldo_err;
+	usleep_range(1000, 1500);
+
+	/* enable low-power PLL */
+	lppllctl = TWL6040_LPLLENA;
+	ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+	if (ret)
+		goto lppll_err;
+	usleep_range(5000, 5500);
+
+	/* disable internal oscillator */
+	ldoctl &= ~TWL6040_OSCENA;
+	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+	if (ret)
+		goto osc_err;
+
+	return 0;
+
+osc_err:
+	lppllctl &= ~TWL6040_LPLLENA;
+	twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+lppll_err:
+	ldoctl &= ~TWL6040_LSLDOENA;
+	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+lsldo_err:
+	ncpctl &= ~TWL6040_NCPENA;
+	twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
+ncp_err:
+	ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
+	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+
+	return ret;
+}
+
+/* twl6040 codec manual power-down sequence */
+static void twl6040_power_down(struct twl6040 *twl6040)
+{
+	u8 ncpctl, ldoctl, lppllctl;
+
+	ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
+	ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
+	lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
+
+	/* enable internal oscillator */
+	ldoctl |= TWL6040_OSCENA;
+	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+	usleep_range(1000, 1500);
+
+	/* disable low-power PLL */
+	lppllctl &= ~TWL6040_LPLLENA;
+	twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+
+	/* disable low-side LDO */
+	ldoctl &= ~TWL6040_LSLDOENA;
+	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+
+	/* disable negative charge pump */
+	ncpctl &= ~TWL6040_NCPENA;
+	twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
+
+	/* disable high-side LDO, reference system and internal oscillator */
+	ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
+	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+}
+
+static irqreturn_t twl6040_naudint_handler(int irq, void *data)
+{
+	struct twl6040 *twl6040 = data;
+	u8 intid;
+
+	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
+
+	if (intid & TWL6040_READYINT)
+		complete(&twl6040->ready);
+
+	return IRQ_HANDLED;
+}
+
+static int twl6040_power_up_completion(struct twl6040 *twl6040,
+				       int naudint)
+{
+	int time_left;
+	u8 intid;
+
+	time_left = wait_for_completion_timeout(&twl6040->ready,
+						msecs_to_jiffies(144));
+	if (!time_left) {
+		intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
+		if (!(intid & TWL6040_READYINT)) {
+			dev_err(&twl6040_dev->dev,
+				"timeout waiting for READYINT\n");
+			return -ETIMEDOUT;
+		}
+	}
+
+	return 0;
+}
+
+int twl6040_power(struct twl6040 *twl6040, int on)
+{
+	int audpwron = twl6040->audpwron;
+	int naudint = twl6040->irq;
+	int ret = 0;
+
+	mutex_lock(&twl6040->mutex);
+
+	if (on) {
+		/* already powered-up */
+		if (twl6040->power_count++)
+			goto out;
+
+		if (gpio_is_valid(audpwron)) {
+			/* use AUDPWRON line */
+			gpio_set_value(audpwron, 1);
+			/* wait for power-up completion */
+			ret = twl6040_power_up_completion(twl6040, naudint);
+			if (ret) {
+				dev_err(&twl6040_dev->dev,
+					"automatic power-down failed\n");
+				twl6040->power_count = 0;
+				goto out;
+			}
+		} else {
+			/* use manual power-up sequence */
+			ret = twl6040_power_up(twl6040);
+			if (ret) {
+				dev_err(&twl6040_dev->dev,
+					"manual power-up failed\n");
+				twl6040->power_count = 0;
+				goto out;
+			}
+		}
+		twl6040->pll = TWL6040_LPPLL_ID;
+		twl6040->sysclk = 19200000;
+	} else {
+		/* already powered-down */
+		if (!twl6040->power_count) {
+			dev_err(&twl6040_dev->dev,
+				"device is already powered-off\n");
+			ret = -EPERM;
+			goto out;
+		}
+
+		if (--twl6040->power_count)
+			goto out;
+
+		if (gpio_is_valid(audpwron)) {
+			/* use AUDPWRON line */
+			gpio_set_value(audpwron, 0);
+
+			/* power-down sequence latency */
+			udelay(500);
+		} else {
+			/* use manual power-down sequence */
+			twl6040_power_down(twl6040);
+		}
+		twl6040->pll = TWL6040_NOPLL_ID;
+		twl6040->sysclk = 0;
+	}
+
+out:
+	mutex_unlock(&twl6040->mutex);
+	return ret;
+}
+EXPORT_SYMBOL(twl6040_power);
+
+int twl6040_is_powered(struct twl6040 *twl6040)
+{
+	return twl6040->power_count;
+}
+EXPORT_SYMBOL(twl6040_is_powered);
+
+int twl6040_set_pll(struct twl6040 *twl6040, enum twl6040_pll_id id,
+		    unsigned int freq_in, unsigned int freq_out)
+{
+	u8 hppllctl, lppllctl;
+	int ret = 0;
+
+	mutex_lock(&twl6040->mutex);
+
+	hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL);
+	lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
+
+	switch (id) {
+	case TWL6040_LPPLL_ID:
+		/* low-power PLL divider */
+		switch (freq_out) {
+		case 17640000:
+			lppllctl |= TWL6040_LPLLFIN;
+			break;
+		case 19200000:
+			lppllctl &= ~TWL6040_LPLLFIN;
+			break;
+		default:
+			dev_err(&twl6040_dev->dev,
+				"freq_out %d not supported\n", freq_out);
+			ret = -EINVAL;
+			goto pll_out;
+		}
+		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+
+		switch (freq_in) {
+		case 32768:
+			lppllctl |= TWL6040_LPLLENA;
+			twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
+					  lppllctl);
+			mdelay(5);
+			lppllctl &= ~TWL6040_HPLLSEL;
+			twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
+					  lppllctl);
+			hppllctl &= ~TWL6040_HPLLENA;
+			twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL,
+					  hppllctl);
+			break;
+		default:
+			dev_err(&twl6040_dev->dev,
+				"freq_in %d not supported\n", freq_in);
+			ret = -EINVAL;
+			goto pll_out;
+		}
+
+		twl6040->pll = TWL6040_LPPLL_ID;
+		break;
+	case TWL6040_HPPLL_ID:
+		/* high-performance PLL can provide only 19.2 MHz */
+		if (freq_out != 19200000) {
+			dev_err(&twl6040_dev->dev,
+				"freq_out %d not supported\n", freq_out);
+			ret = -EINVAL;
+			goto pll_out;
+		}
+
+		hppllctl &= ~TWL6040_MCLK_MSK;
+
+		switch (freq_in) {
+		case 12000000:
+			/* PLL enabled, active mode */
+			hppllctl |= TWL6040_MCLK_12000KHZ |
+				    TWL6040_HPLLENA;
+			break;
+		case 19200000:
+			/*
+			 * PLL disabled
+			 * (enable PLL if MCLK jitter quality
+			 *  doesn't meet specification)
+			 */
+			hppllctl |= TWL6040_MCLK_19200KHZ;
+			break;
+		case 26000000:
+			/* PLL enabled, active mode */
+			hppllctl |= TWL6040_MCLK_26000KHZ |
+				    TWL6040_HPLLENA;
+			break;
+		case 38400000:
+			/* PLL enabled, active mode */
+			hppllctl |= TWL6040_MCLK_38400KHZ |
+				    TWL6040_HPLLENA;
+			break;
+		default:
+			dev_err(&twl6040_dev->dev,
+				"freq_in %d not supported\n", freq_in);
+			ret = -EINVAL;
+			goto pll_out;
+		}
+
+		/* enable clock slicer to ensure input waveform is square */
+		hppllctl |= TWL6040_HPLLSQRENA;
+
+		twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl);
+		udelay(500);
+		lppllctl |= TWL6040_HPLLSEL;
+		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+		lppllctl &= ~TWL6040_LPLLENA;
+		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+
+		twl6040->pll = TWL6040_HPPLL_ID;
+		break;
+	default:
+		dev_err(&twl6040_dev->dev, "unknown pll id %d\n", id);
+		ret = -EINVAL;
+		goto pll_out;
+	}
+
+	twl6040->sysclk = freq_out;
+
+pll_out:
+	mutex_unlock(&twl6040->mutex);
+	return ret;
+}
+EXPORT_SYMBOL(twl6040_set_pll);
+
+enum twl6040_pll_id twl6040_get_pll(struct twl6040 *twl6040)
+{
+	return twl6040->pll;
+}
+EXPORT_SYMBOL(twl6040_get_pll);
+
+unsigned int twl6040_get_sysclk(struct twl6040 *twl6040)
+{
+	return twl6040->sysclk;
+}
+EXPORT_SYMBOL(twl6040_get_sysclk);
+
+static int __devinit twl6040_probe(struct platform_device *pdev)
+{
+	struct twl4030_codec_data *pdata = pdev->dev.platform_data;
+	struct twl6040 *twl6040;
+	struct mfd_cell *cell = NULL;
+	int ret, children = 0;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "Platform data is missing\n");
+		return -EINVAL;
+	}
+
+	twl6040 = kzalloc(sizeof(struct twl6040), GFP_KERNEL);
+	if (!twl6040)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, twl6040);
+
+	twl6040_dev = pdev;
+	twl6040->dev = &pdev->dev;
+	twl6040->audpwron = pdata->audpwron_gpio;
+	twl6040->irq = pdata->naudint_irq;
+	twl6040->irq_base = pdata->irq_base;
+
+	mutex_init(&twl6040->mutex);
+	mutex_init(&twl6040->io_mutex);
+	init_completion(&twl6040->ready);
+
+	twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
+
+	if (gpio_is_valid(twl6040->audpwron)) {
+		ret = gpio_request(twl6040->audpwron, "audpwron");
+		if (ret)
+			goto gpio1_err;
+
+		ret = gpio_direction_output(twl6040->audpwron, 0);
+		if (ret)
+			goto gpio2_err;
+	}
+
+	/* ERRATA: Automatic power-up is not possible in ES1.0 */
+	if (twl6040_get_rev(twl6040) == TWL6040_REV_ES1_0)
+		twl6040->audpwron = -EINVAL;
+
+	if (twl6040->irq) {
+		/* codec interrupt */
+		ret = twl6040_irq_init(twl6040);
+		if (ret)
+			goto gpio2_err;
+
+		ret = twl6040_request_irq(twl6040, TWL6040_IRQ_READY,
+					  twl6040_naudint_handler, 0,
+					  "twl6040_irq_ready", twl6040);
+		if (ret) {
+			dev_err(twl6040->dev, "READY IRQ request failed: %d\n",
+				ret);
+			goto irq_err;
+		}
+	}
+
+	/* dual-access registers controlled by I2C only */
+	twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL);
+
+	if (pdata->audio) {
+		cell = &twl6040->cells[children];
+		cell->name = "twl6040-codec";
+		cell->platform_data = pdata->audio;
+		cell->pdata_size = sizeof(*pdata->audio);
+		children++;
+	}
+
+	if (pdata->vibra) {
+		cell = &twl6040->cells[children];
+		cell->name = "twl6040-vibra";
+		cell->platform_data = pdata->vibra;
+		cell->pdata_size = sizeof(*pdata->vibra);
+		children++;
+	}
+
+	if (children) {
+		ret = mfd_add_devices(&pdev->dev, pdev->id, twl6040->cells,
+				      children, NULL, 0);
+		if (ret)
+			goto mfd_err;
+	} else {
+		dev_err(&pdev->dev, "No platform data found for children\n");
+		ret = -ENODEV;
+		goto mfd_err;
+	}
+
+	return 0;
+
+mfd_err:
+	if (twl6040->irq)
+		twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
+irq_err:
+	if (twl6040->irq)
+		twl6040_irq_exit(twl6040);
+gpio2_err:
+	if (gpio_is_valid(twl6040->audpwron))
+		gpio_free(twl6040->audpwron);
+gpio1_err:
+	platform_set_drvdata(pdev, NULL);
+	kfree(twl6040);
+	twl6040_dev = NULL;
+	return ret;
+}
+
+static int __devexit twl6040_remove(struct platform_device *pdev)
+{
+	struct twl6040 *twl6040 = platform_get_drvdata(pdev);
+
+	if (twl6040_is_powered(twl6040))
+		twl6040_power(twl6040, 0);
+
+	if (gpio_is_valid(twl6040->audpwron))
+		gpio_free(twl6040->audpwron);
+
+	twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
+
+	if (twl6040->irq)
+		twl6040_irq_exit(twl6040);
+
+	mfd_remove_devices(&pdev->dev);
+	platform_set_drvdata(pdev, NULL);
+	kfree(twl6040);
+	twl6040_dev = NULL;
+
+	return 0;
+}
+
+static struct platform_driver twl6040_driver = {
+	.probe		= twl6040_probe,
+	.remove		= __devexit_p(twl6040_remove),
+	.driver		= {
+		.owner	= THIS_MODULE,
+		.name	= "twl6040-audio",
+	},
+};
+
+static int __devinit twl6040_init(void)
+{
+	return platform_driver_register(&twl6040_driver);
+}
+module_init(twl6040_init);
+
+static void __devexit twl6040_exit(void)
+{
+	platform_driver_unregister(&twl6040_driver);
+}
+
+module_exit(twl6040_exit);
+
+MODULE_DESCRIPTION("TWL6040 MFD");
+MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
+MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:twl6040-audio");
diff --git a/drivers/mfd/twl6040-irq.c b/drivers/mfd/twl6040-irq.c
new file mode 100644
index 0000000..ac776be
--- /dev/null
+++ b/drivers/mfd/twl6040-irq.c
@@ -0,0 +1,205 @@
+/*
+ * Interrupt controller support for TWL6040
+ *
+ * Author:     Misael Lopez Cruz <misael.lopez@ti.com>
+ *
+ * Copyright:   (C) 2011 Texas Instruments, Inc.
+ *
+ * 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/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/twl6040-codec.h>
+
+struct twl6040_irq_data {
+	int mask;
+	int status;
+};
+
+static struct twl6040_irq_data twl6040_irqs[] = {
+	{
+		.mask = TWL6040_THMSK,
+		.status = TWL6040_THINT,
+	},
+	{
+		.mask = TWL6040_PLUGMSK,
+		.status = TWL6040_PLUGINT | TWL6040_UNPLUGINT,
+	},
+	{
+		.mask = TWL6040_HOOKMSK,
+		.status = TWL6040_HOOKINT,
+	},
+	{
+		.mask = TWL6040_HFMSK,
+		.status = TWL6040_HFINT,
+	},
+	{
+		.mask = TWL6040_VIBMSK,
+		.status = TWL6040_VIBINT,
+	},
+	{
+		.mask = TWL6040_READYMSK,
+		.status = TWL6040_READYINT,
+	},
+};
+
+static inline
+struct twl6040_irq_data *irq_to_twl6040_irq(struct twl6040 *twl6040,
+					    int irq)
+{
+	return &twl6040_irqs[irq - twl6040->irq_base];
+}
+
+static void twl6040_irq_lock(struct irq_data *data)
+{
+	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&twl6040->irq_mutex);
+}
+
+static void twl6040_irq_sync_unlock(struct irq_data *data)
+{
+	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
+
+	/* write back to hardware any change in irq mask */
+	if (twl6040->irq_masks_cur != twl6040->irq_masks_cache) {
+		twl6040->irq_masks_cache = twl6040->irq_masks_cur;
+		twl6040_reg_write(twl6040, TWL6040_REG_INTMR,
+				  twl6040->irq_masks_cur);
+	}
+
+	mutex_unlock(&twl6040->irq_mutex);
+}
+
+static void twl6040_irq_enable(struct irq_data *data)
+{
+	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
+	struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
+							       data->irq);
+
+	twl6040->irq_masks_cur &= ~irq_data->mask;
+}
+
+static void twl6040_irq_disable(struct irq_data *data)
+{
+	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
+	struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
+							       data->irq);
+
+	twl6040->irq_masks_cur |= irq_data->mask;
+}
+
+static struct irq_chip twl6040_irq_chip = {
+	.name			= "twl6040",
+	.irq_bus_lock		= twl6040_irq_lock,
+	.irq_bus_sync_unlock	= twl6040_irq_sync_unlock,
+	.irq_enable		= twl6040_irq_enable,
+	.irq_disable		= twl6040_irq_disable,
+};
+
+static irqreturn_t twl6040_irq_thread(int irq, void *data)
+{
+	struct twl6040 *twl6040 = data;
+	u8 intid;
+	int i;
+
+	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
+
+	/* apply masking and report (backwards to handle READYINT first) */
+	for (i = ARRAY_SIZE(twl6040_irqs) - 1; i >= 0; i--) {
+		if (twl6040->irq_masks_cur & twl6040_irqs[i].mask)
+			intid &= ~twl6040_irqs[i].status;
+		if (intid & twl6040_irqs[i].status)
+			handle_nested_irq(twl6040->irq_base + i);
+	}
+
+	/* ack unmasked irqs */
+	twl6040_reg_write(twl6040, TWL6040_REG_INTID, intid);
+
+	return IRQ_HANDLED;
+}
+
+int twl6040_irq_init(struct twl6040 *twl6040)
+{
+	int cur_irq, ret;
+	u8 val;
+
+	mutex_init(&twl6040->irq_mutex);
+
+	/* mask the individual interrupt sources */
+	twl6040->irq_masks_cur = TWL6040_ALLINT_MSK;
+	twl6040->irq_masks_cache = TWL6040_ALLINT_MSK;
+	twl6040_reg_write(twl6040, TWL6040_REG_INTMR, TWL6040_ALLINT_MSK);
+
+	if (!twl6040->irq) {
+		dev_warn(twl6040->dev,
+			 "no interrupt specified, no interrupts\n");
+		twl6040->irq_base = 0;
+		return 0;
+	}
+
+	if (!twl6040->irq_base) {
+		dev_err(twl6040->dev,
+			"no interrupt base specified, no interrupts\n");
+		return 0;
+	}
+
+	/* Register them with genirq */
+	for (cur_irq = twl6040->irq_base;
+	     cur_irq < twl6040->irq_base + ARRAY_SIZE(twl6040_irqs);
+	     cur_irq++) {
+		irq_set_chip_data(cur_irq, twl6040);
+		irq_set_chip_and_handler(cur_irq, &twl6040_irq_chip,
+					 handle_level_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
+		irq_set_noprobe(cur_irq);
+#endif
+	}
+
+	ret = request_threaded_irq(twl6040->irq, NULL, twl6040_irq_thread,
+				   IRQF_ONESHOT, "twl6040", twl6040);
+	if (ret) {
+		dev_err(twl6040->dev, "failed to request IRQ %d: %d\n",
+			twl6040->irq, ret);
+		return ret;
+	}
+
+	/* reset interrupts */
+	val = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
+
+	/* interrupts cleared on write */
+	twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_INTCLRMODE);
+
+	return 0;
+}
+EXPORT_SYMBOL(twl6040_irq_init);
+
+void twl6040_irq_exit(struct twl6040 *twl6040)
+{
+	if (twl6040->irq)
+		free_irq(twl6040->irq, twl6040);
+}
+EXPORT_SYMBOL(twl6040_irq_exit);
diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h
index 314218e..9b1ae23 100644
--- a/include/linux/i2c/twl.h
+++ b/include/linux/i2c/twl.h
@@ -675,6 +675,7 @@ struct twl4030_codec_data {
 	/* twl6040 */
 	int audpwron_gpio;	/* audio power-on gpio */
 	int naudint_irq;	/* audio interrupt */
+	unsigned int irq_base;
 };
 
 struct twl4030_platform_data {
diff --git a/include/linux/mfd/twl6040-codec.h b/include/linux/mfd/twl6040-codec.h
new file mode 100644
index 0000000..2a41cd4
--- /dev/null
+++ b/include/linux/mfd/twl6040-codec.h
@@ -0,0 +1,260 @@
+/*
+ * MFD driver for twl6040 codec submodule
+ *
+ * Authors:     Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
+ *              Misael Lopez Cruz <misael.lopez@ti.com>
+ *
+ * Copyright:   (C) 2011 Texas Instruments, Inc.
+ *
+ * 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
+ *
+ */
+
+#ifndef __TWL6040_CODEC_H__
+#define __TWL6040_CODEC_H__
+
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+
+#define TWL6040_REG_ASICID		0x01
+#define TWL6040_REG_ASICREV		0x02
+#define TWL6040_REG_INTID		0x03
+#define TWL6040_REG_INTMR		0x04
+#define TWL6040_REG_NCPCTL		0x05
+#define TWL6040_REG_LDOCTL		0x06
+#define TWL6040_REG_HPPLLCTL		0x07
+#define TWL6040_REG_LPPLLCTL		0x08
+#define TWL6040_REG_LPPLLDIV		0x09
+#define TWL6040_REG_AMICBCTL		0x0A
+#define TWL6040_REG_DMICBCTL		0x0B
+#define TWL6040_REG_MICLCTL		0x0C
+#define TWL6040_REG_MICRCTL		0x0D
+#define TWL6040_REG_MICGAIN		0x0E
+#define TWL6040_REG_LINEGAIN		0x0F
+#define TWL6040_REG_HSLCTL		0x10
+#define TWL6040_REG_HSRCTL		0x11
+#define TWL6040_REG_HSGAIN		0x12
+#define TWL6040_REG_EARCTL		0x13
+#define TWL6040_REG_HFLCTL		0x14
+#define TWL6040_REG_HFLGAIN		0x15
+#define TWL6040_REG_HFRCTL		0x16
+#define TWL6040_REG_HFRGAIN		0x17
+#define TWL6040_REG_VIBCTLL		0x18
+#define TWL6040_REG_VIBDATL		0x19
+#define TWL6040_REG_VIBCTLR		0x1A
+#define TWL6040_REG_VIBDATR		0x1B
+#define TWL6040_REG_HKCTL1		0x1C
+#define TWL6040_REG_HKCTL2		0x1D
+#define TWL6040_REG_GPOCTL		0x1E
+#define TWL6040_REG_ALB			0x1F
+#define TWL6040_REG_DLB			0x20
+#define TWL6040_REG_TRIM1		0x28
+#define TWL6040_REG_TRIM2		0x29
+#define TWL6040_REG_TRIM3		0x2A
+#define TWL6040_REG_HSOTRIM		0x2B
+#define TWL6040_REG_HFOTRIM		0x2C
+#define TWL6040_REG_ACCCTL		0x2D
+#define TWL6040_REG_STATUS		0x2E
+
+#define TWL6040_CACHEREGNUM		(TWL6040_REG_STATUS + 1)
+
+#define TWL6040_VIOREGNUM		18
+#define TWL6040_VDDREGNUM		21
+
+/* INTID (0x03) fields */
+
+#define TWL6040_THINT			0x01
+#define TWL6040_PLUGINT			0x02
+#define TWL6040_UNPLUGINT		0x04
+#define TWL6040_HOOKINT			0x08
+#define TWL6040_HFINT			0x10
+#define TWL6040_VIBINT			0x20
+#define TWL6040_READYINT		0x40
+
+/* INTMR (0x04) fields */
+
+#define TWL6040_THMSK			0x01
+#define TWL6040_PLUGMSK			0x02
+#define TWL6040_HOOKMSK			0x08
+#define TWL6040_HFMSK			0x10
+#define TWL6040_VIBMSK			0x20
+#define TWL6040_READYMSK		0x40
+#define TWL6040_ALLINT_MSK		0x7B
+
+/* NCPCTL (0x05) fields */
+
+#define TWL6040_NCPENA			0x01
+#define TWL6040_NCPOPEN			0x40
+
+/* LDOCTL (0x06) fields */
+
+#define TWL6040_LSLDOENA		0x01
+#define TWL6040_HSLDOENA		0x04
+#define TWL6040_REFENA			0x40
+#define TWL6040_OSCENA			0x80
+
+/* HPPLLCTL (0x07) fields */
+
+#define TWL6040_HPLLENA			0x01
+#define TWL6040_HPLLRST			0x02
+#define TWL6040_HPLLBP			0x04
+#define TWL6040_HPLLSQRENA		0x08
+#define TWL6040_MCLK_12000KHZ		(0 << 5)
+#define TWL6040_MCLK_19200KHZ		(1 << 5)
+#define TWL6040_MCLK_26000KHZ		(2 << 5)
+#define TWL6040_MCLK_38400KHZ		(3 << 5)
+#define TWL6040_MCLK_MSK		0x60
+
+/* LPPLLCTL (0x08) fields */
+
+#define TWL6040_LPLLENA			0x01
+#define TWL6040_LPLLRST			0x02
+#define TWL6040_LPLLSEL			0x04
+#define TWL6040_LPLLFIN			0x08
+#define TWL6040_HPLLSEL			0x10
+
+/* HSLCTL (0x10) fields */
+
+#define TWL6040_HSDACMODEL		0x02
+#define TWL6040_HSDRVMODEL		0x08
+
+/* HSRCTL (0x11) fields */
+
+#define TWL6040_HSDACMODER		0x02
+#define TWL6040_HSDRVMODER		0x08
+
+/* VIBCTLL (0x18) fields */
+
+#define TWL6040_VIBENAL			0x01
+#define TWL6040_VIBCTRLL		0x04
+#define TWL6040_VIBCTRLLP		0x08
+#define TWL6040_VIBCTRLLN		0x10
+
+/* VIBDATL (0x19) fields */
+
+#define TWL6040_VIBDAT_MAX		0x64
+
+/* VIBCTLR (0x1A) fields */
+
+#define TWL6040_VIBENAR			0x01
+#define TWL6040_VIBCTRLR		0x04
+#define TWL6040_VIBCTRLRP		0x08
+#define TWL6040_VIBCTRLRN		0x10
+
+/* GPOCTL (0x1E) fields */
+
+#define TWL6040_GPO1			0x01
+#define TWL6040_GPO2			0x02
+#define TWL6040_GPO3			0x03
+
+/* ACCCTL (0x2D) fields */
+
+#define TWL6040_I2CSEL			0x01
+#define TWL6040_RESETSPLIT		0x04
+#define TWL6040_INTCLRMODE		0x08
+
+#define TWL6040_SYSCLK_SEL_LPPLL	1
+#define TWL6040_SYSCLK_SEL_HPPLL	2
+
+/* STATUS (0x2E) fields */
+
+#define TWL6040_PLUGCOMP		0x02
+#define TWL6040_VIBLOCDET		0x10
+#define TWL6040_VIBROCDET		0x20
+
+#define TWL6040_CELLS			2
+
+#define TWL6040_REV_ES1_0		0x00
+#define TWL6040_REV_ES1_1		0x01
+#define TWL6040_REV_ES1_2		0x02
+
+#define TWL6040_IRQ_TH			0
+#define TWL6040_IRQ_PLUG		1
+#define TWL6040_IRQ_HOOK		2
+#define TWL6040_IRQ_HF			3
+#define TWL6040_IRQ_VIB			4
+#define TWL6040_IRQ_READY		5
+
+enum twl6040_pll_id {
+	TWL6040_NOPLL_ID,
+	TWL6040_LPPLL_ID,
+	TWL6040_HPPLL_ID,
+};
+
+struct twl6040 {
+	struct device *dev;
+	struct mutex mutex;
+	struct mutex io_mutex;
+	struct mutex irq_mutex;
+	struct mfd_cell cells[TWL6040_CELLS];
+	struct completion ready;
+
+	int audpwron;
+	int power_count;
+	int rev;
+
+	enum twl6040_pll_id pll;
+	unsigned int sysclk;
+
+	unsigned int irq;
+	unsigned int irq_base;
+	u8 irq_masks_cur;
+	u8 irq_masks_cache;
+};
+
+static inline int twl6040_get_rev(struct twl6040 *twl6040)
+{
+	return twl6040->rev;
+}
+
+static inline int twl6040_request_irq(struct twl6040 *twl6040, int irq,
+				      irq_handler_t handler,
+				      unsigned long irqflags,
+				      const char *name,
+				      void *data)
+{
+	if (!twl6040->irq_base)
+		return -EINVAL;
+
+	return request_threaded_irq(twl6040->irq_base + irq, NULL, handler,
+				    irqflags, name, data);
+}
+
+static inline void twl6040_free_irq(struct twl6040 *twl6040, int irq,
+				    void *data)
+{
+	if (!twl6040->irq_base)
+		return;
+
+	free_irq(twl6040->irq_base + irq, data);
+}
+
+int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg);
+int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg,
+		      u8 val);
+int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg,
+		     u8 mask);
+int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg,
+		       u8 mask);
+int twl6040_power(struct twl6040 *twl6040, int on);
+int twl6040_is_powered(struct twl6040 *twl6040);
+int twl6040_set_pll(struct twl6040 *twl6040, enum twl6040_pll_id id,
+		    unsigned int freq_in, unsigned int freq_out);
+enum twl6040_pll_id twl6040_get_pll(struct twl6040 *twl6040);
+unsigned int twl6040_get_sysclk(struct twl6040 *twl6040);
+int twl6040_irq_init(struct twl6040 *twl6040);
+void twl6040_irq_exit(struct twl6040 *twl6040);
+
+#endif  /* End of __TWL6040_CODEC_H__ */
-- 
1.7.5.rc3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator"
  2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
@ 2011-08-02 11:28 ` Peter Ujfalusi
  2011-08-02 12:33   ` Felipe Balbi
  2011-08-02 11:28 ` [PATCH 2/3] input: Add initial support for TWL6040 vibrator Peter Ujfalusi
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 11+ messages in thread
From: Peter Ujfalusi @ 2011-08-02 11:28 UTC (permalink / raw)
  To: Liam Girdwood, Tony Lindgren, Mark Brown
  Cc: linux-omap, alsa-devel, Misael Lopez Cruz, Benoit Cousson,
	Sebastien Guiriec

From: Misael Lopez Cruz <misael.lopez@ti.com>

---
 drivers/input/misc/twl6040-vibra.c |   57 ++++++++++++++++-------------------
 1 files changed, 26 insertions(+), 31 deletions(-)

diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c
index fb46bf4..2612019 100644
--- a/drivers/input/misc/twl6040-vibra.c
+++ b/drivers/input/misc/twl6040-vibra.c
@@ -52,23 +52,18 @@ static irqreturn_t twl6040_vib_irq_handler(int irq, void *data)
 {
 	struct vibra_info *info = data;
 	struct twl6040 *twl6040 = info->twl6040;
-	u8 intid = 0, status = 0;
-
-	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
-	if (intid & TWL6040_VIBINT) {
-		status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
-		if (status & TWL6040_VIBLOCDET) {
-			dev_warn(info->dev,
-				 "Vibra left overcurrent detected\n");
-			twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL,
-					   TWL6040_VIBENAL);
-		}
-		if (status & TWL6040_VIBROCDET) {
-			dev_warn(info->dev,
-				 "Vibra right overcurrent detected\n");
-			twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR,
-					   TWL6040_VIBENAR);
-		}
+	u8 status;
+
+	status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
+	if (status & TWL6040_VIBLOCDET) {
+		dev_warn(info->dev, "Vibra left overcurrent detected\n");
+		twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL,
+				   TWL6040_VIBENAL);
+	}
+	if (status & TWL6040_VIBROCDET) {
+		dev_warn(info->dev, "Vibra right overcurrent detected\n");
+		twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR,
+				   TWL6040_VIBENAR);
 	}
 
 	return IRQ_HANDLED;
@@ -78,16 +73,19 @@ static void twl6040_vibra_enable(struct vibra_info *info)
 {
 	struct twl6040 *twl6040 = info->twl6040;
 
-	/*
-	 * ERRATA: Disable overcurrent protection for at least
-	 * 2.5ms when enabling vibrator drivers to avoid false
-	 * overcurrent detection
-	 */
-	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL,
-			  TWL6040_VIBENAL | TWL6040_VIBCTRLL);
-	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR,
-			  TWL6040_VIBENAR | TWL6040_VIBCTRLR);
-	mdelay(3);
+	if (twl6040_get_rev(twl6040) <= TWL6040_REV_ES1_1) {
+		/*
+		 * ERRATA: Disable overcurrent protection for at least
+		 * 2.5ms when enabling vibrator drivers to avoid false
+		 * overcurrent detection
+		 */
+		twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL,
+				  TWL6040_VIBENAL | TWL6040_VIBCTRLL);
+		twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR,
+				  TWL6040_VIBENAR | TWL6040_VIBCTRLR);
+		mdelay(3);
+	}
+
 	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL,
 			  TWL6040_VIBENAL);
 	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR,
@@ -145,7 +143,7 @@ static int vibra_play(struct input_dev *input, void *data,
 
 	ret = queue_work(info->workqueue, &info->play_work);
 	if (!ret) {
-		dev_err(&input->dev, "work is already on queue\n");
+		dev_info(&input->dev, "work is already on queue\n");
 		return ret;
 	}
 
@@ -266,12 +264,10 @@ static int __devinit twl6040_vibra_probe(struct platform_device *pdev)
 		goto err_irq;
 	}
 
-	printk(KERN_ERR "%s:powering twl6040\n", __func__);
 	ret = twl6040_power(info->twl6040, 1);
 	if (ret < 0)
 		goto err_pwr;
 
-	printk(KERN_ERR "%s:powered\n", __func__);
 	return 0;
 
 err_pwr:
@@ -297,7 +293,6 @@ static int __devexit twl6040_vibra_remove(struct platform_device *pdev)
 	twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info);
 	input_unregister_device(info->input_dev);
 	kfree(info);
-	platform_set_drvdata(pdev, NULL);
 
 	return 0;
 }
-- 
1.7.5.rc3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/3] input: Add initial support for TWL6040 vibrator
  2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
  2011-08-02 11:28 ` [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator" Peter Ujfalusi
@ 2011-08-02 11:28 ` Peter Ujfalusi
  2011-08-02 11:28 ` [PATCH 3/3] ASoC: twl6040: Convert into TWL6040 MFD child Peter Ujfalusi
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Peter Ujfalusi @ 2011-08-02 11:28 UTC (permalink / raw)
  To: Liam Girdwood, Tony Lindgren, Mark Brown
  Cc: linux-omap, alsa-devel, Misael Lopez Cruz, Benoit Cousson,
	Sebastien Guiriec

From: Misael Lopez Cruz <misael.lopez@ti.com>

Add twl6040_vibra as a child of MFD device twl6040_codec. This
implementation covers the PCM-to-PWM mode of TWL6040 vibrator
module.

Signed-off-by: Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com>
---
 drivers/input/misc/Kconfig         |   11 ++
 drivers/input/misc/Makefile        |    1 +
 drivers/input/misc/twl6040-vibra.c |  333 ++++++++++++++++++++++++++++++++++++
 3 files changed, 345 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/twl6040-vibra.c

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index f9cf088..9f78f42 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -275,6 +275,17 @@ config INPUT_TWL4030_VIBRA
 	  To compile this driver as a module, choose M here. The module will
 	  be called twl4030_vibra.
 
+config INPUT_TWL6040_VIBRA
+	tristate "Support for TWL6040 Vibrator"
+	depends on TWL4030_CORE
+	select TWL6040_CODEC
+	select INPUT_FF_MEMLESS
+	help
+	  This option enables support for TWL6040 Vibrator Driver.
+
+	  To compile this driver as a module, choose M here. The module will
+	  be called twl6040_vibra.
+
 config INPUT_UINPUT
 	tristate "User level driver support"
 	help
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index e3f7984..3ad998d 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
 obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o
 obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON)	+= twl4030-pwrbutton.o
 obj-$(CONFIG_INPUT_TWL4030_VIBRA)	+= twl4030-vibra.o
+obj-$(CONFIG_INPUT_TWL6040_VIBRA)	+= twl6040-vibra.o
 obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
 obj-$(CONFIG_INPUT_WISTRON_BTNS)	+= wistron_btns.o
 obj-$(CONFIG_INPUT_WM831X_ON)		+= wm831x-on.o
diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c
new file mode 100644
index 0000000..fb46bf4
--- /dev/null
+++ b/drivers/input/misc/twl6040-vibra.c
@@ -0,0 +1,333 @@
+/*
+ * twl6040-vibra.c - TWL6040 Vibrator driver
+ *
+ * Author:      Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
+ * Author:      Misael Lopez Cruz <misael.lopez@ti.com>
+ *
+ * Copyright:   (C) 2011 Texas Instruments, Inc.
+ *
+ * Based on twl4030-vibra.c by Henrik Saari <henrik.saari@nokia.com>
+ *				Felipe Balbi <felipe.balbi@nokia.com>
+ *				Jari Vanhala <ext-javi.vanhala@nokia.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/module.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/i2c/twl.h>
+#include <linux/mfd/twl6040-codec.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#define EFFECT_DIR_180_DEG	0x8000
+
+struct vibra_info {
+	struct device *dev;
+	struct input_dev *input_dev;
+	struct workqueue_struct *workqueue;
+	struct work_struct play_work;
+
+	bool enabled;
+	int speed;
+	int direction;
+
+	struct twl6040 *twl6040;
+};
+
+static irqreturn_t twl6040_vib_irq_handler(int irq, void *data)
+{
+	struct vibra_info *info = data;
+	struct twl6040 *twl6040 = info->twl6040;
+	u8 intid = 0, status = 0;
+
+	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
+	if (intid & TWL6040_VIBINT) {
+		status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
+		if (status & TWL6040_VIBLOCDET) {
+			dev_warn(info->dev,
+				 "Vibra left overcurrent detected\n");
+			twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL,
+					   TWL6040_VIBENAL);
+		}
+		if (status & TWL6040_VIBROCDET) {
+			dev_warn(info->dev,
+				 "Vibra right overcurrent detected\n");
+			twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR,
+					   TWL6040_VIBENAR);
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void twl6040_vibra_enable(struct vibra_info *info)
+{
+	struct twl6040 *twl6040 = info->twl6040;
+
+	/*
+	 * ERRATA: Disable overcurrent protection for at least
+	 * 2.5ms when enabling vibrator drivers to avoid false
+	 * overcurrent detection
+	 */
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL,
+			  TWL6040_VIBENAL | TWL6040_VIBCTRLL);
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR,
+			  TWL6040_VIBENAR | TWL6040_VIBCTRLR);
+	mdelay(3);
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL,
+			  TWL6040_VIBENAL);
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR,
+			  TWL6040_VIBENAR);
+
+	info->enabled = true;
+}
+
+static void twl6040_vibra_disable(struct vibra_info *info)
+{
+	struct twl6040 *twl6040 = info->twl6040;
+
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, 0x00);
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, 0x00);
+
+	info->enabled = false;
+}
+
+static void twl6040_vibra_set_effect(struct vibra_info *info)
+{
+	struct twl6040 *twl6040 = info->twl6040;
+	u8 vibdat = (u8)(info->speed);
+
+	/* 2's complement for direction > 180 degrees */
+	vibdat *= info->direction;
+
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, vibdat);
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, vibdat);
+}
+
+static void vibra_play_work(struct work_struct *work)
+{
+	struct vibra_info *info = container_of(work,
+				struct vibra_info, play_work);
+
+	if (!info->enabled)
+		twl6040_vibra_enable(info);
+
+	twl6040_vibra_set_effect(info);
+}
+
+static int vibra_play(struct input_dev *input, void *data,
+		      struct ff_effect *effect)
+{
+	struct vibra_info *info = input_get_drvdata(input);
+	int ret;
+
+	info->speed = effect->u.rumble.strong_magnitude;
+	if (!info->speed)
+		info->speed = effect->u.rumble.weak_magnitude >> 1;
+
+	/* scale to VIBDAT allowed limits */
+	info->speed = (info->speed * TWL6040_VIBDAT_MAX) / USHRT_MAX;
+	info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1;
+
+	ret = queue_work(info->workqueue, &info->play_work);
+	if (!ret) {
+		dev_err(&input->dev, "work is already on queue\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int twl6040_vibra_open(struct input_dev *input)
+{
+	struct vibra_info *info = input_get_drvdata(input);
+
+	info->workqueue = create_singlethread_workqueue("vibra");
+	if (info->workqueue == NULL) {
+		dev_err(&input->dev, "couldn't create workqueue\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void twl6040_vibra_close(struct input_dev *input)
+{
+	struct vibra_info *info = input_get_drvdata(input);
+
+	cancel_work_sync(&info->play_work);
+	INIT_WORK(&info->play_work, vibra_play_work);
+	destroy_workqueue(info->workqueue);
+	info->workqueue = NULL;
+
+	info->direction = 0;
+	info->speed = 0;
+	twl6040_vibra_set_effect(info);
+
+	if (info->enabled)
+		twl6040_vibra_disable(info);
+}
+
+#if CONFIG_PM
+static int twl6040_vibra_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct vibra_info *info = platform_get_drvdata(pdev);
+
+	twl6040_power(info->twl6040, 0);
+
+	return 0;
+}
+
+static int twl6040_vibra_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct vibra_info *info = platform_get_drvdata(pdev);
+
+	twl6040_power(info->twl6040, 1);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(twl6040_vibra_pm_ops,
+			 twl6040_vibra_suspend, twl6040_vibra_resume);
+#endif
+
+static int __devinit twl6040_vibra_probe(struct platform_device *pdev)
+{
+	struct twl4030_codec_vibra_data *pdata = pdev->dev.platform_data;
+	struct vibra_info *info;
+	int ret;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "platform_data not available\n");
+		return -EINVAL;
+	}
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		dev_err(&pdev->dev, "couldn't allocate memory\n");
+		return -ENOMEM;
+	}
+
+	info->dev = &pdev->dev;
+	info->twl6040 = dev_get_drvdata(pdev->dev.parent);
+	INIT_WORK(&info->play_work, vibra_play_work);
+
+	info->input_dev = input_allocate_device();
+	if (info->input_dev == NULL) {
+		dev_err(&pdev->dev, "couldn't allocate input device\n");
+		ret = -ENOMEM;
+		goto err_kzalloc;
+	}
+
+	input_set_drvdata(info->input_dev, info);
+
+	info->input_dev->name = "twl6040:vibrator";
+	info->input_dev->id.version = 1;
+	info->input_dev->dev.parent = pdev->dev.parent;
+	info->input_dev->open = twl6040_vibra_open;
+	info->input_dev->close = twl6040_vibra_close;
+	__set_bit(FF_RUMBLE, info->input_dev->ffbit);
+
+	ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "couldn't register vibrator to FF\n");
+		goto err_ialloc;
+	}
+
+	ret = input_register_device(info->input_dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "couldn't register input device\n");
+		goto err_iff;
+	}
+
+	platform_set_drvdata(pdev, info);
+
+	ret = twl6040_request_irq(info->twl6040, TWL6040_IRQ_VIB,
+				  twl6040_vib_irq_handler, 0,
+				  "twl6040_irq_vib", info);
+	if (ret) {
+		dev_err(&pdev->dev, "VIB IRQ request failed: %d\n", ret);
+		goto err_irq;
+	}
+
+	printk(KERN_ERR "%s:powering twl6040\n", __func__);
+	ret = twl6040_power(info->twl6040, 1);
+	if (ret < 0)
+		goto err_pwr;
+
+	printk(KERN_ERR "%s:powered\n", __func__);
+	return 0;
+
+err_pwr:
+	twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info);
+err_irq:
+	input_unregister_device(info->input_dev);
+	info->input_dev = NULL;
+err_iff:
+	if (info->input_dev)
+		input_ff_destroy(info->input_dev);
+err_ialloc:
+	input_free_device(info->input_dev);
+err_kzalloc:
+	kfree(info);
+	return ret;
+}
+
+static int __devexit twl6040_vibra_remove(struct platform_device *pdev)
+{
+	struct vibra_info *info = platform_get_drvdata(pdev);
+
+	twl6040_power(info->twl6040, 0);
+	twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info);
+	input_unregister_device(info->input_dev);
+	kfree(info);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver twl6040_vibra_driver = {
+	.probe		= twl6040_vibra_probe,
+	.remove		= __devexit_p(twl6040_vibra_remove),
+	.driver		= {
+		.name	= "twl6040-vibra",
+		.owner	= THIS_MODULE,
+#if CONFIG_PM
+		.pm	= &twl6040_vibra_pm_ops,
+#endif
+	},
+};
+
+static int __init twl6040_vibra_init(void)
+{
+	return platform_driver_register(&twl6040_vibra_driver);
+}
+module_init(twl6040_vibra_init);
+
+static void __exit twl6040_vibra_exit(void)
+{
+	platform_driver_unregister(&twl6040_vibra_driver);
+}
+module_exit(twl6040_vibra_exit);
+
+MODULE_ALIAS("platform:twl6040-vibra");
+MODULE_DESCRIPTION("TWL6040 Vibra driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
+MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
-- 
1.7.5.rc3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/3] ASoC: twl6040: Convert into TWL6040 MFD child
  2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
  2011-08-02 11:28 ` [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator" Peter Ujfalusi
  2011-08-02 11:28 ` [PATCH 2/3] input: Add initial support for TWL6040 vibrator Peter Ujfalusi
@ 2011-08-02 11:28 ` Peter Ujfalusi
  2011-08-02 11:28 ` [PATCH 3/4] Calculate max VIBDAT code based on VDDVIB, motor and driver resistances Peter Ujfalusi
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Peter Ujfalusi @ 2011-08-02 11:28 UTC (permalink / raw)
  To: Liam Girdwood, Tony Lindgren, Mark Brown
  Cc: linux-omap, alsa-devel, Misael Lopez Cruz, Benoit Cousson,
	Sebastien Guiriec

From: Misael Lopez Cruz <misael.lopez@ti.com>

Convert TWL6040 CODEC driver into a TWL6040 MFD child, it implies
that MFD-level operations like register accesses, clock setting
and power management are done through MFD APIs, not directly by
CODEC driver anymore. To avoid conflicts with the other MFD child,
vibrator registers are skipped in CODEC driver.

Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com>
Signed-off-by: Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
---
 sound/soc/codecs/Kconfig   |    1 +
 sound/soc/codecs/twl6040.c |  426 +++++++++-----------------------------------
 sound/soc/codecs/twl6040.h |  118 ------------
 sound/soc/omap/sdp4430.c   |    2 +
 4 files changed, 91 insertions(+), 456 deletions(-)

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 2a69718..5a35b9b 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -236,6 +236,7 @@ config SND_SOC_TWL4030
 	tristate
 
 config SND_SOC_TWL6040
+	select TWL6040_CODEC
 	tristate
 
 config SND_SOC_UDA134X
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
index 255901c..471c68a 100644
--- a/sound/soc/codecs/twl6040.c
+++ b/sound/soc/codecs/twl6040.c
@@ -24,11 +24,10 @@
 #include <linux/init.h>
 #include <linux/delay.h>
 #include <linux/pm.h>
-#include <linux/i2c.h>
-#include <linux/gpio.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/i2c/twl.h>
+#include <linux/mfd/twl6040-codec.h>
 
 #include <sound/core.h>
 #include <sound/pcm.h>
@@ -77,14 +76,12 @@ struct twl6040_jack_data {
 
 /* codec private data */
 struct twl6040_data {
-	int audpwron;
-	int naudint;
 	int codec_powered;
 	int pll;
 	int non_lp;
+	unsigned int clk_in;
 	unsigned int sysclk;
 	struct snd_pcm_hw_constraint_list *sysclk_constraints;
-	struct completion ready;
 	struct twl6040_jack_data hs_jack;
 	struct snd_soc_codec *codec;
 	struct workqueue_struct *workqueue;
@@ -239,12 +236,13 @@ static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec,
 static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
 			unsigned int reg)
 {
+	struct twl6040 *twl6040 = codec->control_data;
 	u8 value;
 
 	if (reg >= TWL6040_CACHEREGNUM)
 		return -EIO;
 
-	twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &value, reg);
+	value = twl6040_reg_read(twl6040, reg);
 	twl6040_write_reg_cache(codec, reg, value);
 
 	return value;
@@ -256,11 +254,13 @@ static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
 static int twl6040_write(struct snd_soc_codec *codec,
 			unsigned int reg, unsigned int value)
 {
+	struct twl6040 *twl6040 = codec->control_data;
+
 	if (reg >= TWL6040_CACHEREGNUM)
 		return -EIO;
 
 	twl6040_write_reg_cache(codec, reg, value);
-	return twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, value, reg);
+	return twl6040_reg_write(twl6040, reg, value);
 }
 
 static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
@@ -268,15 +268,21 @@ static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
 	u8 *cache = codec->reg_cache;
 	int reg, i;
 
-	/* allow registers to be accessed by i2c */
-	twl6040_write(codec, TWL6040_REG_ACCCTL, cache[TWL6040_REG_ACCCTL]);
-
 	for (i = 0; i < TWL6040_VIOREGNUM; i++) {
 		reg = twl6040_vio_reg[i];
-		/* skip read-only registers (ASICID, ASICREV, STATUS) */
+		/*
+		 * skip read-only registers (ASICID, ASICREV, STATUS)
+		 * and registers shared among MFD children
+		 */
 		switch (reg) {
 		case TWL6040_REG_ASICID:
 		case TWL6040_REG_ASICREV:
+		case TWL6040_REG_INTID:
+		case TWL6040_REG_INTMR:
+		case TWL6040_REG_NCPCTL:
+		case TWL6040_REG_LDOCTL:
+		case TWL6040_REG_GPOCTL:
+		case TWL6040_REG_ACCCTL:
 		case TWL6040_REG_STATUS:
 			continue;
 		default:
@@ -293,6 +299,20 @@ static void twl6040_init_vdd_regs(struct snd_soc_codec *codec)
 
 	for (i = 0; i < TWL6040_VDDREGNUM; i++) {
 		reg = twl6040_vdd_reg[i];
+		/* skip vibra and PLL registers */
+		switch (reg) {
+		case TWL6040_REG_VIBCTLL:
+		case TWL6040_REG_VIBDATL:
+		case TWL6040_REG_VIBCTLR:
+		case TWL6040_REG_VIBDATR:
+		case TWL6040_REG_HPPLLCTL:
+		case TWL6040_REG_LPPLLCTL:
+		case TWL6040_REG_LPPLLDIV:
+			continue;
+		default:
+			break;
+		}
+
 		twl6040_write(codec, reg, cache[reg]);
 	}
 }
@@ -596,88 +616,6 @@ static int pga_event(struct snd_soc_dapm_widget *w,
 	return 0;
 }
 
-/* twl6040 codec manual power-up sequence */
-static void twl6040_power_up(struct snd_soc_codec *codec)
-{
-	u8 ncpctl, ldoctl, lppllctl, accctl;
-
-	ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
-	ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
-	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
-	accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
-
-	/* enable reference system */
-	ldoctl |= TWL6040_REFENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	msleep(10);
-	/* enable internal oscillator */
-	ldoctl |= TWL6040_OSCENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	udelay(10);
-	/* enable high-side ldo */
-	ldoctl |= TWL6040_HSLDOENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	udelay(244);
-	/* enable negative charge pump */
-	ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN;
-	twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
-	udelay(488);
-	/* enable low-side ldo */
-	ldoctl |= TWL6040_LSLDOENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	udelay(244);
-	/* enable low-power pll */
-	lppllctl |= TWL6040_LPLLENA;
-	twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
-	/* reset state machine */
-	accctl |= TWL6040_RESETSPLIT;
-	twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
-	mdelay(5);
-	accctl &= ~TWL6040_RESETSPLIT;
-	twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
-	/* disable internal oscillator */
-	ldoctl &= ~TWL6040_OSCENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-}
-
-/* twl6040 codec manual power-down sequence */
-static void twl6040_power_down(struct snd_soc_codec *codec)
-{
-	u8 ncpctl, ldoctl, lppllctl, accctl;
-
-	ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
-	ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
-	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
-	accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
-
-	/* enable internal oscillator */
-	ldoctl |= TWL6040_OSCENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	udelay(10);
-	/* disable low-power pll */
-	lppllctl &= ~TWL6040_LPLLENA;
-	twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
-	/* disable low-side ldo */
-	ldoctl &= ~TWL6040_LSLDOENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	udelay(244);
-	/* disable negative charge pump */
-	ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN);
-	twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
-	udelay(488);
-	/* disable high-side ldo */
-	ldoctl &= ~TWL6040_HSLDOENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	udelay(244);
-	/* disable internal oscillator */
-	ldoctl &= ~TWL6040_OSCENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	/* disable reference system */
-	ldoctl &= ~TWL6040_REFENA;
-	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-	msleep(10);
-}
-
 /* set headset dac and driver power mode */
 static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
 {
@@ -766,33 +704,19 @@ static void twl6040_accessory_work(struct work_struct *work)
 }
 
 /* audio interrupt handler */
-static irqreturn_t twl6040_naudint_handler(int irq, void *data)
+static irqreturn_t twl6040_audio_handler(int irq, void *data)
 {
 	struct snd_soc_codec *codec = data;
+	struct twl6040 *twl6040 = codec->control_data;
 	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
 	u8 intid;
 
-	twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID);
-
-	if (intid & TWL6040_THINT)
-		dev_alert(codec->dev, "die temp over-limit detection\n");
+	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
 
 	if ((intid & TWL6040_PLUGINT) || (intid & TWL6040_UNPLUGINT))
 		queue_delayed_work(priv->workqueue, &priv->delayed_work,
 							msecs_to_jiffies(200));
 
-	if (intid & TWL6040_HOOKINT)
-		dev_info(codec->dev, "hook detection\n");
-
-	if (intid & TWL6040_HFINT)
-		dev_alert(codec->dev, "hf drivers over current detection\n");
-
-	if (intid & TWL6040_VIBINT)
-		dev_alert(codec->dev, "vib drivers over current detection\n");
-
-	if (intid & TWL6040_READYINT)
-		complete(&priv->ready);
-
 	return IRQ_HANDLED;
 }
 
@@ -1231,36 +1155,11 @@ static int twl6040_add_widgets(struct snd_soc_codec *codec)
 	return 0;
 }
 
-static int twl6040_power_up_completion(struct snd_soc_codec *codec,
-					int naudint)
-{
-	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
-	int time_left;
-	u8 intid;
-
-	time_left = wait_for_completion_timeout(&priv->ready,
-				msecs_to_jiffies(144));
-
-	if (!time_left) {
-		twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid,
-							TWL6040_REG_INTID);
-		if (!(intid & TWL6040_READYINT)) {
-			dev_err(codec->dev, "timeout waiting for READYINT\n");
-			return -ETIMEDOUT;
-		}
-	}
-
-	priv->codec_powered = 1;
-
-	return 0;
-}
-
 static int twl6040_set_bias_level(struct snd_soc_codec *codec,
 				enum snd_soc_bias_level level)
 {
+	struct twl6040 *twl6040 = codec->control_data;
 	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
-	int audpwron = priv->audpwron;
-	int naudint = priv->naudint;
 	int ret;
 
 	switch (level) {
@@ -1272,62 +1171,30 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
 		if (priv->codec_powered)
 			break;
 
-		if (gpio_is_valid(audpwron)) {
-			/* use AUDPWRON line */
-			gpio_set_value(audpwron, 1);
-
-			/* wait for power-up completion */
-			ret = twl6040_power_up_completion(codec, naudint);
-			if (ret)
-				return ret;
+		ret = twl6040_power(twl6040, 1);
+		if (ret)
+			return ret;
 
-			/* sync registers updated during power-up sequence */
-			twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
-			twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
-			twl6040_read_reg_volatile(codec, TWL6040_REG_LPPLLCTL);
-		} else {
-			/* use manual power-up sequence */
-			twl6040_power_up(codec);
-			priv->codec_powered = 1;
-		}
+		priv->codec_powered = 1;
 
 		/* initialize vdd/vss registers with reg_cache */
 		twl6040_init_vdd_regs(codec);
 
 		/* Set external boost GPO */
 		twl6040_write(codec, TWL6040_REG_GPOCTL, 0x02);
-
-		/* Set initial minimal gain values */
-		twl6040_write(codec, TWL6040_REG_HSGAIN, 0xFF);
-		twl6040_write(codec, TWL6040_REG_EARCTL, 0x1E);
-		twl6040_write(codec, TWL6040_REG_HFLGAIN, 0x1D);
-		twl6040_write(codec, TWL6040_REG_HFRGAIN, 0x1D);
 		break;
 	case SND_SOC_BIAS_OFF:
 		if (!priv->codec_powered)
 			break;
 
-		if (gpio_is_valid(audpwron)) {
-			/* use AUDPWRON line */
-			gpio_set_value(audpwron, 0);
-
-			/* power-down sequence latency */
-			udelay(500);
-
-			/* sync registers updated during power-down sequence */
-			twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
-			twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
-			twl6040_write_reg_cache(codec, TWL6040_REG_LPPLLCTL,
-						0x00);
-		} else {
-			/* use manual power-down sequence */
-			twl6040_power_down(codec);
-		}
-
+		twl6040_power(twl6040, 0);
 		priv->codec_powered = 0;
 		break;
 	}
 
+	/* get PLL and sysclk after power transition */
+	priv->pll = twl6040_get_pll(twl6040);
+	priv->sysclk = twl6040_get_sysclk(twl6040);
 	codec->dapm.bias_level = level;
 
 	return 0;
@@ -1374,39 +1241,40 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
 {
 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
 	struct snd_soc_codec *codec = rtd->codec;
+	struct twl6040 *twl6040 = codec->control_data;
 	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
-	u8 lppllctl;
-	int rate;
+	unsigned int sysclk;
+	int rate, ret;
 
 	/* nothing to do for high-perf pll, it supports only 48 kHz */
 	if (priv->pll == TWL6040_HPPLL_ID)
 		return 0;
 
-	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
-
 	rate = params_rate(params);
 	switch (rate) {
 	case 11250:
 	case 22500:
 	case 44100:
 	case 88200:
-		lppllctl |= TWL6040_LPLLFIN;
-		priv->sysclk = 17640000;
+		sysclk = 17640000;
 		break;
 	case 8000:
 	case 16000:
 	case 32000:
 	case 48000:
 	case 96000:
-		lppllctl &= ~TWL6040_LPLLFIN;
-		priv->sysclk = 19200000;
+		sysclk = 19200000;
 		break;
 	default:
 		dev_err(codec->dev, "unsupported rate %d\n", rate);
 		return -EINVAL;
 	}
 
-	twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+	ret = twl6040_set_pll(twl6040, TWL6040_LPPLL_ID, priv->clk_in, sysclk);
+	if (ret)
+		return ret;
+
+	priv->sysclk = twl6040_get_sysclk(twl6040);
 
 	return 0;
 }
@@ -1449,99 +1317,27 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
 		int clk_id, unsigned int freq, int dir)
 {
 	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl6040 *twl6040 = codec->control_data;
 	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
-	u8 hppllctl, lppllctl;
-
-	hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL);
-	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+	int ret = 0;
 
 	switch (clk_id) {
 	case TWL6040_SYSCLK_SEL_LPPLL:
-		switch (freq) {
-		case 32768:
-			/* headset dac and driver must be in low-power mode */
-			headset_power_mode(codec, 0);
-
-			/* clk32k input requires low-power pll */
-			lppllctl |= TWL6040_LPLLENA;
-			twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
-			mdelay(5);
-			lppllctl &= ~TWL6040_HPLLSEL;
-			twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
-			hppllctl &= ~TWL6040_HPLLENA;
-			twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
-			break;
-		default:
-			dev_err(codec->dev, "unknown mclk freq %d\n", freq);
-			return -EINVAL;
-		}
-
-		/* lppll divider */
-		switch (priv->sysclk) {
-		case 17640000:
-			lppllctl |= TWL6040_LPLLFIN;
-			break;
-		case 19200000:
-			lppllctl &= ~TWL6040_LPLLFIN;
-			break;
-		default:
-			/* sysclk not yet configured */
-			lppllctl &= ~TWL6040_LPLLFIN;
-			priv->sysclk = 19200000;
-			break;
-		}
-
-		twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+		ret = twl6040_set_pll(twl6040, TWL6040_LPPLL_ID,
+				      freq, priv->sysclk);
+		if (ret)
+			return ret;
 
-		priv->pll = TWL6040_LPPLL_ID;
+		headset_power_mode(codec, 0);
 		priv->sysclk_constraints = &lp_constraints;
 		break;
 	case TWL6040_SYSCLK_SEL_HPPLL:
-		hppllctl &= ~TWL6040_MCLK_MSK;
-
-		switch (freq) {
-		case 12000000:
-			/* mclk input, pll enabled */
-			hppllctl |= TWL6040_MCLK_12000KHZ |
-				    TWL6040_HPLLSQRBP |
-				    TWL6040_HPLLENA;
-			break;
-		case 19200000:
-			/* mclk input, pll disabled */
-			hppllctl |= TWL6040_MCLK_19200KHZ |
-				    TWL6040_HPLLSQRENA |
-				    TWL6040_HPLLBP;
-			break;
-		case 26000000:
-			/* mclk input, pll enabled */
-			hppllctl |= TWL6040_MCLK_26000KHZ |
-				    TWL6040_HPLLSQRBP |
-				    TWL6040_HPLLENA;
-			break;
-		case 38400000:
-			/* clk slicer, pll disabled */
-			hppllctl |= TWL6040_MCLK_38400KHZ |
-				    TWL6040_HPLLSQRENA |
-				    TWL6040_HPLLBP;
-			break;
-		default:
-			dev_err(codec->dev, "unknown mclk freq %d\n", freq);
-			return -EINVAL;
-		}
+		ret = twl6040_set_pll(twl6040, TWL6040_HPPLL_ID,
+				      freq, priv->sysclk);
+		if (ret)
+			return ret;
 
-		/* headset dac and driver must be in high-performance mode */
 		headset_power_mode(codec, 1);
-
-		twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
-		udelay(500);
-		lppllctl |= TWL6040_HPLLSEL;
-		twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
-		lppllctl &= ~TWL6040_LPLLENA;
-		twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
-
-		/* high-performance pll can provide only 19.2 MHz */
-		priv->pll = TWL6040_HPPLL_ID;
-		priv->sysclk = 19200000;
 		priv->sysclk_constraints = &hp_constraints;
 		break;
 	default:
@@ -1549,6 +1345,10 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
 		return -EINVAL;
 	}
 
+	priv->pll = twl6040_get_pll(twl6040);
+	priv->clk_in = freq;
+	priv->sysclk = twl6040_get_sysclk(twl6040);
+
 	return 0;
 }
 
@@ -1600,11 +1400,10 @@ static int twl6040_resume(struct snd_soc_codec *codec)
 
 static int twl6040_probe(struct snd_soc_codec *codec)
 {
-	struct twl4030_codec_data *twl_codec = codec->dev->platform_data;
 	struct twl6040_data *priv;
-	int audpwron, naudint;
 	int ret = 0;
-	u8 icrev, intmr = TWL6040_ALLINT_MSK;
+
+	codec->control_data = dev_get_drvdata(codec->dev->parent);
 
 	priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL);
 	if (priv == NULL)
@@ -1613,22 +1412,7 @@ static int twl6040_probe(struct snd_soc_codec *codec)
 
 	priv->codec = codec;
 
-	twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &icrev, TWL6040_REG_ASICREV);
-
-	if (twl_codec && (icrev > 0))
-		audpwron = twl_codec->audpwron_gpio;
-	else
-		audpwron = -EINVAL;
-
-	if (twl_codec)
-		naudint = twl_codec->naudint_irq;
-	else
-		naudint = 0;
-
-	priv->audpwron = audpwron;
-	priv->naudint = naudint;
 	priv->workqueue = create_singlethread_workqueue("twl6040-codec");
-
 	if (!priv->workqueue) {
 		ret = -ENOMEM;
 		goto work_err;
@@ -1638,56 +1422,34 @@ static int twl6040_probe(struct snd_soc_codec *codec)
 
 	mutex_init(&priv->mutex);
 
-	init_completion(&priv->ready);
 	init_completion(&priv->headset.ramp_done);
 	init_completion(&priv->handsfree.ramp_done);
 
-	if (gpio_is_valid(audpwron)) {
-		ret = gpio_request(audpwron, "audpwron");
-		if (ret)
-			goto gpio1_err;
-
-		ret = gpio_direction_output(audpwron, 0);
-		if (ret)
-			goto gpio2_err;
-
-		priv->codec_powered = 0;
-
-		/* enable only codec ready interrupt */
-		intmr &= ~(TWL6040_READYMSK | TWL6040_PLUGMSK);
-
-		/* reset interrupt status to allow correct power up sequence */
-		twl6040_read_reg_volatile(codec, TWL6040_REG_INTID);
-	}
-	twl6040_write(codec, TWL6040_REG_INTMR, intmr);
-
-	if (naudint) {
-		/* audio interrupt */
-		ret = request_threaded_irq(naudint, NULL,
-				twl6040_naudint_handler,
-				IRQF_TRIGGER_LOW | IRQF_ONESHOT,
-				"twl6040_codec", codec);
-		if (ret)
-			goto gpio2_err;
-	}
-
-	/* init vio registers */
-	twl6040_init_vio_regs(codec);
-
 	priv->hf_workqueue = create_singlethread_workqueue("twl6040-hf");
 	if (priv->hf_workqueue == NULL) {
 		ret = -ENOMEM;
-		goto irq_err;
+		goto hfwq_err;
 	}
 	priv->hs_workqueue = create_singlethread_workqueue("twl6040-hs");
 	if (priv->hs_workqueue == NULL) {
 		ret = -ENOMEM;
-		goto wq_err;
+		goto hswq_err;
 	}
 
 	INIT_DELAYED_WORK(&priv->hs_delayed_work, twl6040_pga_hs_work);
 	INIT_DELAYED_WORK(&priv->hf_delayed_work, twl6040_pga_hf_work);
 
+	ret = twl6040_request_irq(codec->control_data, TWL6040_IRQ_PLUG,
+				  twl6040_audio_handler, 0,
+				  "twl6040_irq_plug", codec);
+	if (ret) {
+		dev_err(codec->dev, "PLUG IRQ request failed: %d\n", ret);
+		goto plugirq_err;
+	}
+
+	/* init vio registers */
+	twl6040_init_vio_regs(codec);
+
 	/* power on device */
 	ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 	if (ret)
@@ -1700,16 +1462,12 @@ static int twl6040_probe(struct snd_soc_codec *codec)
 	return 0;
 
 bias_err:
+	twl6040_free_irq(codec->control_data, TWL6040_IRQ_PLUG, codec);
+plugirq_err:
 	destroy_workqueue(priv->hs_workqueue);
-wq_err:
+hswq_err:
 	destroy_workqueue(priv->hf_workqueue);
-irq_err:
-	if (naudint)
-		free_irq(naudint, codec);
-gpio2_err:
-	if (gpio_is_valid(audpwron))
-		gpio_free(audpwron);
-gpio1_err:
+hfwq_err:
 	destroy_workqueue(priv->workqueue);
 work_err:
 	kfree(priv);
@@ -1719,17 +1477,9 @@ work_err:
 static int twl6040_remove(struct snd_soc_codec *codec)
 {
 	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
-	int audpwron = priv->audpwron;
-	int naudint = priv->naudint;
 
 	twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
-
-	if (gpio_is_valid(audpwron))
-		gpio_free(audpwron);
-
-	if (naudint)
-		free_irq(naudint, codec);
-
+	twl6040_free_irq(codec->control_data, TWL6040_IRQ_PLUG, codec);
 	destroy_workqueue(priv->workqueue);
 	destroy_workqueue(priv->hf_workqueue);
 	destroy_workqueue(priv->hs_workqueue);
diff --git a/sound/soc/codecs/twl6040.h b/sound/soc/codecs/twl6040.h
index 23aeed0..234bfad 100644
--- a/sound/soc/codecs/twl6040.h
+++ b/sound/soc/codecs/twl6040.h
@@ -22,124 +22,6 @@
 #ifndef __TWL6040_H__
 #define __TWL6040_H__
 
-#define TWL6040_REG_ASICID		0x01
-#define TWL6040_REG_ASICREV		0x02
-#define TWL6040_REG_INTID		0x03
-#define TWL6040_REG_INTMR		0x04
-#define TWL6040_REG_NCPCTL		0x05
-#define TWL6040_REG_LDOCTL		0x06
-#define TWL6040_REG_HPPLLCTL		0x07
-#define TWL6040_REG_LPPLLCTL		0x08
-#define TWL6040_REG_LPPLLDIV		0x09
-#define TWL6040_REG_AMICBCTL		0x0A
-#define TWL6040_REG_DMICBCTL		0x0B
-#define TWL6040_REG_MICLCTL		0x0C
-#define TWL6040_REG_MICRCTL		0x0D
-#define TWL6040_REG_MICGAIN		0x0E
-#define TWL6040_REG_LINEGAIN		0x0F
-#define TWL6040_REG_HSLCTL		0x10
-#define TWL6040_REG_HSRCTL		0x11
-#define TWL6040_REG_HSGAIN		0x12
-#define TWL6040_REG_EARCTL		0x13
-#define TWL6040_REG_HFLCTL		0x14
-#define TWL6040_REG_HFLGAIN		0x15
-#define TWL6040_REG_HFRCTL		0x16
-#define TWL6040_REG_HFRGAIN		0x17
-#define TWL6040_REG_VIBCTLL		0x18
-#define TWL6040_REG_VIBDATL		0x19
-#define TWL6040_REG_VIBCTLR		0x1A
-#define TWL6040_REG_VIBDATR		0x1B
-#define TWL6040_REG_HKCTL1		0x1C
-#define TWL6040_REG_HKCTL2		0x1D
-#define TWL6040_REG_GPOCTL		0x1E
-#define TWL6040_REG_ALB			0x1F
-#define TWL6040_REG_DLB			0x20
-#define TWL6040_REG_TRIM1		0x28
-#define TWL6040_REG_TRIM2		0x29
-#define TWL6040_REG_TRIM3		0x2A
-#define TWL6040_REG_HSOTRIM		0x2B
-#define TWL6040_REG_HFOTRIM		0x2C
-#define TWL6040_REG_ACCCTL		0x2D
-#define TWL6040_REG_STATUS		0x2E
-
-#define TWL6040_CACHEREGNUM		(TWL6040_REG_STATUS + 1)
-
-#define TWL6040_VIOREGNUM		18
-#define TWL6040_VDDREGNUM		21
-
-/* INTID (0x03) fields */
-
-#define TWL6040_THINT			0x01
-#define TWL6040_PLUGINT			0x02
-#define TWL6040_UNPLUGINT		0x04
-#define TWL6040_HOOKINT			0x08
-#define TWL6040_HFINT			0x10
-#define TWL6040_VIBINT			0x20
-#define TWL6040_READYINT		0x40
-
-/* INTMR (0x04) fields */
-
-#define TWL6040_PLUGMSK			0x02
-#define TWL6040_READYMSK		0x40
-#define TWL6040_ALLINT_MSK		0x7B
-
-/* NCPCTL (0x05) fields */
-
-#define TWL6040_NCPENA			0x01
-#define TWL6040_NCPOPEN			0x40
-
-/* LDOCTL (0x06) fields */
-
-#define TWL6040_LSLDOENA		0x01
-#define TWL6040_HSLDOENA		0x04
-#define TWL6040_REFENA			0x40
-#define TWL6040_OSCENA			0x80
-
-/* HPPLLCTL (0x07) fields */
-
-#define TWL6040_HPLLENA			0x01
-#define TWL6040_HPLLRST			0x02
-#define TWL6040_HPLLBP			0x04
-#define TWL6040_HPLLSQRENA		0x08
-#define TWL6040_HPLLSQRBP		0x10
-#define TWL6040_MCLK_12000KHZ		(0 << 5)
-#define TWL6040_MCLK_19200KHZ		(1 << 5)
-#define TWL6040_MCLK_26000KHZ		(2 << 5)
-#define TWL6040_MCLK_38400KHZ		(3 << 5)
-#define TWL6040_MCLK_MSK		0x60
-
-/* LPPLLCTL (0x08) fields */
-
-#define TWL6040_LPLLENA			0x01
-#define TWL6040_LPLLRST			0x02
-#define TWL6040_LPLLSEL			0x04
-#define TWL6040_LPLLFIN			0x08
-#define TWL6040_HPLLSEL			0x10
-
-/* HSLCTL (0x10) fields */
-
-#define TWL6040_HSDACMODEL		0x02
-#define TWL6040_HSDRVMODEL		0x08
-
-/* HSRCTL (0x11) fields */
-
-#define TWL6040_HSDACMODER		0x02
-#define TWL6040_HSDRVMODER		0x08
-
-/* ACCCTL (0x2D) fields */
-
-#define TWL6040_RESETSPLIT		0x04
-
-#define TWL6040_SYSCLK_SEL_LPPLL	1
-#define TWL6040_SYSCLK_SEL_HPPLL	2
-
-#define TWL6040_HPPLL_ID		1
-#define TWL6040_LPPLL_ID		2
-
-/* STATUS (0x2E) fields */
-
-#define TWL6040_PLUGCOMP		0x02
-
 void twl6040_hs_jack_detect(struct snd_soc_codec *codec,
 			    struct snd_soc_jack *jack, int report);
 
diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c
index 189e039..0142083 100644
--- a/sound/soc/omap/sdp4430.c
+++ b/sound/soc/omap/sdp4430.c
@@ -21,6 +21,8 @@
 
 #include <linux/clk.h>
 #include <linux/platform_device.h>
+#include <linux/mfd/twl6040-codec.h>
+
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/soc.h>
-- 
1.7.5.rc3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/4] Calculate max VIBDAT code based on VDDVIB, motor and driver resistances
  2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
                   ` (2 preceding siblings ...)
  2011-08-02 11:28 ` [PATCH 3/3] ASoC: twl6040: Convert into TWL6040 MFD child Peter Ujfalusi
@ 2011-08-02 11:28 ` Peter Ujfalusi
  2011-08-02 11:28 ` [PATCH 4/4] OMAP4: SDP4430: Add vibrator platform data Peter Ujfalusi
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Peter Ujfalusi @ 2011-08-02 11:28 UTC (permalink / raw)
  To: Liam Girdwood, Tony Lindgren, Mark Brown
  Cc: linux-omap, alsa-devel, Misael Lopez Cruz, Benoit Cousson,
	Sebastien Guiriec

From: Misael Lopez Cruz <misael.lopez@ti.com>

Squash with "input: Add initial support for TWL6040 vibrator".
---
 drivers/input/misc/twl6040-vibra.c |  165 ++++++++++++++++++++++++++++++++----
 include/linux/i2c/twl.h            |    7 ++
 2 files changed, 154 insertions(+), 18 deletions(-)

diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c
index 2612019..2f77913 100644
--- a/drivers/input/misc/twl6040-vibra.c
+++ b/drivers/input/misc/twl6040-vibra.c
@@ -32,20 +32,33 @@
 #include <linux/mfd/twl6040-codec.h>
 #include <linux/slab.h>
 #include <linux/delay.h>
+#include <linux/regulator/consumer.h>
 
 #define EFFECT_DIR_180_DEG	0x8000
 
+/* Recommended modulation index 85% */
+#define TWL6040_VIBRA_MOD	85
+
 struct vibra_info {
 	struct device *dev;
 	struct input_dev *input_dev;
 	struct workqueue_struct *workqueue;
 	struct work_struct play_work;
+	struct mutex mutex;
 
 	bool enabled;
-	int speed;
+	int weak_speed;
+	int strong_speed;
 	int direction;
 
+	unsigned int vibldrv_res;
+	unsigned int vibrdrv_res;
+	unsigned int viblmotor_res;
+	unsigned int vibrmotor_res;
+
 	struct twl6040 *twl6040;
+	struct regulator *vddvibl_reg;
+	struct regulator *vddvibr_reg;
 };
 
 static irqreturn_t twl6040_vib_irq_handler(int irq, void *data)
@@ -72,6 +85,21 @@ static irqreturn_t twl6040_vib_irq_handler(int irq, void *data)
 static void twl6040_vibra_enable(struct vibra_info *info)
 {
 	struct twl6040 *twl6040 = info->twl6040;
+	int ret = 0;
+
+	ret = regulator_enable(info->vddvibl_reg);
+	if (ret) {
+		dev_err(info->dev, "failed to enable VDDVIBL %d\n",
+			ret);
+		return;
+	}
+
+	ret = regulator_enable(info->vddvibr_reg);
+	if (ret) {
+		dev_err(info->dev, "failed to enable VDDVIBR %d\n",
+			ret);
+		goto err;
+	}
 
 	if (twl6040_get_rev(twl6040) <= TWL6040_REV_ES1_1) {
 		/*
@@ -92,6 +120,10 @@ static void twl6040_vibra_enable(struct vibra_info *info)
 			  TWL6040_VIBENAR);
 
 	info->enabled = true;
+	return;
+
+err:
+	regulator_disable(info->vddvibl_reg);
 }
 
 static void twl6040_vibra_disable(struct vibra_info *info)
@@ -101,19 +133,56 @@ static void twl6040_vibra_disable(struct vibra_info *info)
 	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, 0x00);
 	twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, 0x00);
 
+	regulator_disable(info->vddvibl_reg);
+	regulator_disable(info->vddvibr_reg);
+
 	info->enabled = false;
 }
 
-static void twl6040_vibra_set_effect(struct vibra_info *info)
+static u8 twl6040_vibra_code(int vddvib, int vibdrv_res, int motor_res,
+			     int speed, int direction)
 {
-	struct twl6040 *twl6040 = info->twl6040;
-	u8 vibdat = (u8)(info->speed);
+	int vpk, max_code;
+	u8 vibdat;
+
+	/* output swing */
+	vpk = (vddvib * motor_res * TWL6040_VIBRA_MOD) /
+		(100 * (vibdrv_res + motor_res));
+
+	/* 50mV per VIBDAT code step */
+	max_code = vpk / 50;
+	if (max_code > TWL6040_VIBDAT_MAX)
+		max_code = TWL6040_VIBDAT_MAX;
+
+	/* scale speed to max allowed code */
+	vibdat = (u8)((speed * max_code) / USHRT_MAX);
 
 	/* 2's complement for direction > 180 degrees */
-	vibdat *= info->direction;
+	vibdat *= direction;
 
-	twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, vibdat);
-	twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, vibdat);
+	return vibdat;
+}
+
+static void twl6040_vibra_set_effect(struct vibra_info *info)
+{
+	struct twl6040 *twl6040 = info->twl6040;
+	u8 vibdatl, vibdatr;
+	int volt;
+
+	/* weak motor */
+	volt = regulator_get_voltage(info->vddvibl_reg) / 1000;
+	vibdatl = twl6040_vibra_code(volt, info->vibldrv_res,
+				     info->viblmotor_res,
+				     info->weak_speed, info->direction);
+
+	/* strong motor */
+	volt = regulator_get_voltage(info->vddvibr_reg) / 1000;
+	vibdatr = twl6040_vibra_code(volt, info->vibrdrv_res,
+				     info->vibrmotor_res,
+				     info->strong_speed, info->direction);
+
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, vibdatl);
+	twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, vibdatr);
 }
 
 static void vibra_play_work(struct work_struct *work)
@@ -121,10 +190,14 @@ static void vibra_play_work(struct work_struct *work)
 	struct vibra_info *info = container_of(work,
 				struct vibra_info, play_work);
 
+	mutex_lock(&info->mutex);
+
 	if (!info->enabled)
 		twl6040_vibra_enable(info);
 
 	twl6040_vibra_set_effect(info);
+
+	mutex_unlock(&info->mutex);
 }
 
 static int vibra_play(struct input_dev *input, void *data,
@@ -133,12 +206,8 @@ static int vibra_play(struct input_dev *input, void *data,
 	struct vibra_info *info = input_get_drvdata(input);
 	int ret;
 
-	info->speed = effect->u.rumble.strong_magnitude;
-	if (!info->speed)
-		info->speed = effect->u.rumble.weak_magnitude >> 1;
-
-	/* scale to VIBDAT allowed limits */
-	info->speed = (info->speed * TWL6040_VIBDAT_MAX) / USHRT_MAX;
+	info->weak_speed = effect->u.rumble.weak_magnitude;
+	info->strong_speed = effect->u.rumble.strong_magnitude;
 	info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1;
 
 	ret = queue_work(info->workqueue, &info->play_work);
@@ -173,11 +242,17 @@ static void twl6040_vibra_close(struct input_dev *input)
 	info->workqueue = NULL;
 
 	info->direction = 0;
-	info->speed = 0;
+	info->weak_speed = 0;
+	info->strong_speed = 0;
+
+	mutex_lock(&info->mutex);
+
 	twl6040_vibra_set_effect(info);
 
 	if (info->enabled)
 		twl6040_vibra_disable(info);
+
+	mutex_unlock(&info->mutex);
 }
 
 #if CONFIG_PM
@@ -224,11 +299,23 @@ static int __devinit twl6040_vibra_probe(struct platform_device *pdev)
 
 	info->dev = &pdev->dev;
 	info->twl6040 = dev_get_drvdata(pdev->dev.parent);
+	info->vibldrv_res = pdata->vibldrv_res;
+	info->vibrdrv_res = pdata->vibrdrv_res;
+	info->viblmotor_res = pdata->viblmotor_res;
+	info->vibrmotor_res = pdata->vibrmotor_res;
+	if ((!info->vibldrv_res && !info->viblmotor_res) ||
+	    (!info->vibrdrv_res && !info->vibrmotor_res)) {
+		dev_err(info->dev, "invalid vibra driver/motor resistance\n");
+		ret = -EINVAL;
+		goto err_kzalloc;
+	}
+
+	mutex_init(&info->mutex);
 	INIT_WORK(&info->play_work, vibra_play_work);
 
 	info->input_dev = input_allocate_device();
 	if (info->input_dev == NULL) {
-		dev_err(&pdev->dev, "couldn't allocate input device\n");
+		dev_err(info->dev, "couldn't allocate input device\n");
 		ret = -ENOMEM;
 		goto err_kzalloc;
 	}
@@ -244,13 +331,13 @@ static int __devinit twl6040_vibra_probe(struct platform_device *pdev)
 
 	ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
 	if (ret < 0) {
-		dev_err(&pdev->dev, "couldn't register vibrator to FF\n");
+		dev_err(info->dev, "couldn't register vibrator to FF\n");
 		goto err_ialloc;
 	}
 
 	ret = input_register_device(info->input_dev);
 	if (ret < 0) {
-		dev_err(&pdev->dev, "couldn't register input device\n");
+		dev_err(info->dev, "couldn't register input device\n");
 		goto err_iff;
 	}
 
@@ -260,10 +347,46 @@ static int __devinit twl6040_vibra_probe(struct platform_device *pdev)
 				  twl6040_vib_irq_handler, 0,
 				  "twl6040_irq_vib", info);
 	if (ret) {
-		dev_err(&pdev->dev, "VIB IRQ request failed: %d\n", ret);
+		dev_err(info->dev, "VIB IRQ request failed: %d\n", ret);
 		goto err_irq;
 	}
 
+	info->vddvibl_reg = regulator_get(info->dev, "vddvibl");
+	if (IS_ERR(info->vddvibl_reg)) {
+		ret = PTR_ERR(info->vddvibl_reg);
+		dev_err(info->dev, "couldn't get VDDVIBL regulator %d\n", ret);
+		goto err_vddvibl;
+	}
+
+	if (pdata->vddvibl_uV) {
+		ret = regulator_set_voltage(info->vddvibl_reg,
+					    pdata->vddvibl_uV,
+					    pdata->vddvibl_uV);
+		if (ret) {
+			dev_err(info->dev, "failed to set VDDVIBL volt %d\n",
+				ret);
+			goto err_vddvibr;
+		}
+	}
+
+	info->vddvibr_reg = regulator_get(info->dev, "vddvibr");
+	if (IS_ERR(info->vddvibr_reg)) {
+		ret = PTR_ERR(info->vddvibr_reg);
+		dev_err(info->dev, "couldn't get VDDVIBR regulator %d\n", ret);
+		goto err_vddvibr;
+	}
+
+	if (pdata->vddvibr_uV) {
+		ret = regulator_set_voltage(info->vddvibr_reg,
+					    pdata->vddvibr_uV,
+					    pdata->vddvibr_uV);
+		if (ret) {
+			dev_err(info->dev, "failed to set VDDVIBR volt %d\n",
+				ret);
+			goto err_pwr;
+		}
+	}
+
 	ret = twl6040_power(info->twl6040, 1);
 	if (ret < 0)
 		goto err_pwr;
@@ -271,6 +394,10 @@ static int __devinit twl6040_vibra_probe(struct platform_device *pdev)
 	return 0;
 
 err_pwr:
+	regulator_put(info->vddvibr_reg);
+err_vddvibr:
+	regulator_put(info->vddvibl_reg);
+err_vddvibl:
 	twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info);
 err_irq:
 	input_unregister_device(info->input_dev);
@@ -290,6 +417,8 @@ static int __devexit twl6040_vibra_remove(struct platform_device *pdev)
 	struct vibra_info *info = platform_get_drvdata(pdev);
 
 	twl6040_power(info->twl6040, 0);
+	regulator_put(info->vddvibl_reg);
+	regulator_put(info->vddvibr_reg);
 	twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info);
 	input_unregister_device(info->input_dev);
 	kfree(info);
diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h
index 9d70719..5b4e732 100644
--- a/include/linux/i2c/twl.h
+++ b/include/linux/i2c/twl.h
@@ -669,6 +669,13 @@ struct twl4030_codec_audio_data {
 
 struct twl4030_codec_vibra_data {
 	unsigned int	coexist;
+
+	unsigned int vibldrv_res;	/* left driver resistance */
+	unsigned int vibrdrv_res;	/* right driver resistance */
+	unsigned int viblmotor_res;	/* left motor resistance */
+	unsigned int vibrmotor_res;	/* right motor resistance */
+	int vddvibl_uV;			/* VDDVIBL volt, set 0 for fixed reg */
+	int vddvibr_uV;			/* VDDVIBR volt, set 0 for fixed reg */
 };
 
 struct twl4030_codec_data {
-- 
1.7.5.rc3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/4] OMAP4: SDP4430: Add vibrator platform data
  2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
                   ` (3 preceding siblings ...)
  2011-08-02 11:28 ` [PATCH 3/4] Calculate max VIBDAT code based on VDDVIB, motor and driver resistances Peter Ujfalusi
@ 2011-08-02 11:28 ` Peter Ujfalusi
  2011-08-02 11:32 ` [PATCH 1/3] mfd: twl6040: Add initial support Péter Ujfalusi
  2011-08-02 12:31 ` Felipe Balbi
  6 siblings, 0 replies; 11+ messages in thread
From: Peter Ujfalusi @ 2011-08-02 11:28 UTC (permalink / raw)
  To: Liam Girdwood, Tony Lindgren, Mark Brown
  Cc: linux-omap, alsa-devel, Misael Lopez Cruz, Benoit Cousson,
	Sebastien Guiriec

From: Misael Lopez Cruz <misael.lopez@ti.com>

Blaze/SDP4430 contains two 10-Ohms vibrator motors, both supplied
by VBAT_PROC (3.75V).

Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com>
---
 arch/arm/mach-omap2/board-4430sdp.c |   37 ++++++++++++++++++++++++++++++++++-
 1 files changed, 36 insertions(+), 1 deletions(-)

diff --git a/arch/arm/mach-omap2/board-4430sdp.c b/arch/arm/mach-omap2/board-4430sdp.c
index 95396ce..1060d48 100644
--- a/arch/arm/mach-omap2/board-4430sdp.c
+++ b/arch/arm/mach-omap2/board-4430sdp.c
@@ -22,6 +22,7 @@
 #include <linux/i2c/twl.h>
 #include <linux/gpio_keys.h>
 #include <linux/regulator/machine.h>
+#include <linux/regulator/fixed.h>
 #include <linux/leds.h>
 #include <linux/leds_pwm.h>
 
@@ -311,11 +312,40 @@ static struct platform_device sdp4430_lcd_device = {
 	.id		= -1,
 };
 
+static struct regulator_consumer_supply sdp4430_vbat_supply[] = {
+	REGULATOR_SUPPLY("vddvibl", "twl6040-vibra"),
+	REGULATOR_SUPPLY("vddvibr", "twl6040-vibra"),
+};
+
+static struct regulator_init_data sdp4430_vbat_data = {
+	.constraints = {
+		.always_on	= 1,
+	},
+	.num_consumer_supplies	= ARRAY_SIZE(sdp4430_vbat_supply),
+	.consumer_supplies	= sdp4430_vbat_supply,
+};
+
+static struct fixed_voltage_config sdp4430_vbat_pdata = {
+	.supply_name	= "VBAT",
+	.microvolts	= 3750000,
+	.init_data	= &sdp4430_vbat_data,
+	.gpio		= -EINVAL,
+};
+
+static struct platform_device sdp4430_vbat = {
+	.name		= "reg-fixed-voltage",
+	.id		= -1,
+	.dev = {
+		.platform_data = &sdp4430_vbat_pdata,
+	},
+};
+
 static struct platform_device *sdp4430_devices[] __initdata = {
 	&sdp4430_lcd_device,
 	&sdp4430_gpio_keys_device,
 	&sdp4430_leds_gpio,
 	&sdp4430_leds_pwm,
+	&sdp4430_vbat,
 };
 
 static struct omap_lcd_config sdp4430_lcd_config __initdata = {
@@ -572,7 +602,12 @@ static struct twl4030_codec_audio_data twl6040_audio = {
 };
 
 static struct twl4030_codec_vibra_data twl6040_vibra = {
-	/* Add vibra only data */
+	.vibldrv_res = 8,
+	.vibrdrv_res = 3,
+	.viblmotor_res = 10,
+	.vibrmotor_res = 10,
+	.vddvibl_uV = 0,	/* fixed volt supply - VBAT */
+	.vddvibr_uV = 0,	/* fixed volt supply - VBAT */
 };
 
 static struct twl4030_codec_data twl6040_codec = {
-- 
1.7.5.rc3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/3] mfd: twl6040: Add initial support
  2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
                   ` (4 preceding siblings ...)
  2011-08-02 11:28 ` [PATCH 4/4] OMAP4: SDP4430: Add vibrator platform data Peter Ujfalusi
@ 2011-08-02 11:32 ` Péter Ujfalusi
  2011-08-02 12:36   ` [alsa-devel] " Felipe Balbi
  2011-08-02 12:31 ` Felipe Balbi
  6 siblings, 1 reply; 11+ messages in thread
From: Péter Ujfalusi @ 2011-08-02 11:32 UTC (permalink / raw)
  To: alsa-devel
  Cc: Sebastien@alsa-project.org, Cousson, Benoit, Tony Lindgren,
	Mark Brown, Misael@alsa-project.org, Guiriec, Sebastien,
	Lopez Cruz, Misael, linux-omap@vger.kernel.org, Girdwood, Liam

Oops...

On Tuesday 02 August 2011 13:28:41 Ujfalusi, Peter wrote:
> From: Misael Lopez Cruz <misael.lopez@ti.com>
> 
> TWL6040 IC provides analog high-end audio codec functions for
> handset applications. It contains several audio analog inputs
> and outputs as well as vibrator support. It's connected to the
> host processor via PDM interface for audio data communication.
> The audio modules are controlled by internal registers that
> can be accessed by I2C and PDM interface.
> 
> TWL6040 MFD will be registered as a child of TWL-CORE, and will
> have two children of its own: twl6040-codec and twl6040-vibra.
> 
> This driver is based on TWL4030 and WM8350 MFD drivers.

I have sent the patches from wrong directory :(
Please disregard this series, this has been already merged...

Sorry!

--
Péter

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/3] mfd: twl6040: Add initial support
  2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
                   ` (5 preceding siblings ...)
  2011-08-02 11:32 ` [PATCH 1/3] mfd: twl6040: Add initial support Péter Ujfalusi
@ 2011-08-02 12:31 ` Felipe Balbi
  6 siblings, 0 replies; 11+ messages in thread
From: Felipe Balbi @ 2011-08-02 12:31 UTC (permalink / raw)
  To: Peter Ujfalusi
  Cc: Liam Girdwood, Tony Lindgren, Mark Brown, linux-omap, alsa-devel,
	Misael Lopez Cruz, Benoit Cousson, Sebastien Guiriec

[-- Attachment #1: Type: text/plain, Size: 27406 bytes --]

Hi,

On Tue, Aug 02, 2011 at 02:28:41PM +0300, Peter Ujfalusi wrote:
> From: Misael Lopez Cruz <misael.lopez@ti.com>
> 
> TWL6040 IC provides analog high-end audio codec functions for
> handset applications. It contains several audio analog inputs
> and outputs as well as vibrator support. It's connected to the
> host processor via PDM interface for audio data communication.
> The audio modules are controlled by internal registers that
> can be accessed by I2C and PDM interface.
> 
> TWL6040 MFD will be registered as a child of TWL-CORE, and will
> have two children of its own: twl6040-codec and twl6040-vibra.
> 
> This driver is based on TWL4030 and WM8350 MFD drivers.
> 
> Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com>
> Signed-off-by: Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
> Signed-off-by: Margarita Olaya Cabrera <magi.olaya@ti.com>
> ---
>  arch/arm/plat-omap/include/plat/irqs.h |   12 +-
>  drivers/mfd/Kconfig                    |    6 +
>  drivers/mfd/Makefile                   |    1 +
>  drivers/mfd/twl-core.c                 |    4 +-
>  drivers/mfd/twl6040-codec.c            |  587 ++++++++++++++++++++++++++++++++
>  drivers/mfd/twl6040-irq.c              |  205 +++++++++++
>  include/linux/i2c/twl.h                |    1 +
>  include/linux/mfd/twl6040-codec.h      |  260 ++++++++++++++
>  8 files changed, 1072 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/mfd/twl6040-codec.c
>  create mode 100644 drivers/mfd/twl6040-irq.c
>  create mode 100644 include/linux/mfd/twl6040-codec.h
> 
> diff --git a/arch/arm/plat-omap/include/plat/irqs.h b/arch/arm/plat-omap/include/plat/irqs.h
> index 5a25098..2cfba51 100644
> --- a/arch/arm/plat-omap/include/plat/irqs.h
> +++ b/arch/arm/plat-omap/include/plat/irqs.h
> @@ -407,11 +407,19 @@
>  #endif
>  #define TWL6030_IRQ_END		(TWL6030_IRQ_BASE + TWL6030_BASE_NR_IRQS)
>  
> +#define TWL6040_CODEC_IRQ_BASE	TWL6030_IRQ_END
> +#ifdef CONFIG_TWL6040_CODEC
> +#define TWL6040_CODEC_NR_IRQS	6
> +#else
> +#define TWL6040_CODEC_NR_IRQS	0
> +#endif
> +#define TWL6040_CODEC_IRQ_END	(TWL6040_CODEC_IRQ_BASE + TWL6040_CODEC_NR_IRQS)

since this is a new driver, please don't pullute this header and use
irq_alloc_descs() instead ?!?

> diff --git a/drivers/mfd/twl6040-codec.c b/drivers/mfd/twl6040-codec.c
> new file mode 100644
> index 0000000..a40cd07
> --- /dev/null
> +++ b/drivers/mfd/twl6040-codec.c
> @@ -0,0 +1,587 @@
> +/*
> + * MFD driver for TWL6040 codec submodule
> + *
> + * Authors:	Misael Lopez Cruz <misael.lopez@ti.com>
> + *		Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
> + *
> + * Copyright:	(C) 2011 Texas Instruments, Inc.
> + *
> + * 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/module.h>
> +#include <linux/types.h>
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/platform_device.h>
> +#include <linux/gpio.h>
> +#include <linux/delay.h>
> +#include <linux/i2c/twl.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/twl6040-codec.h>
> +
> +static struct platform_device *twl6040_dev;

this is useless, see below.

> +int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg)
> +{
> +	int ret;
> +	u8 val = 0;
> +
> +	mutex_lock(&twl6040->io_mutex);
> +	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
> +	if (ret < 0) {
> +		mutex_unlock(&twl6040->io_mutex);
> +		return ret;
> +	}
> +	mutex_unlock(&twl6040->io_mutex);
> +
> +	return val;
> +}
> +EXPORT_SYMBOL(twl6040_reg_read);

EXPORT_SYMBOL_GPL(), all users of this should be GPL so we have access
to the code. Ditto to all below.

> +int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val)
> +{
> +	int ret;
> +
> +	mutex_lock(&twl6040->io_mutex);
> +	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
> +	mutex_unlock(&twl6040->io_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(twl6040_reg_write);
> +
> +int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
> +{
> +	int ret;
> +	u8 val;
> +
> +	mutex_lock(&twl6040->io_mutex);
> +	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
> +	if (ret)
> +		goto out;
> +
> +	val |= mask;
> +	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
> +out:
> +	mutex_unlock(&twl6040->io_mutex);
> +	return ret;
> +}
> +EXPORT_SYMBOL(twl6040_set_bits);
> +
> +int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
> +{
> +	int ret;
> +	u8 val;
> +
> +	mutex_lock(&twl6040->io_mutex);
> +	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
> +	if (ret)
> +		goto out;
> +
> +	val &= ~mask;
> +	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
> +out:
> +	mutex_unlock(&twl6040->io_mutex);
> +	return ret;
> +}
> +EXPORT_SYMBOL(twl6040_clear_bits);
> +
> +/* twl6040 codec manual power-up sequence */
> +static int twl6040_power_up(struct twl6040 *twl6040)
> +{
> +	u8 ldoctl, ncpctl, lppllctl;
> +	int ret;
> +
> +	/* enable high-side LDO, reference system and internal oscillator */
> +	ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA;
> +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +	if (ret)
> +		return ret;
> +	usleep_range(10000, 10500);
> +
> +	/* enable negative charge pump */
> +	ncpctl = TWL6040_NCPENA;
> +	ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
> +	if (ret)
> +		goto ncp_err;
> +	usleep_range(1000, 1500);
> +
> +	/* enable low-side LDO */
> +	ldoctl |= TWL6040_LSLDOENA;
> +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +	if (ret)
> +		goto lsldo_err;
> +	usleep_range(1000, 1500);
> +
> +	/* enable low-power PLL */
> +	lppllctl = TWL6040_LPLLENA;
> +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
> +	if (ret)
> +		goto lppll_err;
> +	usleep_range(5000, 5500);
> +
> +	/* disable internal oscillator */
> +	ldoctl &= ~TWL6040_OSCENA;
> +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +	if (ret)
> +		goto osc_err;
> +
> +	return 0;
> +
> +osc_err:
> +	lppllctl &= ~TWL6040_LPLLENA;
> +	twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
> +lppll_err:
> +	ldoctl &= ~TWL6040_LSLDOENA;
> +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +lsldo_err:
> +	ncpctl &= ~TWL6040_NCPENA;
> +	twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
> +ncp_err:
> +	ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
> +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +
> +	return ret;
> +}
> +
> +/* twl6040 codec manual power-down sequence */
> +static void twl6040_power_down(struct twl6040 *twl6040)
> +{
> +	u8 ncpctl, ldoctl, lppllctl;
> +
> +	ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
> +	ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
> +	lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
> +
> +	/* enable internal oscillator */
> +	ldoctl |= TWL6040_OSCENA;
> +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +	usleep_range(1000, 1500);
> +
> +	/* disable low-power PLL */
> +	lppllctl &= ~TWL6040_LPLLENA;
> +	twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
> +
> +	/* disable low-side LDO */
> +	ldoctl &= ~TWL6040_LSLDOENA;
> +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +
> +	/* disable negative charge pump */
> +	ncpctl &= ~TWL6040_NCPENA;
> +	twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
> +
> +	/* disable high-side LDO, reference system and internal oscillator */
> +	ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
> +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
> +}
> +
> +static irqreturn_t twl6040_naudint_handler(int irq, void *data)
> +{
> +	struct twl6040 *twl6040 = data;
> +	u8 intid;
> +
> +	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
> +
> +	if (intid & TWL6040_READYINT)
> +		complete(&twl6040->ready);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int twl6040_power_up_completion(struct twl6040 *twl6040,
> +				       int naudint)
> +{
> +	int time_left;
> +	u8 intid;
> +
> +	time_left = wait_for_completion_timeout(&twl6040->ready,
> +						msecs_to_jiffies(144));

should this be interruptible ?

> +	if (!time_left) {
> +		intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
> +		if (!(intid & TWL6040_READYINT)) {
> +			dev_err(&twl6040_dev->dev,

you have twl6040 as argument to this function, meaning:

dev_err(twl6040->dev,

should work just fine.

> +int twl6040_power(struct twl6040 *twl6040, int on)
> +{
> +	int audpwron = twl6040->audpwron;
> +	int naudint = twl6040->irq;
> +	int ret = 0;
> +
> +	mutex_lock(&twl6040->mutex);
> +
> +	if (on) {
> +		/* already powered-up */
> +		if (twl6040->power_count++)
> +			goto out;
> +
> +		if (gpio_is_valid(audpwron)) {
> +			/* use AUDPWRON line */
> +			gpio_set_value(audpwron, 1);
> +			/* wait for power-up completion */
> +			ret = twl6040_power_up_completion(twl6040, naudint);
> +			if (ret) {
> +				dev_err(&twl6040_dev->dev,

ditto here and all below.

> +					"automatic power-down failed\n");
> +				twl6040->power_count = 0;
> +				goto out;
> +			}
> +		} else {
> +			/* use manual power-up sequence */
> +			ret = twl6040_power_up(twl6040);
> +			if (ret) {
> +				dev_err(&twl6040_dev->dev,
> +					"manual power-up failed\n");
> +				twl6040->power_count = 0;
> +				goto out;
> +			}
> +		}
> +		twl6040->pll = TWL6040_LPPLL_ID;
> +		twl6040->sysclk = 19200000;
> +	} else {
> +		/* already powered-down */
> +		if (!twl6040->power_count) {
> +			dev_err(&twl6040_dev->dev,
> +				"device is already powered-off\n");
> +			ret = -EPERM;
> +			goto out;
> +		}
> +
> +		if (--twl6040->power_count)
> +			goto out;
> +
> +		if (gpio_is_valid(audpwron)) {
> +			/* use AUDPWRON line */
> +			gpio_set_value(audpwron, 0);
> +
> +			/* power-down sequence latency */
> +			udelay(500);
> +		} else {
> +			/* use manual power-down sequence */
> +			twl6040_power_down(twl6040);
> +		}
> +		twl6040->pll = TWL6040_NOPLL_ID;
> +		twl6040->sysclk = 0;
> +	}
> +
> +out:
> +	mutex_unlock(&twl6040->mutex);
> +	return ret;
> +}
> +EXPORT_SYMBOL(twl6040_power);

should this function be a ->runtime_resume/suspend method ? Then
children would simply pm_runtime_get_sync() and this would be called by
the pm runtime framework. Does it make sense ?

> +int twl6040_is_powered(struct twl6040 *twl6040)
> +{
> +	return twl6040->power_count;
> +}
> +EXPORT_SYMBOL(twl6040_is_powered);

I'm not sure this should be needed. You allocate your children yourself,
so they will only probe after this has succesfully probed, rendering
this unneeded, right ?

> +int twl6040_set_pll(struct twl6040 *twl6040, enum twl6040_pll_id id,
> +		    unsigned int freq_in, unsigned int freq_out)
> +{
> +	u8 hppllctl, lppllctl;
> +	int ret = 0;
> +
> +	mutex_lock(&twl6040->mutex);
> +
> +	hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL);
> +	lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
> +
> +	switch (id) {
> +	case TWL6040_LPPLL_ID:
> +		/* low-power PLL divider */
> +		switch (freq_out) {
> +		case 17640000:
> +			lppllctl |= TWL6040_LPLLFIN;
> +			break;
> +		case 19200000:
> +			lppllctl &= ~TWL6040_LPLLFIN;
> +			break;
> +		default:
> +			dev_err(&twl6040_dev->dev,

also don't need that global static pointer. twl6040->dev. Ditto all
below

> +				"freq_out %d not supported\n", freq_out);
> +			ret = -EINVAL;
> +			goto pll_out;
> +		}
> +		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
> +
> +		switch (freq_in) {
> +		case 32768:
> +			lppllctl |= TWL6040_LPLLENA;
> +			twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
> +					  lppllctl);
> +			mdelay(5);
> +			lppllctl &= ~TWL6040_HPLLSEL;
> +			twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
> +					  lppllctl);
> +			hppllctl &= ~TWL6040_HPLLENA;
> +			twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL,
> +					  hppllctl);
> +			break;
> +		default:
> +			dev_err(&twl6040_dev->dev,
> +				"freq_in %d not supported\n", freq_in);
> +			ret = -EINVAL;
> +			goto pll_out;
> +		}
> +
> +		twl6040->pll = TWL6040_LPPLL_ID;
> +		break;
> +	case TWL6040_HPPLL_ID:
> +		/* high-performance PLL can provide only 19.2 MHz */
> +		if (freq_out != 19200000) {
> +			dev_err(&twl6040_dev->dev,
> +				"freq_out %d not supported\n", freq_out);
> +			ret = -EINVAL;
> +			goto pll_out;
> +		}
> +
> +		hppllctl &= ~TWL6040_MCLK_MSK;
> +
> +		switch (freq_in) {
> +		case 12000000:
> +			/* PLL enabled, active mode */
> +			hppllctl |= TWL6040_MCLK_12000KHZ |
> +				    TWL6040_HPLLENA;
> +			break;
> +		case 19200000:
> +			/*
> +			 * PLL disabled
> +			 * (enable PLL if MCLK jitter quality
> +			 *  doesn't meet specification)
> +			 */
> +			hppllctl |= TWL6040_MCLK_19200KHZ;
> +			break;
> +		case 26000000:
> +			/* PLL enabled, active mode */
> +			hppllctl |= TWL6040_MCLK_26000KHZ |
> +				    TWL6040_HPLLENA;
> +			break;
> +		case 38400000:
> +			/* PLL enabled, active mode */
> +			hppllctl |= TWL6040_MCLK_38400KHZ |
> +				    TWL6040_HPLLENA;
> +			break;
> +		default:
> +			dev_err(&twl6040_dev->dev,
> +				"freq_in %d not supported\n", freq_in);
> +			ret = -EINVAL;
> +			goto pll_out;
> +		}
> +
> +		/* enable clock slicer to ensure input waveform is square */
> +		hppllctl |= TWL6040_HPLLSQRENA;
> +
> +		twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl);
> +		udelay(500);
> +		lppllctl |= TWL6040_HPLLSEL;
> +		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
> +		lppllctl &= ~TWL6040_LPLLENA;
> +		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
> +
> +		twl6040->pll = TWL6040_HPPLL_ID;
> +		break;
> +	default:
> +		dev_err(&twl6040_dev->dev, "unknown pll id %d\n", id);
> +		ret = -EINVAL;
> +		goto pll_out;
> +	}
> +
> +	twl6040->sysclk = freq_out;
> +
> +pll_out:
> +	mutex_unlock(&twl6040->mutex);
> +	return ret;
> +}
> +EXPORT_SYMBOL(twl6040_set_pll);
> +
> +enum twl6040_pll_id twl6040_get_pll(struct twl6040 *twl6040)
> +{
> +	return twl6040->pll;
> +}
> +EXPORT_SYMBOL(twl6040_get_pll);
> +
> +unsigned int twl6040_get_sysclk(struct twl6040 *twl6040)
> +{
> +	return twl6040->sysclk;
> +}
> +EXPORT_SYMBOL(twl6040_get_sysclk);

these three above look like they should be done via clk framework ??

> +static int __devinit twl6040_probe(struct platform_device *pdev)
> +{
> +	struct twl4030_codec_data *pdata = pdev->dev.platform_data;
> +	struct twl6040 *twl6040;
> +	struct mfd_cell *cell = NULL;
> +	int ret, children = 0;
> +
> +	if (!pdata) {
> +		dev_err(&pdev->dev, "Platform data is missing\n");
> +		return -EINVAL;
> +	}
> +
> +	twl6040 = kzalloc(sizeof(struct twl6040), GFP_KERNEL);

sizeof(*twl6040) would allow to change the type without need to patch
this line. It's what's generally done on most drivers.

> +	if (!twl6040)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, twl6040);
> +
> +	twl6040_dev = pdev;

this is useless.

> +	twl6040->dev = &pdev->dev;
> +	twl6040->audpwron = pdata->audpwron_gpio;
> +	twl6040->irq = pdata->naudint_irq;
> +	twl6040->irq_base = pdata->irq_base;
> +
> +	mutex_init(&twl6040->mutex);
> +	mutex_init(&twl6040->io_mutex);
> +	init_completion(&twl6040->ready);
> +
> +	twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
> +
> +	if (gpio_is_valid(twl6040->audpwron)) {
> +		ret = gpio_request(twl6040->audpwron, "audpwron");
> +		if (ret)
> +			goto gpio1_err;
> +
> +		ret = gpio_direction_output(twl6040->audpwron, 0);
> +		if (ret)
> +			goto gpio2_err;
> +	}

what if gpio isn't valid ? Does it make sense to continue ? Is the GPIO
poweron pin a "must-have" or can you tie that pin to Vcc (or ground,
depending if it's active high or low) and have it working ?

> +	/* ERRATA: Automatic power-up is not possible in ES1.0 */
> +	if (twl6040_get_rev(twl6040) == TWL6040_REV_ES1_0)
> +		twl6040->audpwron = -EINVAL;
> +
> +	if (twl6040->irq) {
> +		/* codec interrupt */
> +		ret = twl6040_irq_init(twl6040);
> +		if (ret)
> +			goto gpio2_err;
> +
> +		ret = twl6040_request_irq(twl6040, TWL6040_IRQ_READY,

this is really wrong. Didn't we agree on using tradicional
request_threaded_irq() here ? Also, the IRQ number should be passed via
struct resource.

> +					  twl6040_naudint_handler, 0,
> +					  "twl6040_irq_ready", twl6040);
> +		if (ret) {
> +			dev_err(twl6040->dev, "READY IRQ request failed: %d\n",
> +				ret);
> +			goto irq_err;
> +		}
> +	}
> +
> +	/* dual-access registers controlled by I2C only */
> +	twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL);
> +
> +	if (pdata->audio) {
> +		cell = &twl6040->cells[children];
> +		cell->name = "twl6040-codec";
> +		cell->platform_data = pdata->audio;
> +		cell->pdata_size = sizeof(*pdata->audio);
> +		children++;
> +	}
> +
> +	if (pdata->vibra) {
> +		cell = &twl6040->cells[children];
> +		cell->name = "twl6040-vibra";
> +		cell->platform_data = pdata->vibra;
> +		cell->pdata_size = sizeof(*pdata->vibra);
> +		children++;
> +	}
> +
> +	if (children) {
> +		ret = mfd_add_devices(&pdev->dev, pdev->id, twl6040->cells,
> +				      children, NULL, 0);
> +		if (ret)
> +			goto mfd_err;
> +	} else {
> +		dev_err(&pdev->dev, "No platform data found for children\n");
> +		ret = -ENODEV;
> +		goto mfd_err;
> +	}
> +
> +	return 0;
> +
> +mfd_err:
> +	if (twl6040->irq)
> +		twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
> +irq_err:
> +	if (twl6040->irq)
> +		twl6040_irq_exit(twl6040);
> +gpio2_err:
> +	if (gpio_is_valid(twl6040->audpwron))
> +		gpio_free(twl6040->audpwron);
> +gpio1_err:
> +	platform_set_drvdata(pdev, NULL);
> +	kfree(twl6040);
> +	twl6040_dev = NULL;
> +	return ret;
> +}
> +
> +static int __devexit twl6040_remove(struct platform_device *pdev)
> +{
> +	struct twl6040 *twl6040 = platform_get_drvdata(pdev);
> +
> +	if (twl6040_is_powered(twl6040))
> +		twl6040_power(twl6040, 0);

disabling it twice, shouldn't be a problem, so this is also useless
check.

> +	if (gpio_is_valid(twl6040->audpwron))
> +		gpio_free(twl6040->audpwron);
> +
> +	twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);

this has to be passed via struct resource.

> +	if (twl6040->irq)
> +		twl6040_irq_exit(twl6040);
> +
> +	mfd_remove_devices(&pdev->dev);
> +	platform_set_drvdata(pdev, NULL);
> +	kfree(twl6040);
> +	twl6040_dev = NULL;
> +
> +	return 0;
> +}
> +
> +static struct platform_driver twl6040_driver = {
> +	.probe		= twl6040_probe,
> +	.remove		= __devexit_p(twl6040_remove),
> +	.driver		= {
> +		.owner	= THIS_MODULE,
> +		.name	= "twl6040-audio",
> +	},
> +};
> +
> +static int __devinit twl6040_init(void)
> +{
> +	return platform_driver_register(&twl6040_driver);
> +}
> +module_init(twl6040_init);
> +
> +static void __devexit twl6040_exit(void)
> +{
> +	platform_driver_unregister(&twl6040_driver);
> +}
> +
> +module_exit(twl6040_exit);
> +
> +MODULE_DESCRIPTION("TWL6040 MFD");
> +MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
> +MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:twl6040-audio");
> diff --git a/drivers/mfd/twl6040-irq.c b/drivers/mfd/twl6040-irq.c
> new file mode 100644
> index 0000000..ac776be
> --- /dev/null
> +++ b/drivers/mfd/twl6040-irq.c
> @@ -0,0 +1,205 @@
> +/*
> + * Interrupt controller support for TWL6040
> + *
> + * Author:     Misael Lopez Cruz <misael.lopez@ti.com>
> + *
> + * Copyright:   (C) 2011 Texas Instruments, Inc.
> + *
> + * 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/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/twl6040-codec.h>
> +
> +struct twl6040_irq_data {
> +	int mask;
> +	int status;
> +};
> +
> +static struct twl6040_irq_data twl6040_irqs[] = {
> +	{
> +		.mask = TWL6040_THMSK,
> +		.status = TWL6040_THINT,
> +	},
> +	{
> +		.mask = TWL6040_PLUGMSK,
> +		.status = TWL6040_PLUGINT | TWL6040_UNPLUGINT,
> +	},
> +	{
> +		.mask = TWL6040_HOOKMSK,
> +		.status = TWL6040_HOOKINT,
> +	},
> +	{
> +		.mask = TWL6040_HFMSK,
> +		.status = TWL6040_HFINT,
> +	},
> +	{
> +		.mask = TWL6040_VIBMSK,
> +		.status = TWL6040_VIBINT,
> +	},
> +	{
> +		.mask = TWL6040_READYMSK,
> +		.status = TWL6040_READYINT,
> +	},
> +};
> +
> +static inline
> +struct twl6040_irq_data *irq_to_twl6040_irq(struct twl6040 *twl6040,
> +					    int irq)
> +{
> +	return &twl6040_irqs[irq - twl6040->irq_base];
> +}
> +
> +static void twl6040_irq_lock(struct irq_data *data)
> +{
> +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
> +
> +	mutex_lock(&twl6040->irq_mutex);
> +}
> +
> +static void twl6040_irq_sync_unlock(struct irq_data *data)
> +{
> +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
> +
> +	/* write back to hardware any change in irq mask */
> +	if (twl6040->irq_masks_cur != twl6040->irq_masks_cache) {
> +		twl6040->irq_masks_cache = twl6040->irq_masks_cur;
> +		twl6040_reg_write(twl6040, TWL6040_REG_INTMR,
> +				  twl6040->irq_masks_cur);
> +	}
> +
> +	mutex_unlock(&twl6040->irq_mutex);
> +}
> +
> +static void twl6040_irq_enable(struct irq_data *data)
> +{
> +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
> +	struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
> +							       data->irq);
> +
> +	twl6040->irq_masks_cur &= ~irq_data->mask;
> +}
> +
> +static void twl6040_irq_disable(struct irq_data *data)
> +{
> +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
> +	struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
> +							       data->irq);
> +
> +	twl6040->irq_masks_cur |= irq_data->mask;
> +}
> +
> +static struct irq_chip twl6040_irq_chip = {
> +	.name			= "twl6040",
> +	.irq_bus_lock		= twl6040_irq_lock,
> +	.irq_bus_sync_unlock	= twl6040_irq_sync_unlock,
> +	.irq_enable		= twl6040_irq_enable,
> +	.irq_disable		= twl6040_irq_disable,
> +};
> +
> +static irqreturn_t twl6040_irq_thread(int irq, void *data)
> +{
> +	struct twl6040 *twl6040 = data;
> +	u8 intid;
> +	int i;
> +
> +	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);

what's available on this register ?? Are the status fields above bit
positions ? If so you can change this:

> +	/* apply masking and report (backwards to handle READYINT first) */
> +	for (i = ARRAY_SIZE(twl6040_irqs) - 1; i >= 0; i--) {
> +		if (twl6040->irq_masks_cur & twl6040_irqs[i].mask)
> +			intid &= ~twl6040_irqs[i].status;
> +		if (intid & twl6040_irqs[i].status)
> +			handle_nested_irq(twl6040->irq_base + i);
> +	}

into something like this:

	while (intid) {
		unsigned long pending = __ffs(intid);
		unsigned long irq;

		intid &= ~BIT(pending);
		irq = pending + twl6040->irq_base;
		handle_nested_irq(irq);
	}

and that twl6040_irq_data structure can go into the bin.

> +	/* ack unmasked irqs */
> +	twl6040_reg_write(twl6040, TWL6040_REG_INTID, intid);

I believe IRQ subsystem will handle this for you on your irq_ack() +
bus_sync_unlock() methods, right ? So you should provide them.

> +	return IRQ_HANDLED;
> +}
> +
> +int twl6040_irq_init(struct twl6040 *twl6040)
> +{
> +	int cur_irq, ret;
> +	u8 val;
> +
> +	mutex_init(&twl6040->irq_mutex);
> +
> +	/* mask the individual interrupt sources */
> +	twl6040->irq_masks_cur = TWL6040_ALLINT_MSK;
> +	twl6040->irq_masks_cache = TWL6040_ALLINT_MSK;
> +	twl6040_reg_write(twl6040, TWL6040_REG_INTMR, TWL6040_ALLINT_MSK);
> +
> +	if (!twl6040->irq) {
> +		dev_warn(twl6040->dev,
> +			 "no interrupt specified, no interrupts\n");
> +		twl6040->irq_base = 0;
> +		return 0;
> +	}
> +
> +	if (!twl6040->irq_base) {

base should be allocated here with irq_alloc_descs()

> +		dev_err(twl6040->dev,
> +			"no interrupt base specified, no interrupts\n");
> +		return 0;
> +	}
> +
> +	/* Register them with genirq */
> +	for (cur_irq = twl6040->irq_base;
> +	     cur_irq < twl6040->irq_base + ARRAY_SIZE(twl6040_irqs);
> +	     cur_irq++) {
> +		irq_set_chip_data(cur_irq, twl6040);
> +		irq_set_chip_and_handler(cur_irq, &twl6040_irq_chip,
> +					 handle_level_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. */

multiline comment style is wrong here.

> +#ifdef CONFIG_ARM
> +		set_irq_flags(cur_irq, IRQF_VALID);
> +#else
> +		irq_set_noprobe(cur_irq);
> +#endif
> +	}
> +
> +	ret = request_threaded_irq(twl6040->irq, NULL, twl6040_irq_thread,
> +				   IRQF_ONESHOT, "twl6040", twl6040);

I'm not sure you need IRQF_ONESHOT here, but that can be changed later.

> +	if (ret) {
> +		dev_err(twl6040->dev, "failed to request IRQ %d: %d\n",
> +			twl6040->irq, ret);
> +		return ret;
> +	}
> +
> +	/* reset interrupts */
> +	val = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
> +
> +	/* interrupts cleared on write */
> +	twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_INTCLRMODE);

you should do these two before request_threaded_irq(), you might have a
real IRQ which will be missed.

> +	return 0;
> +}
> +EXPORT_SYMBOL(twl6040_irq_init);

EXPORT_SYMBOL_GPL()

> +void twl6040_irq_exit(struct twl6040 *twl6040)
> +{
> +	if (twl6040->irq)

this check should always be true. You only call this on error path of
probe() and exit path.

> +		free_irq(twl6040->irq, twl6040);
> +}
> +EXPORT_SYMBOL(twl6040_irq_exit);

EXPORT_SYMBOL_GPL()

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator"
  2011-08-02 11:28 ` [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator" Peter Ujfalusi
@ 2011-08-02 12:33   ` Felipe Balbi
  2011-08-02 12:37     ` Péter Ujfalusi
  0 siblings, 1 reply; 11+ messages in thread
From: Felipe Balbi @ 2011-08-02 12:33 UTC (permalink / raw)
  To: Peter Ujfalusi
  Cc: Liam Girdwood, Tony Lindgren, Mark Brown, linux-omap, alsa-devel,
	Misael Lopez Cruz, Benoit Cousson, Sebastien Guiriec

[-- Attachment #1: Type: text/plain, Size: 1083 bytes --]

Hi,

On Tue, Aug 02, 2011 at 02:28:42PM +0300, Peter Ujfalusi wrote:
> @@ -145,7 +143,7 @@ static int vibra_play(struct input_dev *input, void *data,
>  
>  	ret = queue_work(info->workqueue, &info->play_work);
>  	if (!ret) {
> -		dev_err(&input->dev, "work is already on queue\n");
> +		dev_info(&input->dev, "work is already on queue\n");

why ???

>  		return ret;
>  	}
>  
> @@ -266,12 +264,10 @@ static int __devinit twl6040_vibra_probe(struct platform_device *pdev)
>  		goto err_irq;
>  	}
>  
> -	printk(KERN_ERR "%s:powering twl6040\n", __func__);
>  	ret = twl6040_power(info->twl6040, 1);
>  	if (ret < 0)
>  		goto err_pwr;
>  
> -	printk(KERN_ERR "%s:powered\n", __func__);

this hunk is not part of this patch.

>  	return 0;
>  
>  err_pwr:
> @@ -297,7 +293,6 @@ static int __devexit twl6040_vibra_remove(struct platform_device *pdev)
>  	twl6040_free_irq(info->twl6040, TWL6040_IRQ_VIB, info);
>  	input_unregister_device(info->input_dev);
>  	kfree(info);
> -	platform_set_drvdata(pdev, NULL);

neither is this.

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [alsa-devel] [PATCH 1/3] mfd: twl6040: Add initial support
  2011-08-02 11:32 ` [PATCH 1/3] mfd: twl6040: Add initial support Péter Ujfalusi
@ 2011-08-02 12:36   ` Felipe Balbi
  0 siblings, 0 replies; 11+ messages in thread
From: Felipe Balbi @ 2011-08-02 12:36 UTC (permalink / raw)
  To: Péter Ujfalusi
  Cc: alsa-devel, Girdwood, Liam, Tony Lindgren, Mark Brown,
	linux-omap@vger.kernel.org, Cousson, Benoit, Guiriec, Sebastien,
	Sebastien@alsa-project.org, Lopez Cruz, Misael,
	Misael@alsa-project.org

[-- Attachment #1: Type: text/plain, Size: 970 bytes --]

On Tue, Aug 02, 2011 at 02:32:48PM +0300, Péter Ujfalusi wrote:
> Oops...
> 
> On Tuesday 02 August 2011 13:28:41 Ujfalusi, Peter wrote:
> > From: Misael Lopez Cruz <misael.lopez@ti.com>
> > 
> > TWL6040 IC provides analog high-end audio codec functions for
> > handset applications. It contains several audio analog inputs
> > and outputs as well as vibrator support. It's connected to the
> > host processor via PDM interface for audio data communication.
> > The audio modules are controlled by internal registers that
> > can be accessed by I2C and PDM interface.
> > 
> > TWL6040 MFD will be registered as a child of TWL-CORE, and will
> > have two children of its own: twl6040-codec and twl6040-vibra.
> > 
> > This driver is based on TWL4030 and WM8350 MFD drivers.
> 
> I have sent the patches from wrong directory :(
> Please disregard this series, this has been already merged...

too bad... You have missed my comments :-p

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Re: [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator"
  2011-08-02 12:33   ` Felipe Balbi
@ 2011-08-02 12:37     ` Péter Ujfalusi
  0 siblings, 0 replies; 11+ messages in thread
From: Péter Ujfalusi @ 2011-08-02 12:37 UTC (permalink / raw)
  To: Balbi, Felipe
  Cc: Girdwood, Liam, Tony Lindgren, Mark Brown,
	linux-omap@vger.kernel.org, alsa-devel@alsa-project.org,
	Lopez Cruz, Misael, Cousson, Benoit, Guiriec, Sebastien

Hi Felipe,

On Tuesday 02 August 2011 14:33:09 Balbi, Felipe wrote:
> Hi,
> 
> On Tue, Aug 02, 2011 at 02:28:42PM +0300, Peter Ujfalusi wrote:
> > @@ -145,7 +143,7 @@ static int vibra_play(struct input_dev *input, void
> > *data,> 
> >  	ret = queue_work(info->workqueue, &info->play_work);
> >  	if (!ret) {
> > 
> > -		dev_err(&input->dev, "work is already on queue\n");
> > +		dev_info(&input->dev, "work is already on queue\n");
> 
> why ???

Please do not waste your valuable time on this series.
It was an error in my side to send it (just came back form vacation...).
I have somehow ended up in a wrong directory when I typed git send-email, and 
sent the wrong set of patches.
Not these, but the fixed/cleaned ones are already merged upstream.

Sorry for the inconvenience I have caused.

-- 
Péter
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2011-08-02 12:37 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-08-02 11:28 [PATCH 1/3] mfd: twl6040: Add initial support Peter Ujfalusi
2011-08-02 11:28 ` [PATCH 2/4] Fixes for "input: Add initial support for TWL6040 vibrator" Peter Ujfalusi
2011-08-02 12:33   ` Felipe Balbi
2011-08-02 12:37     ` Péter Ujfalusi
2011-08-02 11:28 ` [PATCH 2/3] input: Add initial support for TWL6040 vibrator Peter Ujfalusi
2011-08-02 11:28 ` [PATCH 3/3] ASoC: twl6040: Convert into TWL6040 MFD child Peter Ujfalusi
2011-08-02 11:28 ` [PATCH 3/4] Calculate max VIBDAT code based on VDDVIB, motor and driver resistances Peter Ujfalusi
2011-08-02 11:28 ` [PATCH 4/4] OMAP4: SDP4430: Add vibrator platform data Peter Ujfalusi
2011-08-02 11:32 ` [PATCH 1/3] mfd: twl6040: Add initial support Péter Ujfalusi
2011-08-02 12:36   ` [alsa-devel] " Felipe Balbi
2011-08-02 12:31 ` Felipe Balbi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).