aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig32
-rw-r--r--drivers/mfd/Makefile4
-rw-r--r--drivers/mfd/omap-usb-host.c385
-rw-r--r--drivers/mfd/twl-core.c47
-rw-r--r--drivers/mfd/twl6030-irq.c172
-rw-r--r--drivers/mfd/twl6030-madc.c354
-rw-r--r--drivers/mfd/twl6030-power.c251
-rw-r--r--drivers/mfd/twl6030-poweroff.c75
-rw-r--r--drivers/mfd/twl6040-codec.c820
-rw-r--r--drivers/mfd/twl6040-irq.c196
10 files changed, 2045 insertions, 291 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6ca938a..8f83bfc 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -218,6 +218,18 @@ config TWL4030_POWER
and load scripts controlling which resources are switched off/on
or reset when a sleep, wakeup or warm reset event occurs.
+config TWL6030_POWER
+ bool "Support power resources on TWL6030 family chips"
+ depends on TWL4030_CORE
+ help
+ Say yes here if you want to use the power resources on the
+ TWL6030 family chips. Most of these resources are regulators,
+ which have a separate driver; some are control signals, such
+ as clock request handshaking.
+
+ This driver defaults to assuming only APPs processor uses
+ the resource, it can however be overridden by board file
+
config TWL4030_CODEC
bool
depends on TWL4030_CORE
@@ -233,6 +245,26 @@ config TWL6030_PWM
Say yes here if you want support for TWL6030 PWM.
This is used to control charging LED brightness.
+config TWL6030_POWEROFF
+ bool "TWL6030 device poweroff"
+ depends on TWL4030_CORE
+
+config TWL6030_MADC
+ tristate "Texas Instruments TWL6030 MADC"
+ depends on TWL4030_CORE
+ help
+ This driver provides support for TWL6030-MADC. The
+ driver supports both RT and SW conversion methods.
+
+ This driver can be built as a module. If so it will be
+ named twl6030-madc
+
+config TWL6040_CODEC
+ bool
+ depends on TWL4030_CORE
+ 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 d7d47d2..60f9021 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -42,6 +42,10 @@ 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_TWL6030_MADC) += twl6030-madc.o
+obj-$(CONFIG_TWL6040_CODEC) += twl6040-codec.o twl6040-irq.o
+obj-$(CONFIG_TWL6030_POWEROFF) += twl6030-poweroff.o
+obj-$(CONFIG_TWL6030_POWER) += twl6030-power.o
obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c
index e67c3d3..212a338 100644
--- a/drivers/mfd/omap-usb-host.c
+++ b/drivers/mfd/omap-usb-host.c
@@ -26,8 +26,9 @@
#include <linux/spinlock.h>
#include <linux/gpio.h>
#include <plat/usb.h>
+#include <linux/pm_runtime.h>
-#define USBHS_DRIVER_NAME "usbhs-omap"
+#define USBHS_DRIVER_NAME "usbhs_omap"
#define OMAP_EHCI_DEVICE "ehci-omap"
#define OMAP_OHCI_DEVICE "ohci-omap3"
@@ -146,9 +147,6 @@
struct usbhs_hcd_omap {
- struct clk *usbhost_ick;
- struct clk *usbhost_hs_fck;
- struct clk *usbhost_fs_fck;
struct clk *xclk60mhsp1_ck;
struct clk *xclk60mhsp2_ck;
struct clk *utmi_p1_fck;
@@ -158,8 +156,6 @@ struct usbhs_hcd_omap {
struct clk *usbhost_p2_fck;
struct clk *usbtll_p2_fck;
struct clk *init_60m_fclk;
- struct clk *usbtll_fck;
- struct clk *usbtll_ick;
void __iomem *uhh_base;
void __iomem *tll_base;
@@ -168,7 +164,6 @@ struct usbhs_hcd_omap {
u32 usbhs_rev;
spinlock_t lock;
- int count;
};
/*-------------------------------------------------------------------------*/
@@ -318,6 +313,7 @@ err_end:
return ret;
}
+static void omap_usbhs_init(struct device *dev);
/**
* usbhs_omap_probe - initialize TI-based HCDs
*
@@ -353,46 +349,13 @@ static int __devinit usbhs_omap_probe(struct platform_device *pdev)
omap->platdata.ehci_data = pdata->ehci_data;
omap->platdata.ohci_data = pdata->ohci_data;
- omap->usbhost_ick = clk_get(dev, "usbhost_ick");
- if (IS_ERR(omap->usbhost_ick)) {
- ret = PTR_ERR(omap->usbhost_ick);
- dev_err(dev, "usbhost_ick failed error:%d\n", ret);
- goto err_end;
- }
-
- omap->usbhost_hs_fck = clk_get(dev, "hs_fck");
- if (IS_ERR(omap->usbhost_hs_fck)) {
- ret = PTR_ERR(omap->usbhost_hs_fck);
- dev_err(dev, "usbhost_hs_fck failed error:%d\n", ret);
- goto err_usbhost_ick;
- }
-
- omap->usbhost_fs_fck = clk_get(dev, "fs_fck");
- if (IS_ERR(omap->usbhost_fs_fck)) {
- ret = PTR_ERR(omap->usbhost_fs_fck);
- dev_err(dev, "usbhost_fs_fck failed error:%d\n", ret);
- goto err_usbhost_hs_fck;
- }
-
- omap->usbtll_fck = clk_get(dev, "usbtll_fck");
- if (IS_ERR(omap->usbtll_fck)) {
- ret = PTR_ERR(omap->usbtll_fck);
- dev_err(dev, "usbtll_fck failed error:%d\n", ret);
- goto err_usbhost_fs_fck;
- }
-
- omap->usbtll_ick = clk_get(dev, "usbtll_ick");
- if (IS_ERR(omap->usbtll_ick)) {
- ret = PTR_ERR(omap->usbtll_ick);
- dev_err(dev, "usbtll_ick failed error:%d\n", ret);
- goto err_usbtll_fck;
- }
+ pm_runtime_enable(dev);
omap->utmi_p1_fck = clk_get(dev, "utmi_p1_gfclk");
if (IS_ERR(omap->utmi_p1_fck)) {
ret = PTR_ERR(omap->utmi_p1_fck);
dev_err(dev, "utmi_p1_gfclk failed error:%d\n", ret);
- goto err_usbtll_ick;
+ goto err_end;
}
omap->xclk60mhsp1_ck = clk_get(dev, "xclk60mhsp1_ck");
@@ -451,6 +414,35 @@ static int __devinit usbhs_omap_probe(struct platform_device *pdev)
goto err_usbtll_p2_fck;
}
+ if (is_ehci_phy_mode(pdata->port_mode[0])) {
+ /* for OMAP3 , the clk set paretn fails */
+ ret = clk_set_parent(omap->utmi_p1_fck,
+ omap->xclk60mhsp1_ck);
+ if (ret != 0)
+ dev_err(dev, "xclk60mhsp1_ck set parent"
+ "failed error:%d\n", ret);
+ } else if (is_ehci_tll_mode(pdata->port_mode[0])) {
+ ret = clk_set_parent(omap->utmi_p1_fck,
+ omap->init_60m_fclk);
+ if (ret != 0)
+ dev_err(dev, "init_60m_fclk set parent"
+ "failed error:%d\n", ret);
+ }
+
+ if (is_ehci_phy_mode(pdata->port_mode[1])) {
+ ret = clk_set_parent(omap->utmi_p2_fck,
+ omap->xclk60mhsp2_ck);
+ if (ret != 0)
+ dev_err(dev, "xclk60mhsp2_ck set parent"
+ "failed error:%d\n", ret);
+ } else if (is_ehci_tll_mode(pdata->port_mode[1])) {
+ ret = clk_set_parent(omap->utmi_p2_fck,
+ omap->init_60m_fclk);
+ if (ret != 0)
+ dev_err(dev, "init_60m_fclk set parent"
+ "failed error:%d\n", ret);
+ }
+
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "uhh");
if (!res) {
dev_err(dev, "UHH EHCI get resource failed\n");
@@ -487,6 +479,8 @@ static int __devinit usbhs_omap_probe(struct platform_device *pdev)
goto err_alloc;
}
+ omap_usbhs_init(dev);
+
goto end_probe;
err_alloc:
@@ -522,28 +516,15 @@ err_xclk60mhsp1_ck:
err_utmi_p1_fck:
clk_put(omap->utmi_p1_fck);
-err_usbtll_ick:
- clk_put(omap->usbtll_ick);
-
-err_usbtll_fck:
- clk_put(omap->usbtll_fck);
-
-err_usbhost_fs_fck:
- clk_put(omap->usbhost_fs_fck);
-
-err_usbhost_hs_fck:
- clk_put(omap->usbhost_hs_fck);
-
-err_usbhost_ick:
- clk_put(omap->usbhost_ick);
-
err_end:
+ pm_runtime_disable(dev);
kfree(omap);
end_probe:
return ret;
}
+static void omap_usbhs_deinit(struct device *dev);
/**
* usbhs_omap_remove - shutdown processing for UHH & TLL HCDs
* @pdev: USB Host Controller being removed
@@ -554,12 +535,7 @@ static int __devexit usbhs_omap_remove(struct platform_device *pdev)
{
struct usbhs_hcd_omap *omap = platform_get_drvdata(pdev);
- if (omap->count != 0) {
- dev_err(&pdev->dev,
- "Either EHCI or OHCI is still using usbhs core\n");
- return -EBUSY;
- }
-
+ omap_usbhs_deinit(&pdev->dev);
iounmap(omap->tll_base);
iounmap(omap->uhh_base);
clk_put(omap->init_60m_fclk);
@@ -571,11 +547,7 @@ static int __devexit usbhs_omap_remove(struct platform_device *pdev)
clk_put(omap->utmi_p2_fck);
clk_put(omap->xclk60mhsp1_ck);
clk_put(omap->utmi_p1_fck);
- clk_put(omap->usbtll_ick);
- clk_put(omap->usbtll_fck);
- clk_put(omap->usbhost_fs_fck);
- clk_put(omap->usbhost_hs_fck);
- clk_put(omap->usbhost_ick);
+ pm_runtime_disable(&pdev->dev);
kfree(omap);
return 0;
@@ -688,30 +660,72 @@ static void usbhs_omap_tll_init(struct device *dev, u8 tll_channel_count)
}
}
-static int usbhs_enable(struct device *dev)
+static int usbhs_runtime_resume(struct device *dev)
{
struct usbhs_hcd_omap *omap = dev_get_drvdata(dev);
struct usbhs_omap_platform_data *pdata = &omap->platdata;
- unsigned long flags = 0;
- int ret = 0;
- unsigned long timeout;
- unsigned reg;
- dev_dbg(dev, "starting TI HSUSB Controller\n");
+ dev_dbg(dev, "usbhs_runtime_resume\n");
+
if (!pdata) {
dev_dbg(dev, "missing platform_data\n");
return -ENODEV;
}
- spin_lock_irqsave(&omap->lock, flags);
- if (omap->count > 0)
- goto end_count;
+ if (is_omap_usbhs_rev2(omap)) {
+ if (is_ehci_tll_mode(pdata->port_mode[0])) {
+ clk_enable(omap->usbhost_p1_fck);
+ clk_enable(omap->usbtll_p1_fck);
+ }
+ if (is_ehci_tll_mode(pdata->port_mode[1])) {
+ clk_enable(omap->usbhost_p2_fck);
+ clk_enable(omap->usbtll_p2_fck);
+ }
+ clk_enable(omap->utmi_p1_fck);
+ clk_enable(omap->utmi_p2_fck);
+ }
+ return 0;
+}
+
+static int usbhs_runtime_suspend(struct device *dev)
+{
+ struct usbhs_hcd_omap *omap = dev_get_drvdata(dev);
+ struct usbhs_omap_platform_data *pdata = &omap->platdata;
+
+ dev_dbg(dev, "usbhs_runtime_suspend\n");
- clk_enable(omap->usbhost_ick);
- clk_enable(omap->usbhost_hs_fck);
- clk_enable(omap->usbhost_fs_fck);
- clk_enable(omap->usbtll_fck);
- clk_enable(omap->usbtll_ick);
+ if (!pdata) {
+ dev_dbg(dev, "missing platform_data\n");
+ return -ENODEV;
+ }
+
+ if (is_omap_usbhs_rev2(omap)) {
+ if (is_ehci_tll_mode(pdata->port_mode[0])) {
+ clk_disable(omap->usbhost_p1_fck);
+ clk_disable(omap->usbtll_p1_fck);
+ }
+ if (is_ehci_tll_mode(pdata->port_mode[1])) {
+ clk_disable(omap->usbhost_p2_fck);
+ clk_disable(omap->usbtll_p2_fck);
+ }
+ clk_disable(omap->utmi_p2_fck);
+ clk_disable(omap->utmi_p1_fck);
+ }
+ return 0;
+}
+
+static void omap_usbhs_init(struct device *dev)
+{
+ struct usbhs_hcd_omap *omap = dev_get_drvdata(dev);
+ struct usbhs_omap_platform_data *pdata = &omap->platdata;
+ unsigned long flags = 0;
+ unsigned reg;
+
+ dev_dbg(dev, "starting TI HSUSB Controller\n");
+
+ pm_runtime_get_sync(dev);
+
+ spin_lock_irqsave(&omap->lock, flags);
if (pdata->ehci_data->phy_reset) {
if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0])) {
@@ -735,49 +749,13 @@ static int usbhs_enable(struct device *dev)
omap->usbhs_rev = usbhs_read(omap->uhh_base, OMAP_UHH_REVISION);
dev_dbg(dev, "OMAP UHH_REVISION 0x%x\n", omap->usbhs_rev);
- /* perform TLL soft reset, and wait until reset is complete */
- usbhs_write(omap->tll_base, OMAP_USBTLL_SYSCONFIG,
- OMAP_USBTLL_SYSCONFIG_SOFTRESET);
-
- /* Wait for TLL reset to complete */
- timeout = jiffies + msecs_to_jiffies(1000);
- while (!(usbhs_read(omap->tll_base, OMAP_USBTLL_SYSSTATUS)
- & OMAP_USBTLL_SYSSTATUS_RESETDONE)) {
- cpu_relax();
-
- if (time_after(jiffies, timeout)) {
- dev_dbg(dev, "operation timed out\n");
- ret = -EINVAL;
- goto err_tll;
- }
- }
-
- dev_dbg(dev, "TLL RESET DONE\n");
-
- /* (1<<3) = no idle mode only for initial debugging */
- usbhs_write(omap->tll_base, OMAP_USBTLL_SYSCONFIG,
- OMAP_USBTLL_SYSCONFIG_ENAWAKEUP |
- OMAP_USBTLL_SYSCONFIG_SIDLEMODE |
- OMAP_USBTLL_SYSCONFIG_AUTOIDLE);
-
- /* Put UHH in NoIdle/NoStandby mode */
- reg = usbhs_read(omap->uhh_base, OMAP_UHH_SYSCONFIG);
- if (is_omap_usbhs_rev1(omap)) {
- reg |= (OMAP_UHH_SYSCONFIG_ENAWAKEUP
- | OMAP_UHH_SYSCONFIG_SIDLEMODE
- | OMAP_UHH_SYSCONFIG_CACTIVITY
- | OMAP_UHH_SYSCONFIG_MIDLEMODE);
- reg &= ~OMAP_UHH_SYSCONFIG_AUTOIDLE;
-
-
- } else if (is_omap_usbhs_rev2(omap)) {
- reg &= ~OMAP4_UHH_SYSCONFIG_IDLEMODE_CLEAR;
- reg |= OMAP4_UHH_SYSCONFIG_NOIDLE;
- reg &= ~OMAP4_UHH_SYSCONFIG_STDBYMODE_CLEAR;
- reg |= OMAP4_UHH_SYSCONFIG_NOSTDBY;
- }
-
- usbhs_write(omap->uhh_base, OMAP_UHH_SYSCONFIG, reg);
+ /*
+ * Really enable the port clocks
+ * first call of pm_runtime_get_sync does not enable these
+ * port clocks; because omap->usbhs_rev was not available
+ * This omap->usbhs_rev is available now!
+ */
+ usbhs_runtime_resume(dev);
reg = usbhs_read(omap->uhh_base, OMAP_UHH_HOSTCONFIG);
/* setup ULPI bypass and burst configurations */
@@ -824,49 +802,6 @@ static int usbhs_enable(struct device *dev)
reg &= ~OMAP4_P1_MODE_CLEAR;
reg &= ~OMAP4_P2_MODE_CLEAR;
- if (is_ehci_phy_mode(pdata->port_mode[0])) {
- ret = clk_set_parent(omap->utmi_p1_fck,
- omap->xclk60mhsp1_ck);
- if (ret != 0) {
- dev_err(dev, "xclk60mhsp1_ck set parent"
- "failed error:%d\n", ret);
- goto err_tll;
- }
- } else if (is_ehci_tll_mode(pdata->port_mode[0])) {
- ret = clk_set_parent(omap->utmi_p1_fck,
- omap->init_60m_fclk);
- if (ret != 0) {
- dev_err(dev, "init_60m_fclk set parent"
- "failed error:%d\n", ret);
- goto err_tll;
- }
- clk_enable(omap->usbhost_p1_fck);
- clk_enable(omap->usbtll_p1_fck);
- }
-
- if (is_ehci_phy_mode(pdata->port_mode[1])) {
- ret = clk_set_parent(omap->utmi_p2_fck,
- omap->xclk60mhsp2_ck);
- if (ret != 0) {
- dev_err(dev, "xclk60mhsp1_ck set parent"
- "failed error:%d\n", ret);
- goto err_tll;
- }
- } else if (is_ehci_tll_mode(pdata->port_mode[1])) {
- ret = clk_set_parent(omap->utmi_p2_fck,
- omap->init_60m_fclk);
- if (ret != 0) {
- dev_err(dev, "init_60m_fclk set parent"
- "failed error:%d\n", ret);
- goto err_tll;
- }
- clk_enable(omap->usbhost_p2_fck);
- clk_enable(omap->usbtll_p2_fck);
- }
-
- clk_enable(omap->utmi_p1_fck);
- clk_enable(omap->utmi_p2_fck);
-
if (is_ehci_tll_mode(pdata->port_mode[0]) ||
(is_ohci_port(pdata->port_mode[0])))
reg |= OMAP4_P1_MODE_TLL;
@@ -912,107 +847,17 @@ static int usbhs_enable(struct device *dev)
(pdata->ehci_data->reset_gpio_port[1], 1);
}
-end_count:
- omap->count++;
- spin_unlock_irqrestore(&omap->lock, flags);
- return 0;
-
-err_tll:
- if (pdata->ehci_data->phy_reset) {
- if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0]))
- gpio_free(pdata->ehci_data->reset_gpio_port[0]);
-
- if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[1]))
- gpio_free(pdata->ehci_data->reset_gpio_port[1]);
- }
-
- clk_disable(omap->usbtll_ick);
- clk_disable(omap->usbtll_fck);
- clk_disable(omap->usbhost_fs_fck);
- clk_disable(omap->usbhost_hs_fck);
- clk_disable(omap->usbhost_ick);
spin_unlock_irqrestore(&omap->lock, flags);
- return ret;
+ pm_runtime_put_sync(dev);
}
-static void usbhs_disable(struct device *dev)
+static void omap_usbhs_deinit(struct device *dev)
{
struct usbhs_hcd_omap *omap = dev_get_drvdata(dev);
struct usbhs_omap_platform_data *pdata = &omap->platdata;
- unsigned long flags = 0;
- unsigned long timeout;
dev_dbg(dev, "stopping TI HSUSB Controller\n");
- spin_lock_irqsave(&omap->lock, flags);
-
- if (omap->count == 0)
- goto end_disble;
-
- omap->count--;
-
- if (omap->count != 0)
- goto end_disble;
-
- /* Reset OMAP modules for insmod/rmmod to work */
- usbhs_write(omap->uhh_base, OMAP_UHH_SYSCONFIG,
- is_omap_usbhs_rev2(omap) ?
- OMAP4_UHH_SYSCONFIG_SOFTRESET :
- OMAP_UHH_SYSCONFIG_SOFTRESET);
-
- timeout = jiffies + msecs_to_jiffies(100);
- while (!(usbhs_read(omap->uhh_base, OMAP_UHH_SYSSTATUS)
- & (1 << 0))) {
- cpu_relax();
-
- if (time_after(jiffies, timeout))
- dev_dbg(dev, "operation timed out\n");
- }
-
- while (!(usbhs_read(omap->uhh_base, OMAP_UHH_SYSSTATUS)
- & (1 << 1))) {
- cpu_relax();
-
- if (time_after(jiffies, timeout))
- dev_dbg(dev, "operation timed out\n");
- }
-
- while (!(usbhs_read(omap->uhh_base, OMAP_UHH_SYSSTATUS)
- & (1 << 2))) {
- cpu_relax();
-
- if (time_after(jiffies, timeout))
- dev_dbg(dev, "operation timed out\n");
- }
-
- usbhs_write(omap->tll_base, OMAP_USBTLL_SYSCONFIG, (1 << 1));
-
- while (!(usbhs_read(omap->tll_base, OMAP_USBTLL_SYSSTATUS)
- & (1 << 0))) {
- cpu_relax();
-
- if (time_after(jiffies, timeout))
- dev_dbg(dev, "operation timed out\n");
- }
-
- if (is_omap_usbhs_rev2(omap)) {
- if (is_ehci_tll_mode(pdata->port_mode[0]))
- clk_enable(omap->usbtll_p1_fck);
- if (is_ehci_tll_mode(pdata->port_mode[1]))
- clk_enable(omap->usbtll_p2_fck);
- clk_disable(omap->utmi_p2_fck);
- clk_disable(omap->utmi_p1_fck);
- }
-
- clk_disable(omap->usbtll_ick);
- clk_disable(omap->usbtll_fck);
- clk_disable(omap->usbhost_fs_fck);
- clk_disable(omap->usbhost_hs_fck);
- clk_disable(omap->usbhost_ick);
-
- /* The gpio_free migh sleep; so unlock the spinlock */
- spin_unlock_irqrestore(&omap->lock, flags);
-
if (pdata->ehci_data->phy_reset) {
if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0]))
gpio_free(pdata->ehci_data->reset_gpio_port[0]);
@@ -1020,28 +865,18 @@ static void usbhs_disable(struct device *dev)
if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[1]))
gpio_free(pdata->ehci_data->reset_gpio_port[1]);
}
- return;
-
-end_disble:
- spin_unlock_irqrestore(&omap->lock, flags);
}
-int omap_usbhs_enable(struct device *dev)
-{
- return usbhs_enable(dev->parent);
-}
-EXPORT_SYMBOL_GPL(omap_usbhs_enable);
-
-void omap_usbhs_disable(struct device *dev)
-{
- usbhs_disable(dev->parent);
-}
-EXPORT_SYMBOL_GPL(omap_usbhs_disable);
+static const struct dev_pm_ops usbhsomap_dev_pm_ops = {
+ .runtime_suspend = usbhs_runtime_suspend,
+ .runtime_resume = usbhs_runtime_resume,
+};
static struct platform_driver usbhs_omap_driver = {
.driver = {
.name = (char *)usbhs_driver_name,
.owner = THIS_MODULE,
+ .pm = &usbhsomap_dev_pm_ops,
},
.remove = __exit_p(usbhs_omap_remove),
};
diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c
index f82413a..953189c 100644
--- a/drivers/mfd/twl-core.c
+++ b/drivers/mfd/twl-core.c
@@ -83,7 +83,7 @@
#define twl_has_madc() false
#endif
-#ifdef CONFIG_TWL4030_POWER
+#if defined(CONFIG_TWL4030_POWER) || defined(CONFIG_TWL6030_POWER)
#define twl_has_power() true
#else
#define twl_has_power() false
@@ -126,6 +126,7 @@
/* Last - for index max*/
#define TWL4030_MODULE_LAST TWL4030_MODULE_SECURED_REG
+#define TWL6030_MODULE_LAST TWL6030_MODULE_SLAVE_RES
#define TWL_NUM_SLAVES 4
@@ -141,7 +142,7 @@
#define SUB_CHIP_ID2 2
#define SUB_CHIP_ID3 3
-#define TWL_MODULE_LAST TWL4030_MODULE_LAST
+#define TWL_MODULE_LAST TWL6030_MODULE_LAST
/* Base Address defns for twl4030_map[] */
@@ -187,6 +188,7 @@
#define TWL6030_BASEADD_MEM 0x0017
#define TWL6030_BASEADD_PM_MASTER 0x001F
#define TWL6030_BASEADD_PM_SLAVE_MISC 0x0030 /* PM_RECEIVER */
+#define TWL6030_BASEADD_PM_SLAVE_RES 0x00AD
#define TWL6030_BASEADD_PM_MISC 0x00E2
#define TWL6030_BASEADD_PM_PUPD 0x00F0
@@ -333,6 +335,7 @@ static struct twl_mapping twl6030_map[] = {
{ SUB_CHIP_ID0, TWL6030_BASEADD_RTC },
{ SUB_CHIP_ID0, TWL6030_BASEADD_MEM },
{ SUB_CHIP_ID1, TWL6025_BASEADD_CHARGER },
+ { SUB_CHIP_ID0, TWL6030_BASEADD_PM_SLAVE_RES },
};
/*----------------------------------------------------------------------*/
@@ -386,8 +389,9 @@ int twl_i2c_write(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes)
/* i2c_transfer returns number of messages transferred */
if (ret != 1) {
- pr_err("%s: i2c_write failed to transfer all messages\n",
- DRIVER_NAME);
+ pr_err("%s: i2c_write failed to transfer all messages "
+ "(addr 0x%04x, reg %d, len %d)\n",
+ DRIVER_NAME, twl->address, reg, msg->len);
if (ret < 0)
return ret;
else
@@ -445,8 +449,9 @@ int twl_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes)
/* i2c_transfer returns number of messages transferred */
if (ret != 2) {
- pr_err("%s: i2c_read failed to transfer all messages\n",
- DRIVER_NAME);
+ pr_err("%s: i2c_read failed to transfer all messages "
+ "(addr 0x%04x, reg %d, len %d)\n",
+ DRIVER_NAME, twl->address, reg, msg->len);
if (ret < 0)
return ret;
else
@@ -827,7 +832,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))
@@ -970,6 +975,26 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
features);
if (IS_ERR(child))
return PTR_ERR(child);
+
+ child = add_regulator(TWL6030_REG_CLK32KAUDIO,
+ pdata->clk32kaudio, features);
+ if (IS_ERR(child))
+ return PTR_ERR(child);
+
+ child = add_regulator(TWL6030_REG_VDD3, pdata->vdd3,
+ features);
+ if (IS_ERR(child))
+ return PTR_ERR(child);
+
+ child = add_regulator(TWL6030_REG_VMEM, pdata->vmem,
+ features);
+ if (IS_ERR(child))
+ return PTR_ERR(child);
+
+ child = add_regulator(TWL6030_REG_V2V1, pdata->v2v1,
+ features);
+ if (IS_ERR(child))
+ return PTR_ERR(child);
}
/* 6030 and 6025 share this regulator */
@@ -1238,8 +1263,12 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id)
}
/* load power event scripts */
- if (twl_has_power() && pdata->power)
- twl4030_power_init(pdata->power);
+ if (twl_has_power()) {
+ if (twl_class_is_4030() && pdata->power)
+ twl4030_power_init(pdata->power);
+ if (twl_class_is_6030())
+ twl6030_power_init(pdata->power);
+ }
/* Maybe init the T2 Interrupt subsystem */
if (client->irq
diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c
index b0563b6..fa18b02 100644
--- a/drivers/mfd/twl6030-irq.c
+++ b/drivers/mfd/twl6030-irq.c
@@ -37,6 +37,8 @@
#include <linux/kthread.h>
#include <linux/i2c/twl.h>
#include <linux/platform_device.h>
+#include <linux/suspend.h>
+#include <linux/reboot.h>
#include "twl-core.h"
@@ -55,7 +57,7 @@
static int twl6030_interrupt_mapping[24] = {
PWR_INTR_OFFSET, /* Bit 0 PWRON */
PWR_INTR_OFFSET, /* Bit 1 RPWRON */
- PWR_INTR_OFFSET, /* Bit 2 BAT_VLOW */
+ TWL_VLOW_INTR_OFFSET, /* Bit 2 BAT_VLOW */
RTC_INTR_OFFSET, /* Bit 3 RTC_ALARM */
RTC_INTR_OFFSET, /* Bit 4 RTC_PERIOD */
HOTDIE_INTR_OFFSET, /* Bit 5 HOT_DIE */
@@ -82,9 +84,50 @@ static int twl6030_interrupt_mapping[24] = {
};
/*----------------------------------------------------------------------*/
-static unsigned twl6030_irq_base;
+static unsigned twl6030_irq_base, twl6030_irq_end;
+static int twl_irq;
+static bool twl_irq_wake_enabled;
+static struct task_struct *task;
static struct completion irq_event;
+static atomic_t twl6030_wakeirqs = ATOMIC_INIT(0);
+
+static int twl6030_irq_pm_notifier(struct notifier_block *notifier,
+ unsigned long pm_event, void *unused)
+{
+ int chained_wakeups;
+
+ switch (pm_event) {
+ case PM_SUSPEND_PREPARE:
+ chained_wakeups = atomic_read(&twl6030_wakeirqs);
+
+ if (chained_wakeups && !twl_irq_wake_enabled) {
+ if (enable_irq_wake(twl_irq))
+ pr_err("twl6030 IRQ wake enable failed\n");
+ else
+ twl_irq_wake_enabled = true;
+ } else if (!chained_wakeups && twl_irq_wake_enabled) {
+ disable_irq_wake(twl_irq);
+ twl_irq_wake_enabled = false;
+ }
+
+ disable_irq(twl_irq);
+ break;
+
+ case PM_POST_SUSPEND:
+ enable_irq(twl_irq);
+ break;
+
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block twl6030_irq_pm_notifier_block = {
+ .notifier_call = twl6030_irq_pm_notifier,
+};
/*
* This thread processes interrupts reported by the Primary Interrupt Handler.
@@ -104,6 +147,7 @@ static int twl6030_irq_thread(void *data)
u8 bytes[4];
u32 int_sts;
} sts;
+ u32 int_sts; /* sts.int_sts converted to CPU endianness */
/* Wait for IRQ, then read PIH irq status (also blocking) */
wait_for_completion_interruptible(&irq_event);
@@ -135,9 +179,10 @@ static int twl6030_irq_thread(void *data)
if (sts.bytes[2] & 0x10)
sts.bytes[2] |= 0x08;
- for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) {
+ int_sts = le32_to_cpu(sts.int_sts);
+ for (i = 0; int_sts; int_sts >>= 1, i++) {
local_irq_disable();
- if (sts.int_sts & 0x1) {
+ if (int_sts & 0x1) {
int module_irq = twl6030_irq_base +
twl6030_interrupt_mapping[i];
generic_handle_irq(module_irq);
@@ -181,6 +226,17 @@ static irqreturn_t handle_twl6030_pih(int irq, void *devid)
return IRQ_HANDLED;
}
+/*
+ * handle_twl6030_vlow() is a threaded BAT_VLOW interrupt handler. BAT_VLOW
+ * is a secondary interrupt generated in twl6030_irq_thread().
+ */
+static irqreturn_t handle_twl6030_vlow(int irq, void *unused)
+{
+ pr_info("handle_twl6030_vlow: kernel_power_off()\n");
+ kernel_power_off();
+ return IRQ_HANDLED;
+}
+
/*----------------------------------------------------------------------*/
static inline void activate_irq(int irq)
@@ -196,6 +252,16 @@ static inline void activate_irq(int irq)
#endif
}
+int twl6030_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ if (on)
+ atomic_inc(&twl6030_wakeirqs);
+ else
+ atomic_dec(&twl6030_wakeirqs);
+
+ return 0;
+}
+
/*----------------------------------------------------------------------*/
static unsigned twl6030_irq_next;
@@ -299,12 +365,75 @@ int twl6030_mmc_card_detect(struct device *dev, int slot)
}
EXPORT_SYMBOL(twl6030_mmc_card_detect);
+int twl6030_vlow_init(int vlow_irq)
+{
+ int status;
+ u8 val;
+
+ status = twl_i2c_read_u8(TWL_MODULE_PM_SLAVE_RES, &val,
+ REG_VBATMIN_HI_CFG_STATE);
+ if (status < 0) {
+ pr_err("twl6030: I2C err reading REG_VBATMIN_HI_CFG_STATE: %d\n",
+ status);
+ return status;
+ }
+
+ status = twl_i2c_write_u8(TWL_MODULE_PM_SLAVE_RES,
+ val | VBATMIN_VLOW_EN, REG_VBATMIN_HI_CFG_STATE);
+ if (status < 0) {
+ pr_err("twl6030: I2C err writing REG_VBATMIN_HI_CFG_STATE: %d\n",
+ status);
+ return status;
+ }
+
+ status = twl_i2c_read_u8(TWL_MODULE_PIH, &val, REG_INT_MSK_LINE_A);
+ if (status < 0) {
+ pr_err("twl6030: I2C err reading REG_INT_MSK_LINE_A: %d\n",
+ status);
+ return status;
+ }
+
+ status = twl_i2c_write_u8(TWL_MODULE_PIH, val & ~VLOW_INT_MASK,
+ REG_INT_MSK_LINE_A);
+ if (status < 0) {
+ pr_err("twl6030: I2C err writing REG_INT_MSK_LINE_A: %d\n",
+ status);
+ return status;
+ }
+
+ status = twl_i2c_read_u8(TWL_MODULE_PIH, &val, REG_INT_MSK_STS_A);
+ if (status < 0) {
+ pr_err("twl6030: I2C err reading REG_INT_MSK_STS_A: %d\n",
+ status);
+ return status;
+ }
+
+ status = twl_i2c_write_u8(TWL_MODULE_PIH, val & ~VLOW_INT_MASK,
+ REG_INT_MSK_STS_A);
+ if (status < 0) {
+ pr_err("twl6030: I2C err writing REG_INT_MSK_STS_A: %d\n",
+ status);
+ return status;
+ }
+
+ /* install an irq handler for vlow */
+ status = request_threaded_irq(vlow_irq, NULL, handle_twl6030_vlow,
+ IRQF_ONESHOT,
+ "TWL6030-VLOW", handle_twl6030_vlow);
+ if (status < 0) {
+ pr_err("twl6030: could not claim vlow irq %d: %d\n", vlow_irq,
+ status);
+ return status;
+ }
+
+ return 0;
+}
+
int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end)
{
int status = 0;
int i;
- struct task_struct *task;
int ret;
u8 mask[4];
@@ -320,6 +449,7 @@ int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end)
REG_INT_STS_A, 3); /* clear INT_STS_A,B,C */
twl6030_irq_base = irq_base;
+ twl6030_irq_end = irq_end;
/* install an irq handler for each of the modules;
* clone dummy irq_chip since PIH can't *do* anything
@@ -327,10 +457,12 @@ int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end)
twl6030_irq_chip = dummy_irq_chip;
twl6030_irq_chip.name = "twl6030";
twl6030_irq_chip.irq_set_type = NULL;
+ twl6030_irq_chip.irq_set_wake = twl6030_irq_set_wake;
for (i = irq_base; i < irq_end; i++) {
irq_set_chip_and_handler(i, &twl6030_irq_chip,
handle_simple_irq);
+ irq_set_chip_data(i, (void *)irq_num);
activate_irq(i);
}
@@ -353,10 +485,22 @@ int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end)
pr_err("twl6030: could not claim irq%d: %d\n", irq_num, status);
goto fail_irq;
}
+
+ twl_irq = irq_num;
+ register_pm_notifier(&twl6030_irq_pm_notifier_block);
+
+ status = twl6030_vlow_init(twl6030_irq_base + TWL_VLOW_INTR_OFFSET);
+ if (status < 0)
+ goto fail_vlow;
+
return status;
-fail_irq:
+
+fail_vlow:
free_irq(irq_num, &irq_event);
+fail_irq:
+ kthread_stop(task);
+
fail_kthread:
for (i = irq_base; i < irq_end; i++)
irq_set_chip_and_handler(i, NULL, NULL);
@@ -365,11 +509,25 @@ fail_kthread:
int twl6030_exit_irq(void)
{
+ int i;
+ unregister_pm_notifier(&twl6030_irq_pm_notifier_block);
+
+ if (task)
+ kthread_stop(task);
- if (twl6030_irq_base) {
+ if (!twl6030_irq_base || !twl6030_irq_end) {
pr_err("twl6030: can't yet clean up IRQs?\n");
return -ENOSYS;
}
+
+ free_irq(twl6030_irq_base + TWL_VLOW_INTR_OFFSET,
+ handle_twl6030_vlow);
+
+ free_irq(twl_irq, &irq_event);
+
+ for (i = twl6030_irq_base; i < twl6030_irq_end; i++)
+ irq_set_chip_and_handler(i, NULL, NULL);
+
return 0;
}
diff --git a/drivers/mfd/twl6030-madc.c b/drivers/mfd/twl6030-madc.c
new file mode 100644
index 0000000..f537ba5
--- /dev/null
+++ b/drivers/mfd/twl6030-madc.c
@@ -0,0 +1,354 @@
+/*
+ *
+ * TWL6030 MADC module driver-This driver only implements the ADC read
+ * functions
+ *
+ * Copyright (C) 2011 Samsung Telecommunications of America
+ *
+ * Based on twl4030-madc.c
+ * Copyright (C) 2008 Nokia Corporation
+ * Mikko Ylinen <mikko.k.ylinen@nokia.com>
+ *
+ * Amit Kucheria <amit.kucheria@canonical.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/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/i2c/twl.h>
+#include <linux/i2c/twl6030-madc.h>
+#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/mutex.h>
+#include <linux/bitops.h>
+#include <linux/jiffies.h>
+#include <linux/types.h>
+#include <linux/gfp.h>
+#include <linux/err.h>
+#include <linux/wakelock.h>
+
+#define GPADCS (1 << 1)
+#define GPADCR (1 << 0)
+#define REG_TOGGLE1 0x90
+
+#define DRIVER_NAME (twl6030_madc_driver.driver.name)
+static struct platform_driver twl6030_madc_driver;
+
+/*
+ * struct twl6030_madc_data - a container for madc info
+ * @dev - pointer to device structure for madc
+ * @lock - mutex protecting this data structure
+ */
+struct twl6030_madc_data {
+ struct device *dev;
+ struct mutex lock;
+ struct dentry *file;
+ struct wake_lock wakelock;
+};
+
+static struct twl6030_madc_data *twl6030_madc;
+static u8 gpadc_ctrl_reg;
+
+static inline int twl6030_madc_start_conversion(struct twl6030_madc_data *madc)
+{
+ int ret;
+
+ ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, GPADCS, REG_TOGGLE1);
+ if (ret) {
+ dev_err(madc->dev, "unable to write register 0x%X\n",
+ REG_TOGGLE1);
+ return ret;
+ }
+
+ udelay(100);
+ ret = twl_i2c_write_u8(TWL_MODULE_MADC, TWL6030_MADC_SP1,
+ TWL6030_MADC_CTRL_P1);
+ if (ret) {
+ dev_err(madc->dev, "unable to write register 0x%X\n",
+ TWL6030_MADC_CTRL_P1);
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Function that waits for conversion to be ready
+ * @madc - pointer to twl4030_madc_data struct
+ * @timeout_ms - timeout value in milliseconds
+ * @status_reg - ctrl register
+ * returns 0 if succeeds else a negative error value
+ */
+static int twl6030_madc_wait_conversion_ready(struct twl6030_madc_data *madc,
+ unsigned int timeout_ms,
+ u8 status_reg)
+{
+ unsigned long timeout;
+ unsigned long delta;
+ u8 reg;
+ int ret;
+
+ delta = msecs_to_jiffies(timeout_ms);
+
+ if (delta < 2)
+ delta = 2;
+
+ wake_lock(&madc->wakelock);
+ timeout = jiffies + delta;
+ do {
+ ret = twl_i2c_read_u8(TWL6030_MODULE_MADC, &reg, status_reg);
+ if (ret) {
+ dev_err(madc->dev,
+ "unable to read status register 0x%X\n",
+ status_reg);
+ goto unlock;
+ }
+ if (!(reg & TWL6030_MADC_BUSY) && (reg & TWL6030_MADC_EOCP1)) {
+ ret = 0;
+ goto unlock;
+ }
+
+ if (time_after(jiffies, timeout))
+ break;
+
+ usleep_range(500, 2000);
+ } while (1);
+
+ dev_err(madc->dev, "conversion timeout, ctrl_px=0x%08x\n", reg);
+ ret = -EAGAIN;
+
+unlock:
+ wake_unlock(&madc->wakelock);
+ return ret;
+}
+
+/*
+ * Function to read a particular channel value.
+ * @madc - pointer to struct twl6030_madc_data
+ * @reg - lsb of ADC Channel
+ * If the i2c read fails it returns an error else returns 0.
+ */
+static int twl6030_madc_channel_raw_read(struct twl6030_madc_data *madc,
+ u8 reg)
+{
+ u8 msb, lsb;
+ int ret;
+
+ mutex_lock(&madc->lock);
+ ret = twl6030_madc_start_conversion(twl6030_madc);
+ if (ret)
+ goto unlock;
+
+ ret = twl6030_madc_wait_conversion_ready(twl6030_madc, 5,
+ TWL6030_MADC_CTRL_P1);
+ if (ret)
+ goto unlock;
+
+ /*
+ * For each ADC channel, we have MSB and LSB register
+ * pair. MSB address is always LSB address+1. reg parameter is
+ * the address of LSB register
+ */
+ ret = twl_i2c_read_u8(TWL6030_MODULE_MADC, &msb, reg + 1);
+ if (ret) {
+ dev_err(madc->dev, "unable to read MSB register 0x%X\n",
+ reg + 1);
+ goto unlock;
+ }
+ ret = twl_i2c_read_u8(TWL6030_MODULE_MADC, &lsb, reg);
+ if (ret) {
+ dev_err(madc->dev, "unable to read LSB register 0x%X\n", reg);
+ goto unlock;
+ }
+ ret = (int)((msb << 8) | lsb);
+unlock:
+ /* Disable GPADC for power savings. */
+ twl_i2c_write_u8(TWL6030_MODULE_ID1, GPADCR, REG_TOGGLE1);
+ mutex_unlock(&madc->lock);
+ return ret;
+}
+
+/*
+ * Return channel value
+ * Or < 0 on failure.
+ */
+int twl6030_get_madc_conversion(int channel_no)
+{
+ u8 reg = TWL6030_MADC_GPCH0_LSB + (2 * channel_no);
+ if (!twl6030_madc) {
+ pr_err("%s: No ADC device\n", __func__);
+ return -EINVAL;
+ }
+ if (channel_no >= TWL6030_MADC_MAX_CHANNELS) {
+ dev_err(twl6030_madc->dev,
+ "%s: Channel number (%d) exceeds max (%d)\n",
+ __func__, channel_no, TWL6030_MADC_MAX_CHANNELS);
+ return -EINVAL;
+ }
+
+ return twl6030_madc_channel_raw_read(twl6030_madc, reg);
+}
+EXPORT_SYMBOL_GPL(twl6030_get_madc_conversion);
+
+#ifdef CONFIG_DEBUG_FS
+
+static int debug_twl6030_madc_show(struct seq_file *s, void *_)
+{
+ int i, result;
+ for (i = 0; i < TWL6030_MADC_MAX_CHANNELS; i++) {
+ result = twl6030_get_madc_conversion(i);
+ seq_printf(s, "channel %3d returns result %d\n",
+ i, result);
+ }
+ return 0;
+}
+
+static int debug_twl6030_madc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debug_twl6030_madc_show, inode->i_private);
+}
+
+static const struct file_operations debug_fops = {
+ .open = debug_twl6030_madc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define DEBUG_FOPS (&debug_fops)
+
+#else
+#define DEBUG_FOPS NULL
+#endif
+
+/*
+ * Initialize MADC
+ */
+static int __devinit twl6030_madc_probe(struct platform_device *pdev)
+{
+ struct twl6030_madc_data *madc;
+ struct twl4030_madc_platform_data *pdata = pdev->dev.platform_data;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "platform_data not available\n");
+ return -EINVAL;
+ }
+ madc = kzalloc(sizeof(*madc), GFP_KERNEL);
+ if (!madc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, madc);
+ madc->dev = &pdev->dev;
+ mutex_init(&madc->lock);
+ madc->file = debugfs_create_file(DRIVER_NAME, S_IRUGO, NULL,
+ madc, DEBUG_FOPS);
+ wake_lock_init(&madc->wakelock, WAKE_LOCK_SUSPEND, "twl6030 adc");
+ twl6030_madc = madc;
+ return 0;
+}
+
+static int __devexit twl6030_madc_remove(struct platform_device *pdev)
+{
+ struct twl6030_madc_data *madc = platform_get_drvdata(pdev);
+
+ wake_lock_destroy(&madc->wakelock);
+ mutex_destroy(&madc->lock);
+ free_irq(platform_get_irq(pdev, 0), madc);
+ platform_set_drvdata(pdev, NULL);
+ twl6030_madc = NULL;
+ debugfs_remove(madc->file);
+ kfree(madc);
+
+ return 0;
+}
+
+static int twl6030_madc_suspend(struct device *pdev)
+{
+ int ret;
+ u8 reg_val;
+
+ ret = twl_i2c_read_u8(TWL_MODULE_MADC, &reg_val, TWL6030_MADC_CTRL);
+ if (!ret) {
+ reg_val &= ~(TWL6030_MADC_TEMP1_EN);
+ ret = twl_i2c_write_u8(TWL_MODULE_MADC, reg_val,
+ TWL6030_MADC_CTRL);
+ }
+
+ if (ret) {
+ dev_err(twl6030_madc->dev, "unable to disable madc temp1!\n");
+ gpadc_ctrl_reg = TWL6030_MADC_TEMP1_EN;
+ } else
+ gpadc_ctrl_reg = reg_val;
+
+ return 0;
+};
+
+static int twl6030_madc_resume(struct device *pdev)
+{
+ int ret;
+
+ if (!(gpadc_ctrl_reg & TWL6030_MADC_TEMP1_EN)) {
+ gpadc_ctrl_reg |= TWL6030_MADC_TEMP1_EN;
+ ret = twl_i2c_write_u8(TWL_MODULE_MADC, gpadc_ctrl_reg,
+ TWL6030_MADC_CTRL);
+ if (ret)
+ dev_err(twl6030_madc->dev,
+ "unable to enable madc temp1!\n");
+ }
+
+ return 0;
+};
+
+static const struct dev_pm_ops twl6030_madc_pm_ops = {
+ .suspend = twl6030_madc_suspend,
+ .resume = twl6030_madc_resume,
+};
+
+static struct platform_driver twl6030_madc_driver = {
+ .probe = twl6030_madc_probe,
+ .remove = __exit_p(twl6030_madc_remove),
+ .driver = {
+ .name = "twl6030_madc",
+ .owner = THIS_MODULE,
+ .pm = &twl6030_madc_pm_ops,
+ },
+};
+
+static int __init twl6030_madc_init(void)
+{
+ return platform_driver_register(&twl6030_madc_driver);
+}
+
+module_init(twl6030_madc_init);
+
+static void __exit twl6030_madc_exit(void)
+{
+ platform_driver_unregister(&twl6030_madc_driver);
+}
+
+module_exit(twl6030_madc_exit);
+
+MODULE_DESCRIPTION("TWL6030 ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("J Keerthy");
+MODULE_ALIAS("platform:twl6030_madc");
diff --git a/drivers/mfd/twl6030-power.c b/drivers/mfd/twl6030-power.c
new file mode 100644
index 0000000..2bbea1a
--- /dev/null
+++ b/drivers/mfd/twl6030-power.c
@@ -0,0 +1,251 @@
+/*
+ * Handling for Resource Mapping for TWL6030 Family of chips
+ *
+ * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
+ * Nishanth Menon
+ *
+ * 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 "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/i2c/twl.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+
+#include <asm/mach-types.h>
+
+#define VREG_GRP 0
+
+static u8 dev_on_group;
+
+/**
+ * struct twl6030_resource_map - describe the resource mapping for TWL6030
+ * @name: name of the resource
+ * @res_id: resource ID
+ * @base_addr: base address
+ * @group: which device group can control this resource?
+ */
+struct twl6030_resource_map {
+ char *name;
+ u8 res_id;
+ u8 base_addr;
+ u8 group;
+};
+
+/* list of all s/w modifiable resources in TWL6030 */
+static __initdata struct twl6030_resource_map twl6030_res_map[] = {
+ {.res_id = RES_V1V29,.name = "V1V29",.base_addr = 0x40,.group = DEV_GRP_P1,},
+ {.res_id = RES_V1V8,.name = "V1V8",.base_addr = 0x46,.group = DEV_GRP_P1,},
+ {.res_id = RES_V2V1,.name = "V2V1",.base_addr = 0x4c,.group = DEV_GRP_P1,},
+ {.res_id = RES_VDD1,.name = "CORE1",.base_addr = 0x52,.group = DEV_GRP_P1,},
+ {.res_id = RES_VDD2,.name = "CORE2",.base_addr = 0x58,.group = DEV_GRP_P1,},
+ {.res_id = RES_VDD3,.name = "CORE3",.base_addr = 0x5e,.group = DEV_GRP_P1,},
+ {.res_id = RES_VMEM,.name = "VMEM",.base_addr = 0x64,.group = DEV_GRP_P1,},
+ /* VANA cannot be modified */
+ {.res_id = RES_VUAX1,.name = "VUAX1",.base_addr = 0x84,.group = DEV_GRP_P1,},
+ {.res_id = RES_VAUX2,.name = "VAUX2",.base_addr = 0x88,.group = DEV_GRP_P1,},
+ {.res_id = RES_VAUX3,.name = "VAUX3",.base_addr = 0x8c,.group = DEV_GRP_P1,},
+ {.res_id = RES_VCXIO,.name = "VCXIO",.base_addr = 0x90,.group = DEV_GRP_P1,},
+ {.res_id = RES_VDAC,.name = "VDAC",.base_addr = 0x94,.group = DEV_GRP_P1,},
+ {.res_id = RES_VMMC1,.name = "VMMC",.base_addr = 0x98,.group = DEV_GRP_P1,},
+ {.res_id = RES_VPP,.name = "VPP",.base_addr = 0x9c,.group = DEV_GRP_P1,},
+ /* VRTC cannot be modified */
+ {.res_id = RES_VUSBCP,.name = "VUSB",.base_addr = 0xa0,.group = DEV_GRP_P1,},
+ {.res_id = RES_VSIM,.name = "VSIM",.base_addr = 0xa4,.group = DEV_GRP_P1,},
+ {.res_id = RES_REGEN,.name = "REGEN1",.base_addr = 0xad,.group = DEV_GRP_P1,},
+ {.res_id = RES_REGEN2,.name = "REGEN2",.base_addr = 0xb0,.group = DEV_GRP_P1,},
+ {.res_id = RES_SYSEN,.name = "SYSEN",.base_addr = 0xb3,.group = DEV_GRP_P1,},
+ /* NRES_PWRON cannot be modified */
+ /* 32KCLKAO cannot be modified */
+ {.res_id = RES_32KCLKG,.name = "32KCLKG",.base_addr = 0xbc,.group = DEV_GRP_P1,},
+ {.res_id = RES_32KCLKAUDIO,.name = "32KCLKAUDIO",.base_addr = 0xbf,.group = DEV_GRP_P1,},
+ /* BIAS cannot be modified */
+ /* VBATMIN_HI cannot be modified */
+ /* RC6MHZ cannot be modified */
+ /* TEMP cannot be modified */
+};
+
+static struct twl4030_system_config twl6030_sys_config[] = {
+ {.name = "DEV_ON", .group = DEV_GRP_P1,},
+};
+
+/* Actual power groups that TWL understands */
+#define P3_GRP_6030 BIT(2) /* secondary processor, modem, etc */
+#define P2_GRP_6030 BIT(1) /* "peripherals" */
+#define P1_GRP_6030 BIT(0) /* CPU/Linux */
+
+static __init void twl6030_process_system_config(void)
+{
+ u8 grp;
+ int r;
+ bool i = false;
+
+ struct twl4030_system_config *sys_config;
+ sys_config = twl6030_sys_config;
+
+ while (sys_config && sys_config->name) {
+ if (!strcmp(sys_config->name, "DEV_ON")) {
+ dev_on_group = sys_config->group;
+ i = true;
+ break;
+ }
+ sys_config++;
+ }
+ if (!i)
+ pr_err("%s: Couldn't find DEV_ON resource configuration!"
+ " MOD & CON group would be kept active.\n", __func__);
+
+ if (dev_on_group) {
+ r = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &grp,
+ TWL6030_PHOENIX_DEV_ON);
+ if (r) {
+ pr_err("%s: Error(%d) reading {addr=0x%02x}",
+ __func__, r, TWL6030_PHOENIX_DEV_ON);
+ /*
+ * On error resetting to 0, so that all the process
+ * groups are kept active.
+ */
+ dev_on_group = 0;
+ } else {
+ /*
+ * Unmapped processor groups are disabled by writing
+ * 1 to corresponding group in DEV_ON.
+ */
+ grp |= (dev_on_group & DEV_GRP_P1) ? 0 : P1_GRP_6030;
+ grp |= (dev_on_group & DEV_GRP_P2) ? 0 : P2_GRP_6030;
+ grp |= (dev_on_group & DEV_GRP_P3) ? 0 : P3_GRP_6030;
+ dev_on_group = grp;
+ }
+ }
+}
+
+static __init void twl6030_program_map(void)
+{
+ struct twl6030_resource_map *res = twl6030_res_map;
+ int r, i;
+
+ for (i = 0; i < ARRAY_SIZE(twl6030_res_map); i++) {
+ u8 grp = 0;
+
+ /* map back from generic device id to TWL6030 ID */
+ grp |= (res->group & DEV_GRP_P1) ? P1_GRP_6030 : 0;
+ grp |= (res->group & DEV_GRP_P2) ? P2_GRP_6030 : 0;
+ grp |= (res->group & DEV_GRP_P3) ? P3_GRP_6030 : 0;
+
+ r = twl_i2c_write_u8(TWL6030_MODULE_ID0, res->group,
+ res->base_addr);
+ if (r)
+ pr_err("%s: Error(%d) programming map %s {addr=0x%02x},"
+ "grp=0x%02X\n", __func__, r, res->name,
+ res->base_addr, res->group);
+ res++;
+ }
+}
+
+static __init void twl6030_update_system_map
+ (struct twl4030_system_config *sys_list)
+{
+ int i;
+ struct twl4030_system_config *sys_res;
+
+ while (sys_list && sys_list->name) {
+ sys_res = twl6030_sys_config;
+ for (i = 0; i < ARRAY_SIZE(twl6030_sys_config); i++) {
+ if (!strcmp(sys_res->name, sys_list->name))
+ sys_res->group = sys_list->group &
+ (DEV_GRP_P1 | DEV_GRP_P2 | DEV_GRP_P3);
+ sys_res++;
+ }
+ sys_list++;
+ }
+}
+
+static __init void twl6030_update_map(struct twl4030_resconfig *res_list)
+{
+ int i, res_idx = 0;
+ struct twl6030_resource_map *res;
+
+ while (res_list->resource != TWL4030_RESCONFIG_UNDEF) {
+ res = twl6030_res_map;
+ for (i = 0; i < ARRAY_SIZE(twl6030_res_map); i++) {
+ if (res->res_id == res_list->resource) {
+ res->group = res_list->devgroup &
+ (DEV_GRP_P1 | DEV_GRP_P2 | DEV_GRP_P3);
+ break;
+ }
+ res++;
+ }
+
+ if (i == ARRAY_SIZE(twl6030_res_map)) {
+ pr_err("%s: in platform_data resource index %d, cannot"
+ " find match for resource 0x%02x. NO Update!\n",
+ __func__, res_idx, res_list->resource);
+ }
+ res_list++;
+ res_idx++;
+ }
+}
+
+
+static int twl6030_power_notifier_cb(struct notifier_block *notifier,
+ unsigned long pm_event, void *unused)
+{
+ int r = 0;
+
+ switch (pm_event) {
+ case PM_SUSPEND_PREPARE:
+ r = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, dev_on_group,
+ TWL6030_PHOENIX_DEV_ON);
+ if (r)
+ pr_err("%s: Error(%d) programming {addr=0x%02x}",
+ __func__, r, TWL6030_PHOENIX_DEV_ON);
+ break;
+ }
+
+ return notifier_from_errno(r);
+}
+
+static struct notifier_block twl6030_power_pm_notifier = {
+ .notifier_call = twl6030_power_notifier_cb,
+};
+
+/**
+ * twl6030_power_init() - Update the power map to reflect connectivity of board
+ * @power_data: power resource map to update (OPTIONAL) - use this if a resource
+ * is used by other devices other than APP (DEV_GRP_P1)
+ */
+void __init twl6030_power_init(struct twl4030_power_data *power_data)
+{
+ int r;
+
+ if (power_data && (!power_data->resource_config &&
+ !power_data->sys_config)) {
+ pr_err("%s: power data from platform without configuration!\n",
+ __func__);
+ return;
+ }
+
+ if (power_data && power_data->resource_config)
+ twl6030_update_map(power_data->resource_config);
+
+ if (power_data && power_data->sys_config)
+ twl6030_update_system_map(power_data->sys_config);
+
+ twl6030_process_system_config();
+
+ twl6030_program_map();
+
+ r = register_pm_notifier(&twl6030_power_pm_notifier);
+ if (r)
+ pr_err("%s: twl6030 power registration failed!\n", __func__);
+
+ return;
+}
diff --git a/drivers/mfd/twl6030-poweroff.c b/drivers/mfd/twl6030-poweroff.c
new file mode 100644
index 0000000..776a251
--- /dev/null
+++ b/drivers/mfd/twl6030-poweroff.c
@@ -0,0 +1,75 @@
+/*
+ * /drivers/mfd/twl6030-poweroff.c
+ *
+ * Power off device
+ *
+ * Copyright (C) 2011 Texas Instruments Corporation
+ *
+ * Written by Rajeev Kulkarni <rajeevk@ti.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/i2c/twl.h>
+
+#define TWL6030_PHOENIX_DEV_ON 0x25
+#define APP_DEVOFF (1<<0)
+#define CON_DEVOFF (1<<1)
+#define MOD_DEVOFF (1<<2)
+
+void twl6030_poweroff(void)
+{
+ u8 val = 0;
+ int err = 0;
+
+ err = twl_i2c_read_u8(TWL6030_MODULE_ID0, &val,
+ TWL6030_PHOENIX_DEV_ON);
+ if (err) {
+ pr_warning("I2C error %d reading PHOENIX_DEV_ON\n", err);
+ return;
+ }
+
+ val |= APP_DEVOFF | CON_DEVOFF | MOD_DEVOFF;
+
+ err = twl_i2c_write_u8(TWL6030_MODULE_ID0, val,
+ TWL6030_PHOENIX_DEV_ON);
+
+ if (err) {
+ pr_warning("I2C error %d writing PHOENIX_DEV_ON\n", err);
+ return;
+ }
+
+ return;
+}
+
+static int __init twl6030_poweroff_init(void)
+{
+ pm_power_off = twl6030_poweroff;
+
+ return 0;
+}
+
+static void __exit twl6030_poweroff_exit(void)
+{
+ pm_power_off = NULL;
+}
+
+module_init(twl6030_poweroff_init);
+module_exit(twl6030_poweroff_exit);
+
+MODULE_DESCRIPTION("TLW6030 device power off");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rajeev Kulkarni");
diff --git a/drivers/mfd/twl6040-codec.c b/drivers/mfd/twl6040-codec.c
new file mode 100644
index 0000000..4633d70
--- /dev/null
+++ b/drivers/mfd/twl6040-codec.c
@@ -0,0 +1,820 @@
+/*
+ * MFD driver for twl6040 codec submodule
+ *
+ * Authors: Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
+ * Misael Lopez Cruz <misael.lopez@ti.com>
+ *
+ * Copyright: (C) 20010 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>
+
+int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg)
+{
+ int ret;
+ u8 val;
+
+ 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 ncpctl, ldoctl, lppllctl, accctl;
+ int ret;
+
+ ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
+ ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
+ lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
+ accctl = twl6040_reg_read(twl6040, TWL6040_REG_ACCCTL);
+
+ /* enable reference system */
+ ldoctl |= TWL6040_REFENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ return ret;
+ msleep(10);
+
+ /* enable internal oscillator */
+ ldoctl |= TWL6040_OSCENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto osc_err;
+ udelay(10);
+
+ /* enable high-side ldo */
+ ldoctl |= TWL6040_HSLDOENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto hsldo_err;
+ udelay(244);
+
+ /* enable negative charge pump */
+ ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
+ if (ret)
+ goto ncp_err;
+ udelay(488);
+
+ /* enable low-side ldo */
+ ldoctl |= TWL6040_LSLDOENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto lsldo_err;
+ udelay(244);
+
+ /* enable low-power pll */
+ lppllctl |= TWL6040_LPLLENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+ if (ret)
+ goto lppll_err;
+
+ /* reset state machine */
+ accctl |= TWL6040_RESETSPLIT;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_ACCCTL, accctl);
+ if (ret)
+ goto rst_err;
+ mdelay(5);
+ accctl &= ~TWL6040_RESETSPLIT;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_ACCCTL, accctl);
+ if (ret)
+ goto rst_err;
+
+ /* disable internal oscillator */
+ ldoctl &= ~TWL6040_OSCENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto rst_err;
+
+ return 0;
+
+rst_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);
+ udelay(244);
+lsldo_err:
+ ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN);
+ twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
+ udelay(488);
+ncp_err:
+ ldoctl &= ~TWL6040_HSLDOENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(244);
+hsldo_err:
+ ldoctl &= ~TWL6040_OSCENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+osc_err:
+ ldoctl &= ~TWL6040_REFENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ msleep(10);
+
+ return ret;
+}
+
+/* twl6040 codec manual power-down sequence */
+static int twl6040_power_down(struct twl6040 *twl6040)
+{
+ u8 ncpctl, ldoctl, lppllctl, accctl;
+ int ret;
+
+ ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
+ ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
+ lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
+ accctl = twl6040_reg_read(twl6040, TWL6040_REG_ACCCTL);
+
+ /* enable internal oscillator */
+ ldoctl |= TWL6040_OSCENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ return ret;
+ udelay(10);
+
+ /* disable low-power pll */
+ lppllctl &= ~TWL6040_LPLLENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+ if (ret)
+ goto lppll_err;
+
+ /* disable low-side ldo */
+ ldoctl &= ~TWL6040_LSLDOENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto lsldo_err;
+ udelay(244);
+
+ /* disable negative charge pump */
+ ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN);
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
+ if (ret)
+ goto ncp_err;
+ udelay(488);
+
+ /* disable high-side ldo */
+ ldoctl &= ~TWL6040_HSLDOENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto hsldo_err;
+ udelay(244);
+
+ /* disable internal oscillator */
+ ldoctl &= ~TWL6040_OSCENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto osc_err;
+
+ /* disable reference system */
+ ldoctl &= ~TWL6040_REFENA;
+ ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ if (ret)
+ goto ref_err;
+ msleep(10);
+
+ return 0;
+
+ref_err:
+ ldoctl |= TWL6040_OSCENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(10);
+osc_err:
+ ldoctl |= TWL6040_HSLDOENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(244);
+hsldo_err:
+ ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN;
+ twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
+ udelay(488);
+ncp_err:
+ ldoctl |= TWL6040_LSLDOENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(244);
+lsldo_err:
+ lppllctl |= TWL6040_LPLLENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+lppll_err:
+ lppllctl |= TWL6040_LPLLENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
+ accctl |= TWL6040_RESETSPLIT;
+ twl6040_reg_write(twl6040, TWL6040_REG_ACCCTL, accctl);
+ mdelay(5);
+ accctl &= ~TWL6040_RESETSPLIT;
+ twl6040_reg_write(twl6040, TWL6040_REG_ACCCTL, accctl);
+ ldoctl &= ~TWL6040_OSCENA;
+ twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
+ msleep(10);
+
+ return ret;
+}
+
+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;
+ int round = 0;
+ int ret = 0;
+ int retry = 0;
+ u8 intid;
+ u8 ncpctl;
+ u8 ldoctl;
+ u8 lppllctl;
+ u8 ncpctl_exp;
+ u8 ldoctl_exp;
+ u8 lppllctl_exp;
+
+ /* NCPCTL expected value: NCP enabled */
+ ncpctl_exp = (TWL6040_TSHUTENA | TWL6040_NCPENA);
+
+ /* LDOCTL expected value: HS/LS LDOs and Reference enabled */
+ ldoctl_exp = (TWL6040_REFENA | TWL6040_HSLDOENA | TWL6040_LSLDOENA);
+
+ /* LPPLLCTL expected value: Low-Power PLL enabled */
+ lppllctl_exp = TWL6040_LPLLENA;
+
+ do {
+ gpio_set_value(twl6040->audpwron, 1);
+ 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,
+ "timeout waiting for READYINT\n");
+ return -ETIMEDOUT;
+ }
+ }
+ /*
+ * Power on seemingly completed.
+ * Look for clues that the twl6040 might be still booting.
+ */
+
+ retry = 0;
+ ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
+ if (ncpctl != ncpctl_exp)
+ retry++;
+
+ ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
+ if (ldoctl != ldoctl_exp)
+ retry++;
+
+ lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
+ if (lppllctl != lppllctl_exp)
+ retry++;
+
+ if (retry) {
+ dev_err(twl6040->dev,
+ "NCPCTL: 0x%02x (should be 0x%02x)\n"
+ "LDOCTL: 0x%02x (should be 0x%02x)\n"
+ "LPLLCTL: 0x%02x (should be 0x%02x)\n",
+ ncpctl, ncpctl_exp,
+ ldoctl, ldoctl_exp,
+ lppllctl, lppllctl_exp);
+ round++;
+ gpio_set_value(twl6040->audpwron, 0);
+ usleep_range(1000, 1500);
+ continue;
+ }
+ } while (round && (round < 3));
+
+ if (round >= 3) {
+ dev_err(twl6040->dev,
+ "Automatic power on failed, reverting to manual\n");
+ twl6040->audpwron = -EINVAL;
+ ret = twl6040_power_up(twl6040);
+ if (ret)
+ dev_err(twl6040->dev, "Manual power-up failed\n");
+ }
+
+ return ret;
+}
+
+static int twl6040_power(struct twl6040 *twl6040, int enable)
+{
+ struct twl4030_codec_data *pdata = dev_get_platdata(twl6040->dev);
+ int audpwron = twl6040->audpwron;
+ int naudint = twl6040->irq;
+ int ret = 0;
+
+ if (enable) {
+ /* enable 32kHz external clock */
+ if (pdata->set_ext_clk32k) {
+ ret = pdata->set_ext_clk32k(true);
+ if (ret) {
+ dev_err(twl6040->dev,
+ "failed to enable CLK32K %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* disable internal 32kHz oscillator */
+ twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL,
+ TWL6040_CLK32KSEL);
+
+ if (gpio_is_valid(audpwron)) {
+ /* wait for power-up completion */
+ ret = twl6040_power_up_completion(twl6040, naudint);
+ if (ret) {
+ dev_err(twl6040->dev,
+ "automatic power-down failed\n");
+ return ret;
+ }
+ } else {
+ /* use manual power-up sequence */
+ ret = twl6040_power_up(twl6040);
+ if (ret) {
+ dev_err(twl6040->dev,
+ "manual power-up failed\n");
+ return ret;
+ }
+ }
+ twl6040->pll = TWL6040_LPPLL_ID;
+ twl6040->sysclk = 19200000;
+ } else {
+ 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 */
+ ret = twl6040_power_down(twl6040);
+ if (ret) {
+ dev_err(twl6040->dev,
+ "manual power-down failed\n");
+ return ret;
+ }
+ }
+
+ /* enable internal 32kHz oscillator */
+ twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL,
+ TWL6040_CLK32KSEL);
+
+ /* disable 32kHz external clock */
+ if (pdata->set_ext_clk32k) {
+ ret = pdata->set_ext_clk32k(false);
+ if (ret)
+ dev_err(twl6040->dev,
+ "failed to disable CLK32K %d\n", ret);
+ }
+
+ twl6040->pll = TWL6040_NOPLL_ID;
+ twl6040->sysclk = 0;
+ }
+
+ twl6040->powered = enable;
+
+ return ret;
+}
+
+int twl6040_enable(struct twl6040 *twl6040)
+{
+ int ret = 0;
+
+ mutex_lock(&twl6040->mutex);
+ if (!twl6040->power_count++)
+ ret = twl6040_power(twl6040, 1);
+ mutex_unlock(&twl6040->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(twl6040_enable);
+
+int twl6040_disable(struct twl6040 *twl6040)
+{
+ int ret = 0;
+
+ mutex_lock(&twl6040->mutex);
+ WARN(!twl6040->power_count, "TWL6040 is already disabled");
+ if (!--twl6040->power_count)
+ ret = twl6040_power(twl6040, 0);
+ mutex_unlock(&twl6040->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(twl6040_disable);
+
+int twl6040_is_enabled(struct twl6040 *twl6040)
+{
+ return twl6040->power_count;
+}
+EXPORT_SYMBOL(twl6040_is_enabled);
+
+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:
+ /* lppll divider */
+ switch (freq_out) {
+ case 17640000:
+ lppllctl |= TWL6040_LPLLFIN;
+ break;
+ case 19200000:
+ lppllctl &= ~TWL6040_LPLLFIN;
+ break;
+ default:
+ dev_err(twl6040->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,
+ "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,
+ "freq_out %d not supported\n", freq_out);
+ ret = -EINVAL;
+ goto pll_out;
+ }
+
+ hppllctl &= ~TWL6040_MCLK_MSK;
+
+ switch (freq_in) {
+ 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(twl6040->dev,
+ "freq_in %d not supported\n", freq_in);
+ ret = -EINVAL;
+ goto pll_out;
+ }
+
+ 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, "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);
+
+int twl6040_get_icrev(struct twl6040 *twl6040)
+{
+ return twl6040->icrev;
+}
+EXPORT_SYMBOL(twl6040_get_icrev);
+
+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;
+ unsigned int naudint;
+ int audpwron;
+ int ret, children = 0;
+ u8 accctl;
+
+ 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->dev;
+ mutex_init(&twl6040->mutex);
+ mutex_init(&twl6040->io_mutex);
+
+ twl6040->icrev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
+ if (twl6040->icrev < 0) {
+ ret = twl6040->icrev;
+ goto gpio1_err;
+ }
+
+ if (pdata && (twl6040_get_icrev(twl6040) > TWL6040_REV_1_0))
+ audpwron = pdata->audpwron_gpio;
+ else
+ audpwron = -EINVAL;
+
+ if (pdata)
+ naudint = pdata->naudint_irq;
+ else
+ naudint = 0;
+
+ twl6040->audpwron = audpwron;
+ twl6040->powered = 0;
+ twl6040->irq = naudint;
+ twl6040->irq_base = pdata->irq_base;
+ init_completion(&twl6040->ready);
+
+ 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;
+ }
+
+ if (naudint) {
+ /* codec interrupt */
+ ret = twl6040_irq_init(twl6040);
+ if (ret)
+ goto gpio2_err;
+
+ ret = twl6040_request_irq(twl6040, TWL6040_IRQ_READY,
+ twl6040_naudint_handler, "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 */
+ accctl = twl6040_reg_read(twl6040, TWL6040_REG_ACCCTL);
+ twl6040_reg_write(twl6040, TWL6040_REG_ACCCTL, accctl | TWL6040_I2CSEL);
+
+ if (pdata->get_ext_clk32k) {
+ ret = pdata->get_ext_clk32k();
+ if (ret) {
+ dev_err(twl6040->dev,
+ "failed to get external 32kHz clock %d\n",
+ ret);
+ goto clk32k_err;
+ }
+ }
+
+ 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 (pdata->put_ext_clk32k)
+ pdata->put_ext_clk32k();
+clk32k_err:
+ if (naudint)
+ twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
+irq_err:
+ if (naudint)
+ twl6040_irq_exit(twl6040);
+gpio2_err:
+ if (gpio_is_valid(audpwron))
+ gpio_free(audpwron);
+gpio1_err:
+ platform_set_drvdata(pdev, NULL);
+ kfree(twl6040);
+ return ret;
+}
+
+static int __devexit twl6040_remove(struct platform_device *pdev)
+{
+ struct twl6040 *twl6040 = platform_get_drvdata(pdev);
+ struct twl4030_codec_data *pdata = dev_get_platdata(twl6040->dev);
+ int audpwron = twl6040->audpwron;
+ int naudint = twl6040->irq;
+
+ twl6040_disable(twl6040);
+
+ twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
+
+ if (gpio_is_valid(audpwron))
+ gpio_free(audpwron);
+
+ if (naudint)
+ twl6040_irq_exit(twl6040);
+
+ mfd_remove_devices(&pdev->dev);
+
+ if (pdata->put_ext_clk32k)
+ pdata->put_ext_clk32k();
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(twl6040);
+
+ 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("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..6beac7a
--- /dev/null
+++ b/drivers/mfd/twl6040-irq.c
@@ -0,0 +1,196 @@
+/*
+ * twl6040-irq.c -- Interrupt controller support for TWL6040
+ *
+ * Copyright 2010 Texas Instruments Inc.
+ *
+ * Author: Misael Lopez Cruz <misael.lopez@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#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_unmask(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_mask(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_mask = twl6040_irq_mask,
+ .irq_unmask = twl6040_irq_unmask,
+};
+
+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
+ set_irq_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 */
+ val = twl6040_reg_read(twl6040, TWL6040_REG_ACCCTL)
+ & ~TWL6040_INTCLRMODE;
+ twl6040_reg_write(twl6040, TWL6040_REG_ACCCTL, val);
+
+ 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);