OMAP3: PM: Prevent PER from going OFF when CORE is going INA
[pandora-kernel.git] / arch / arm / mach-omap2 / pm34xx.c
index 8946319..55567bf 100644 (file)
@@ -5,6 +5,9 @@
  * Tony Lindgren <tony@atomide.com>
  * Jouni Hogander
  *
+ * Copyright (C) 2007 Texas Instruments, Inc.
+ * Rajendra Nayak <rnayak@ti.com>
+ *
  * Copyright (C) 2005 Texas Instruments, Inc.
  * Richard Woodruff <r-woodruff2@ti.com>
  *
 #include <linux/list.h>
 #include <linux/err.h>
 #include <linux/gpio.h>
+#include <linux/clk.h>
+
+#include <plat/sram.h>
+#include <plat/clockdomain.h>
+#include <plat/powerdomain.h>
+#include <plat/control.h>
+#include <plat/serial.h>
+#include <plat/sdrc.h>
+#include <plat/prcm.h>
+#include <plat/gpmc.h>
+#include <plat/dma.h>
+#include <plat/dmtimer.h>
 
-#include <mach/sram.h>
-#include <mach/clockdomain.h>
-#include <mach/powerdomain.h>
-#include <mach/control.h>
-#include <mach/serial.h>
+#include <asm/tlbflush.h>
 
 #include "cm.h"
 #include "cm-regbits-34xx.h"
 
 #include "prm.h"
 #include "pm.h"
