]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
34XX: PM: Initial version of suspend and dynamic retention
authorJouni Hogander <jouni.hogander@nokia.com>
Fri, 16 May 2008 10:58:25 +0000 (13:58 +0300)
committerTony Lindgren <tony@atomide.com>
Tue, 20 May 2008 17:30:25 +0000 (10:30 -0700)
This is initial version of suspend and dynamic retention for
34xx. Omap is tried to put to full retention on suspend and pm_idle.

Signed-off-by: Jouni Hogander <jouni.hogander@nokia.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
arch/arm/mach-omap2/Makefile
arch/arm/mach-omap2/pm.c
arch/arm/mach-omap2/pm.h
arch/arm/mach-omap2/pm34xx.c [new file with mode: 0644]

index 9ff50478d01e4d183d1cd37281b2293f2891dc1f..b617b6647e4dee777d2e41ce0b3a9416b16e3933 100644 (file)
@@ -18,6 +18,9 @@ ifeq ($(CONFIG_ARCH_OMAP2),y)
 obj-$(CONFIG_PM) += pm24xx.o sleep24xx.o
 endif
 
+ifeq ($(CONFIG_ARCH_OMAP3),y)
+obj-$(CONFIG_PM) += pm34xx.o sleep34xx.o
+endif
 obj-$(CONFIG_PM_DEBUG) += pm-debug.o
 
 # Clock framework
index 55ed75b2d4a2943af70e89776071f55b69f5ade1..bef58d7dcc6a6736e66f7f246717d67aff3dfdbe 100644 (file)
@@ -74,8 +74,10 @@ int __init omap_pm_init(void)
 
        if (cpu_is_omap24xx())
                error = omap2_pm_init();
+       if (cpu_is_omap34xx())
+               error = omap3_pm_init();
        if (error) {
-               printk(KERN_ERR "omap2_pm_init failed: %d\n", error);
+               printk(KERN_ERR "omap2|3_pm_init failed: %d\n", error);
                return error;
        }
 
index 15482ba7f49361e59688a71095d57af00632c326..351456e084a102d26cd122bf4009aaaf31f11879 100644 (file)
@@ -14,6 +14,8 @@
  */
 
 extern int omap2_pm_init(void);
+extern int omap3_pm_init(void);
+
 extern unsigned short enable_dyn_sleep;
 extern atomic_t sleep_block;
 
diff --git a/arch/arm/mach-omap2/pm34xx.c b/arch/arm/mach-omap2/pm34xx.c
new file mode 100644 (file)
index 0000000..a1bfb30
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * linux/arch/arm/mach-omap2/pm34xx.c
+ *
+ * OMAP3 Power Management Routines
+ *
+ * Copyright (C) 2006-2008 Nokia Corporation
+ * Tony Lindgren <tony@atomide.com>
+ * Jouni Hogander
+ *
+ * Copyright (C) 2005 Texas Instruments, Inc.
+ * Richard Woodruff <r-woodruff2@ti.com>
+ *
+ * Based on pm.c for omap1
+ *
+ * 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.
+ */
+
+#include <linux/pm.h>
+#include <linux/suspend.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/err.h>
+
+#include <asm/arch/gpio.h>
+#include <asm/arch/sram.h>
+#include <asm/arch/pm.h>
+#include <asm/arch/clockdomain.h>
+#include <asm/arch/powerdomain.h>
+
+#include "cm.h"
+#include "cm-regbits-34xx.h"
+#include "prm-regbits-34xx.h"
+
+#include "prm.h"
+#include "pm.h"
+
+struct power_state {
+       struct powerdomain *pwrdm;
+       u32 next_state;
+       u32 saved_state;
+       struct list_head node;
+};
+
+static LIST_HEAD(pwrst_list);
+
+void (*_omap_sram_idle)(u32 *addr, int save_state);
+
+static void (*saved_idle)(void);
+
+static struct powerdomain *mpu_pwrdm;
+
+/* PRCM Interrupt Handler for wakeups */
+static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id)
+{
+       u32 wkst, irqstatus_mpu;
+       u32 fclk, iclk;
+
+       /* WKUP */
+       wkst = prm_read_mod_reg(WKUP_MOD, PM_WKST);
+       if (wkst) {
+               iclk = cm_read_mod_reg(WKUP_MOD, CM_ICLKEN);
+               fclk = cm_read_mod_reg(WKUP_MOD, CM_FCLKEN);
+               cm_set_mod_reg_bits(wkst, WKUP_MOD, CM_ICLKEN);
+               cm_set_mod_reg_bits(wkst, WKUP_MOD, CM_FCLKEN);
+               prm_write_mod_reg(wkst, WKUP_MOD, PM_WKST);
+               while (prm_read_mod_reg(WKUP_MOD, PM_WKST));
+               cm_write_mod_reg(iclk, WKUP_MOD, CM_ICLKEN);
+               cm_write_mod_reg(fclk, WKUP_MOD, CM_FCLKEN);
+       }
+
+       /* CORE */
+       wkst = prm_read_mod_reg(CORE_MOD, PM_WKST1);
+       if (wkst) {
+               iclk = cm_read_mod_reg(CORE_MOD, CM_ICLKEN1);
+               fclk = cm_read_mod_reg(CORE_MOD, CM_FCLKEN1);
+               cm_set_mod_reg_bits(wkst, CORE_MOD, CM_ICLKEN1);
+               cm_set_mod_reg_bits(wkst, CORE_MOD, CM_FCLKEN1);
+               prm_write_mod_reg(wkst, CORE_MOD, PM_WKST1);
+               while (prm_read_mod_reg(CORE_MOD, PM_WKST1));
+               cm_write_mod_reg(iclk, CORE_MOD, CM_ICLKEN1);
+               cm_write_mod_reg(fclk, CORE_MOD, CM_FCLKEN1);
+       }
+       wkst = prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3);
+       if (wkst) {
+               iclk = cm_read_mod_reg(CORE_MOD, CM_ICLKEN3);
+               fclk = cm_read_mod_reg(CORE_MOD, OMAP3430ES2_CM_FCLKEN3);
+               cm_set_mod_reg_bits(wkst, CORE_MOD, CM_ICLKEN3);
+               cm_set_mod_reg_bits(wkst, CORE_MOD, OMAP3430ES2_CM_FCLKEN3);
+               prm_write_mod_reg(wkst, CORE_MOD, OMAP3430ES2_PM_WKST3);
+               while (prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3));
+               cm_write_mod_reg(iclk, CORE_MOD, CM_ICLKEN3);
+               cm_write_mod_reg(fclk, CORE_MOD, OMAP3430ES2_CM_FCLKEN3);
+       }
+
+       /* PER */
+       wkst = prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST);
+       if (wkst) {
+               iclk = cm_read_mod_reg(OMAP3430_PER_MOD, CM_ICLKEN);
+               fclk = cm_read_mod_reg(OMAP3430_PER_MOD, CM_FCLKEN);
+               cm_set_mod_reg_bits(wkst, OMAP3430_PER_MOD, CM_ICLKEN);
+               cm_set_mod_reg_bits(wkst, OMAP3430_PER_MOD, CM_FCLKEN);
+               prm_write_mod_reg(wkst, OMAP3430_PER_MOD, PM_WKST);
+               while (prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST));
+               cm_write_mod_reg(iclk, OMAP3430_PER_MOD, CM_ICLKEN);
+               cm_write_mod_reg(fclk, OMAP3430_PER_MOD, CM_FCLKEN);
+       }
+
+       if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0)) {
+               /* USBHOST */
+               wkst = prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, PM_WKST);
+               if (wkst) {
+                       iclk = cm_read_mod_reg(OMAP3430ES2_USBHOST_MOD,
+                                              CM_ICLKEN);
+                       fclk = cm_read_mod_reg(OMAP3430ES2_USBHOST_MOD,
+                                              CM_FCLKEN);
+                       cm_set_mod_reg_bits(wkst, OMAP3430ES2_USBHOST_MOD,
+                                        CM_ICLKEN);
+                       cm_set_mod_reg_bits(wkst, OMAP3430ES2_USBHOST_MOD,
+                                        CM_FCLKEN);
+                       prm_write_mod_reg(wkst, OMAP3430ES2_USBHOST_MOD,
+                                         PM_WKST);
+                       while (prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD,
+                                               PM_WKST));
+                       cm_write_mod_reg(iclk, OMAP3430ES2_USBHOST_MOD,
+                                        CM_ICLKEN);
+                       cm_write_mod_reg(fclk, OMAP3430ES2_USBHOST_MOD,
+                                        CM_FCLKEN);
+               }
+       }
+
+       irqstatus_mpu = __raw_readl(OMAP3430_PRM_IRQSTATUS_MPU);
+       __raw_writel(irqstatus_mpu, OMAP3430_PRM_IRQSTATUS_MPU);
+       while (__raw_readl(OMAP3430_PRM_IRQSTATUS_MPU));
+
+       return IRQ_HANDLED;
+}
+
+static void omap_sram_idle(void)
+{
+       /* Variable to tell what needs to be saved and restored
+        * in omap_sram_idle*/
+       /* save_state = 0 => Nothing to save and restored */
+       /* save_state = 1 => Only L1 and logic lost */
+       /* save_state = 2 => Only L2 lost */
+       /* save_state = 3 => L1, L2 and logic lost */
+       int save_state = 0, mpu_next_state;
+
+       if (!_omap_sram_idle)
+               return;
+
+       mpu_next_state = pwrdm_read_next_pwrst(mpu_pwrdm);
+       switch (mpu_next_state) {
+       case PWRDM_POWER_RET:
+               /* No need to save context */
+               save_state = 0;
+               break;
+       default:
+               /* Invalid state */
+               printk(KERN_ERR "Invalid mpu state in sram_idle\n");
+               return;
+       }
+
+       omap2_gpio_prepare_for_retention();
+
+       _omap_sram_idle(NULL, save_state);
+
+       omap2_gpio_resume_after_retention();
+}
+
+static int omap3_can_sleep(void)
+{
+       if (!enable_dyn_sleep)
+               return 0;
+       if (atomic_read(&sleep_block) > 0)
+               return 0;
+       return 1;
+}
+
+/* This sets pwrdm state (other than mpu & core. Currently only ON &
+ * RET are supported. Function is assuming that clkdm doesn't have
+ * hw_sup mode enabled. */
+static int set_pwrdm_state(struct powerdomain *pwrdm, u32 state)
+{
+       u32 cur_state;
+       int ret = 0;
+       int i = 0;
+
+       if (pwrdm == NULL || IS_ERR(pwrdm))
+               return -EINVAL;
+
+       cur_state = pwrdm_read_next_pwrst(pwrdm);
+
+       if (cur_state == state)
+               return ret;
+
+       for (i = 0; pwrdm->pwrdm_clkdms[i]; i++)
+               omap2_clkdm_deny_idle(pwrdm->pwrdm_clkdms[i]);
+
+       ret = pwrdm_set_next_pwrst(pwrdm, state);
+       if (ret) {
+               printk(KERN_ERR "Unable to set state of powerdomain: %s\n",
+                      pwrdm->name);
+               goto err;
+       }
+
+       for (i = 0; pwrdm->pwrdm_clkdms[i]; i++)
+               omap2_clkdm_allow_idle(pwrdm->pwrdm_clkdms[i]);
+
+err:
+       return ret;
+}
+
+static void omap3_pm_idle(void)
+{
+       local_irq_disable();
+       local_fiq_disable();
+
+       if (!omap3_can_sleep())
+               goto out;
+
+       if (omap_irq_pending())
+               goto out;
+
+       omap_sram_idle();
+
+out:
+       local_fiq_enable();
+       local_irq_enable();
+}
+
+static int omap3_pm_prepare(void)
+{
+       saved_idle = pm_idle;
+       pm_idle = NULL;
+       return 0;
+}
+
+static int omap3_pm_suspend(void)
+{
+       struct power_state *pwrst;
+       int state, ret = 0;
+
+       /* Read current next_pwrsts */
+       list_for_each_entry(pwrst, &pwrst_list, node)
+               pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm);
+       /* Set ones wanted by suspend */
+       list_for_each_entry(pwrst, &pwrst_list, node) {
+               if (set_pwrdm_state(pwrst->pwrdm, pwrst->next_state))
+                       goto restore;
+               if (pwrdm_clear_all_prev_pwrst(pwrst->pwrdm))
+                       goto restore;
+       }
+
+       omap_sram_idle();
+
+restore:
+       /* Restore next_pwrsts */
+       list_for_each_entry(pwrst, &pwrst_list, node) {
+               set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state);
+               state = pwrdm_read_prev_pwrst(pwrst->pwrdm);
+               if (state != pwrst->next_state) {
+                       printk(KERN_INFO "Powerdomain (%s) didn't enter "
+                              "target state %d\n",
+                              pwrst->pwrdm->name, pwrst->next_state);
+                       ret = -1;
+               }
+       }
+       if (ret)
+               printk(KERN_ERR "Could not enter target state in pm_suspend\n");
+       else
+               printk(KERN_INFO "Successfully put all powerdomains "
+                      "to target state\n");
+
+       return ret;
+}
+
+static int omap3_pm_enter(suspend_state_t state)
+{
+       int ret = 0;
+
+       switch (state) {
+       case PM_SUSPEND_STANDBY:
+       case PM_SUSPEND_MEM:
+               ret = omap3_pm_suspend();
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+static void omap3_pm_finish(void)
+{
+       pm_idle = saved_idle;
+}
+
+static struct platform_suspend_ops omap_pm_ops = {
+       .prepare        = omap3_pm_prepare,
+       .enter          = omap3_pm_enter,
+       .finish         = omap3_pm_finish,
+       .valid          = suspend_valid_only_mem,
+};
+
+static void __init prcm_setup_regs(void)
+{
+       u32 v;
+
+       /* setup wakup source */
+       prm_write_mod_reg(OMAP3430_EN_IO | OMAP3430_EN_GPIO1 | OMAP3430_EN_GPT1,
+                         WKUP_MOD, PM_WKEN);
+       /* No need to write EN_IO, that is always enabled */
+       prm_write_mod_reg(OMAP3430_EN_GPIO1 | OMAP3430_EN_GPT1,
+                         WKUP_MOD, OMAP3430_PM_MPUGRPSEL);
+       /* For some reason IO doesn't generate wakeup event even if
+        * it is selected to mpu wakeup goup */
+       __raw_writel(OMAP3430_IO_EN | OMAP3430_WKUP_EN,
+                    OMAP3430_PRM_IRQENABLE_MPU);
+}
+
+static int __init pwrdms_setup(struct powerdomain *pwrdm)
+{
+       struct power_state *pwrst;
+
+       if (!pwrdm->pwrsts)
+               return 0;
+
+       pwrst = kmalloc(sizeof(struct power_state), GFP_KERNEL);
+       if (!pwrst)
+               return -ENOMEM;
+       pwrst->pwrdm = pwrdm;
+       pwrst->next_state = PWRDM_POWER_RET;
+       list_add(&pwrst->node, &pwrst_list);
+       return set_pwrdm_state(pwrst->pwrdm, pwrst->next_state);
+}
+
+int __init omap3_pm_init(void)
+{
+       struct power_state *pwrst;
+       int ret;
+
+       printk(KERN_ERR "Power Management for TI OMAP3.\n");
+
+       ret = request_irq(INT_34XX_PRCM_MPU_IRQ,
+                         (irq_handler_t)prcm_interrupt_handler,
+                         IRQF_DISABLED, "prcm", NULL);
+       if (ret) {
+               printk(KERN_ERR "request_irq failed to register for 0x%x\n",
+                      INT_34XX_PRCM_MPU_IRQ);
+               goto err1;
+       }
+
+       ret = pwrdm_for_each(pwrdms_setup);
+       if (ret) {
+               printk(KERN_ERR "Failed to setup powerdomains\n");
+               goto err2;
+       }
+
+       mpu_pwrdm = pwrdm_lookup("mpu_pwrdm");
+       if (mpu_pwrdm == NULL) {
+               printk(KERN_ERR "Failed to get mpu_pwrdm\n");
+               goto err2;
+       }
+
+       _omap_sram_idle = omap_sram_push(omap34xx_cpu_suspend,
+                                       omap34xx_cpu_suspend_sz);
+
+       suspend_set_ops(&omap_pm_ops);
+
+       prcm_setup_regs();
+
+       pm_idle = omap3_pm_idle;
+
+err1:
+       return ret;
+err2:
+       free_irq(INT_34XX_PRCM_MPU_IRQ, NULL);
+       list_for_each_entry(pwrst, &pwrst_list, node) {
+               list_del(&pwrst->node);
+               kfree(pwrst);
+       }
+       return ret;
+}