diff options
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 32 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 4 | ||||
-rw-r--r-- | drivers/mfd/omap-usb-host.c | 385 | ||||
-rw-r--r-- | drivers/mfd/twl-core.c | 47 | ||||
-rw-r--r-- | drivers/mfd/twl6030-irq.c | 172 | ||||
-rw-r--r-- | drivers/mfd/twl6030-madc.c | 354 | ||||
-rw-r--r-- | drivers/mfd/twl6030-power.c | 251 | ||||
-rw-r--r-- | drivers/mfd/twl6030-poweroff.c | 75 | ||||
-rw-r--r-- | drivers/mfd/twl6040-codec.c | 820 | ||||
-rw-r--r-- | drivers/mfd/twl6040-irq.c | 196 |
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, ®, 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, ®_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); |