+#include "sdrc.h"
+
+#define SDRC_POWER_AUTOCOUNT_SHIFT 8
+#define SDRC_POWER_AUTOCOUNT_MASK (0xffff << SDRC_POWER_AUTOCOUNT_SHIFT)
+#define SDRC_POWER_CLKCTRL_SHIFT 4
+#define SDRC_POWER_CLKCTRL_MASK (0x3 << SDRC_POWER_CLKCTRL_SHIFT)
+#define SDRC_SELF_REFRESH_ON_AUTOCOUNT (0x2 << SDRC_POWER_CLKCTRL_SHIFT)
+
+/* Scratchpad offsets */
+#define OMAP343X_TABLE_ADDRESS_OFFSET     0x31
+#define OMAP343X_TABLE_VALUE_OFFSET       0x30
+#define OMAP343X_CONTROL_REG_VALUE_OFFSET  0x32
+
+u32 enable_off_mode;
+u32 sleep_while_idle;
+u32 wakeup_timer_seconds;
 
 struct power_state {
        struct powerdomain *pwrdm;
@@ -49,7 +76,84 @@ static LIST_HEAD(pwrst_list);
 
 static void (*_omap_sram_idle)(u32 *addr, int save_state);
 
-static struct powerdomain *mpu_pwrdm;
+static int (*_omap_save_secure_sram)(u32 *addr);
+
+static struct powerdomain *mpu_pwrdm, *neon_pwrdm;
+static struct powerdomain *core_pwrdm, *per_pwrdm;
+
+static int set_pwrdm_state(struct powerdomain *pwrdm, u32 state);
+
+static inline void omap3_per_save_context(void)
+{
+       omap_gpio_save_context();
+}
+
+static inline void omap3_per_restore_context(void)
+{
+       omap_gpio_restore_context();
+}
+
+static void omap3_core_save_context(void)
+{
+       u32 control_padconf_off;
+
+       /* Save the padconf registers */
+       control_padconf_off = omap_ctrl_readl(OMAP343X_CONTROL_PADCONF_OFF);
+       control_padconf_off |= START_PADCONF_SAVE;
+       omap_ctrl_writel(control_padconf_off, OMAP343X_CONTROL_PADCONF_OFF);
+       /* wait for the save to complete */
+       while (!omap_ctrl_readl(OMAP343X_CONTROL_GENERAL_PURPOSE_STATUS)
+                       & PADCONF_SAVE_DONE)
+               ;
+       /* Save the Interrupt controller context */
+       omap_intc_save_context();
+       /* Save the GPMC context */
+       omap3_gpmc_save_context();
+       /* Save the system control module context, padconf already save above*/
+       omap3_control_save_context();
+       omap_dma_global_context_save();
+}
+
+static void omap3_core_restore_context(void)
+{
+       /* Restore the control module context, padconf restored by h/w */
+       omap3_control_restore_context();
+       /* Restore the GPMC context */
+       omap3_gpmc_restore_context();
+       /* Restore the interrupt controller context */
+       omap_intc_restore_context();
+       omap_dma_global_context_restore();
+}
+
+/*
+ * FIXME: This function should be called before entering off-mode after
+ * OMAP3 secure services have been accessed. Currently it is only called
+ * once during boot sequence, but this works as we are not using secure
+ * services.
+ */
+static void omap3_save_secure_ram_context(u32 target_mpu_state)
+{
+       u32 ret;
+
+       if (omap_type() != OMAP2_DEVICE_TYPE_GP) {
+               /*
+                * MPU next state must be set to POWER_ON temporarily,
+                * otherwise the WFI executed inside the ROM code
+                * will hang the system.
+                */
+               pwrdm_set_next_pwrst(mpu_pwrdm, PWRDM_POWER_ON);
+               ret = _omap_save_secure_sram((u32 *)
+                               __pa(omap3_secure_ram_storage));
+               pwrdm_set_next_pwrst(mpu_pwrdm, target_mpu_state);
+               /* Following is for error tracking, it should not happen */
+               if (ret) {
+                       printk(KERN_ERR "save_secure_sram() returns %08x\n",
+                               ret);
+                       while (1)
+                               ;
+               }
+       }
+}
 
 /*
  * PRCM Interrupt Handler Helper Function
@@ -161,6 +265,35 @@ static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
+static void restore_control_register(u32 val)
+{
+       __asm__ __volatile__ ("mcr p15, 0, %0, c1, c0, 0" : : "r" (val));
+}
+
+/* Function to restore the table entry that was modified for enabling MMU */
+static void restore_table_entry(void)
+{
+       u32 *scratchpad_address;
+       u32 previous_value, control_reg_value;
+       u32 *address;
+
+       scratchpad_address = OMAP2_L4_IO_ADDRESS(OMAP343X_SCRATCHPAD);
+
+       /* Get address of entry that was modified */
+       address = (u32 *)__raw_readl(scratchpad_address +
+                                    OMAP343X_TABLE_ADDRESS_OFFSET);
+       /* Get the previous value which needs to be restored */
+       previous_value = __raw_readl(scratchpad_address +
+                                    OMAP343X_TABLE_VALUE_OFFSET);
+       address = __va(address);
+       *address = previous_value;
+       flush_tlb_all();
+       control_reg_value = __raw_readl(scratchpad_address
+                                       + OMAP343X_CONTROL_REG_VALUE_OFFSET);
+       /* This will enable caches and prediction */
+       restore_control_register(control_reg_value);
+}
+
 static void omap_sram_idle(void)
 {
        /* Variable to tell what needs to be saved and restored
@@ -169,17 +302,32 @@ static void omap_sram_idle(void)
        /* 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;
+       int save_state = 0;
+       int mpu_next_state = PWRDM_POWER_ON;
+       int per_next_state = PWRDM_POWER_ON;
+       int core_next_state = PWRDM_POWER_ON;
+       int core_prev_state, per_prev_state;
+       u32 sdrc_pwr = 0;
+       int per_state_modified = 0;
 
        if (!_omap_sram_idle)
                return;
 
+       pwrdm_clear_all_prev_pwrst(mpu_pwrdm);
+       pwrdm_clear_all_prev_pwrst(neon_pwrdm);
+       pwrdm_clear_all_prev_pwrst(core_pwrdm);
+       pwrdm_clear_all_prev_pwrst(per_pwrdm);
+
        mpu_next_state = pwrdm_read_next_pwrst(mpu_pwrdm);
        switch (mpu_next_state) {
+       case PWRDM_POWER_ON:
        case PWRDM_POWER_RET:
                /* No need to save context */
                save_state = 0;
                break;
+       case PWRDM_POWER_OFF:
+               save_state = 3;
+               break;
        default:
                /* Invalid state */
                printk(KERN_ERR "Invalid mpu state in sram_idle\n");
@@ -187,18 +335,102 @@ static void omap_sram_idle(void)
        }
        pwrdm_pre_transition();
 
-       omap2_gpio_prepare_for_retention();
-       omap_uart_prepare_idle(0);
-       omap_uart_prepare_idle(1);
-       omap_uart_prepare_idle(2);
+       /* NEON control */
+       if (pwrdm_read_pwrst(neon_pwrdm) == PWRDM_POWER_ON)
+               set_pwrdm_state(neon_pwrdm, mpu_next_state);
+
+       /* PER */
+       per_next_state = pwrdm_read_next_pwrst(per_pwrdm);
+       core_next_state = pwrdm_read_next_pwrst(core_pwrdm);
+       if (per_next_state < PWRDM_POWER_ON) {
+               omap_uart_prepare_idle(2);
+               omap2_gpio_prepare_for_retention();
+               if (per_next_state == PWRDM_POWER_OFF) {
+                       if (core_next_state == PWRDM_POWER_ON) {
+                               per_next_state = PWRDM_POWER_RET;
+                               pwrdm_set_next_pwrst(per_pwrdm, per_next_state);
+                               per_state_modified = 1;
+                       } else
+                               omap3_per_save_context();
+               }
+       }
+
+       /* CORE */
+       if (core_next_state < PWRDM_POWER_ON) {
+               omap_uart_prepare_idle(0);
+               omap_uart_prepare_idle(1);
+               if (core_next_state == PWRDM_POWER_OFF) {
+                       omap3_core_save_context();
+                       omap3_prcm_save_context();
+               }
+               /* Enable IO-PAD wakeup */
+               prm_set_mod_reg_bits(OMAP3430_EN_IO, WKUP_MOD, PM_WKEN);
+       }
+
+       /*
+        * Force SDRAM controller to self-refresh mode after timeout on
+        * autocount. This is needed on ES3.0 to avoid SDRAM controller
+        * hang-ups.
+        */
+       if (omap_rev() >= OMAP3430_REV_ES3_0 &&
+           omap_type() != OMAP2_DEVICE_TYPE_GP &&
+           core_next_state == PWRDM_POWER_OFF) {
+               sdrc_pwr = sdrc_read_reg(SDRC_POWER);
+               sdrc_write_reg((sdrc_pwr &
+                       ~(SDRC_POWER_AUTOCOUNT_MASK|SDRC_POWER_CLKCTRL_MASK)) |
+                       (1 << SDRC_POWER_AUTOCOUNT_SHIFT) |
+                       SDRC_SELF_REFRESH_ON_AUTOCOUNT, SDRC_POWER);
+       }
 
-       _omap_sram_idle(NULL, save_state);
+       /*
+        * omap3_arm_context is the location where ARM registers
+        * get saved. The restore path then reads from this
+        * location and restores them back.
+        */
+       _omap_sram_idle(omap3_arm_context, save_state);
        cpu_init();
 
-       omap_uart_resume_idle(2);
-       omap_uart_resume_idle(1);
-       omap_uart_resume_idle(0);
-       omap2_gpio_resume_after_retention();
+       /* Restore normal SDRAM settings */
+       if (omap_rev() >= OMAP3430_REV_ES3_0 &&
+           omap_type() != OMAP2_DEVICE_TYPE_GP &&
+           core_next_state == PWRDM_POWER_OFF)
+               sdrc_write_reg(sdrc_pwr, SDRC_POWER);
+
+       /* Restore table entry modified during MMU restoration */
+       if (pwrdm_read_prev_pwrst(mpu_pwrdm) == PWRDM_POWER_OFF)
+               restore_table_entry();
+
+       /* CORE */
+       if (core_next_state < PWRDM_POWER_ON) {
+               core_prev_state = pwrdm_read_prev_pwrst(core_pwrdm);
+               if (core_prev_state == PWRDM_POWER_OFF) {
+                       omap3_core_restore_context();
+                       omap3_prcm_restore_context();
+                       omap3_sram_restore_context();
+                       omap2_sms_restore_context();
+               }
+               omap_uart_resume_idle(0);
+               omap_uart_resume_idle(1);
+               if (core_next_state == PWRDM_POWER_OFF)
+                       prm_clear_mod_reg_bits(OMAP3430_AUTO_OFF,
+                                              OMAP3430_GR_MOD,
+                                              OMAP3_PRM_VOLTCTRL_OFFSET);
+       }
+
+       /* PER */
+       if (per_next_state < PWRDM_POWER_ON) {
+               per_prev_state = pwrdm_read_prev_pwrst(per_pwrdm);
+               if (per_prev_state == PWRDM_POWER_OFF)
+                       omap3_per_restore_context();
+               omap2_gpio_resume_after_retention();
+               omap_uart_resume_idle(2);
+               if (per_state_modified)
+                       pwrdm_set_next_pwrst(per_pwrdm, PWRDM_POWER_OFF);
+       }
+
+       /* Disable IO-PAD wakeup */
+       if (core_next_state < PWRDM_POWER_ON)
+               prm_clear_mod_reg_bits(OMAP3430_EN_IO, WKUP_MOD, PM_WKEN);
 
        pwrdm_post_transition();
 
@@ -246,6 +478,8 @@ static int omap3_fclks_active(void)
 
 static int omap3_can_sleep(void)
 {
+       if (!sleep_while_idle)
+               return 0;
        if (!omap_uart_can_sleep())
                return 0;
        if (omap3_fclks_active())
@@ -319,6 +553,22 @@ out:
 #ifdef CONFIG_SUSPEND
 static suspend_state_t suspend_state;
 
+static void omap2_pm_wakeup_on_timer(u32 seconds)
+{
+       u32 tick_rate, cycles;
+
+       if (!seconds)
+               return;
+
+       tick_rate = clk_get_rate(omap_dm_timer_get_fclk(gptimer_wakeup));
+       cycles = tick_rate * seconds;
+       omap_dm_timer_stop(gptimer_wakeup);
+       omap_dm_timer_set_load_start(gptimer_wakeup, 0, 0xffffffff - cycles);
+
+       pr_info("PM: Resume timer in %d secs (%d ticks at %d ticks/sec.)\n",
+               seconds, cycles, tick_rate);
+}
+
 static int omap3_pm_prepare(void)
 {
        disable_hlt();
@@ -330,6 +580,9 @@ static int omap3_pm_suspend(void)
        struct power_state *pwrst;
        int state, ret = 0;
 
+       if (wakeup_timer_seconds)
+               omap2_pm_wakeup_on_timer(wakeup_timer_seconds);
+
        /* Read current next_pwrsts */
        list_for_each_entry(pwrst, &pwrst_list, node)
                pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm);
@@ -690,6 +943,22 @@ static void __init prcm_setup_regs(void)
        omap3_d2d_idle();
 }
 
+void omap3_pm_off_mode_enable(int enable)
+{
+       struct power_state *pwrst;
+       u32 state;
+
+       if (enable)
+               state = PWRDM_POWER_OFF;
+       else
+               state = PWRDM_POWER_RET;
+
+       list_for_each_entry(pwrst, &pwrst_list, node) {
+               pwrst->next_state = state;
+               set_pwrdm_state(pwrst->pwrdm, state);
+       }
+}
+
 int omap3_pm_get_suspend_state(struct powerdomain *pwrdm)
 {
        struct power_state *pwrst;
@@ -749,6 +1018,15 @@ static int __init clkdms_setup(struct clockdomain *clkdm, void *unused)
        return 0;
 }
 
+void omap_push_sram_idle(void)
+{
+       _omap_sram_idle = omap_sram_push(omap34xx_cpu_suspend,
+                                       omap34xx_cpu_suspend_sz);
+       if (omap_type() != OMAP2_DEVICE_TYPE_GP)
+               _omap_save_secure_sram = omap_sram_push(save_secure_ram_context,
+                               save_secure_ram_context_sz);
+}
+
 static int __init omap3_pm_init(void)
 {
        struct power_state *pwrst, *tmp;
@@ -786,15 +1064,45 @@ static int __init omap3_pm_init(void)
                goto err2;
        }
 
-       _omap_sram_idle = omap_sram_push(omap34xx_cpu_suspend,
-                                        omap34xx_cpu_suspend_sz);
+       neon_pwrdm = pwrdm_lookup("neon_pwrdm");
+       per_pwrdm = pwrdm_lookup("per_pwrdm");
+       core_pwrdm = pwrdm_lookup("core_pwrdm");
 
+       omap_push_sram_idle();
 #ifdef CONFIG_SUSPEND
        suspend_set_ops(&omap_pm_ops);
 #endif /* CONFIG_SUSPEND */
 
        pm_idle = omap3_pm_idle;
 
+       pwrdm_add_wkdep(neon_pwrdm, mpu_pwrdm);
+       /*
+        * REVISIT: This wkdep is only necessary when GPIO2-6 are enabled for
+        * IO-pad wakeup.  Otherwise it will unnecessarily waste power
+        * waking up PER with every CORE wakeup - see
+        * http://marc.info/?l=linux-omap&m=121852150710062&w=2
+       */
+       pwrdm_add_wkdep(per_pwrdm, core_pwrdm);
+
+       if (omap_type() != OMAP2_DEVICE_TYPE_GP) {
+               omap3_secure_ram_storage =
+                       kmalloc(0x803F, GFP_KERNEL);
+               if (!omap3_secure_ram_storage)
+                       printk(KERN_ERR "Memory allocation failed when"
+                                       "allocating for secure sram context\n");
+
+               local_irq_disable();
+               local_fiq_disable();
+
+               omap_dma_global_context_save();
+               omap3_save_secure_ram_context(PWRDM_POWER_ON);
+               omap_dma_global_context_restore();
+
+               local_irq_enable();
+               local_fiq_enable();
+       }
+
+       omap3_save_scratchpad_contents();
 err1:
        return ret;
 err2: