Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 1 | /* |
| 2 | * linux/arch/arm/mach-omap2/pm.c |
| 3 | * |
| 4 | * OMAP2 Power Management Routines |
| 5 | * |
| 6 | * Copyright (C) 2006 Nokia Corporation |
| 7 | * Tony Lindgren <tony@atomide.com> |
| 8 | * |
| 9 | * Copyright (C) 2005 Texas Instruments, Inc. |
| 10 | * Richard Woodruff <r-woodruff2@ti.com> |
| 11 | * |
| 12 | * Based on pm.c for omap1 |
| 13 | * |
| 14 | * This program is free software; you can redistribute it and/or modify |
| 15 | * it under the terms of the GNU General Public License version 2 as |
| 16 | * published by the Free Software Foundation. |
| 17 | */ |
| 18 | |
Rafael J. Wysocki | 95d9ffb | 2007-10-18 03:04:39 -0700 | [diff] [blame^] | 19 | #include <linux/suspend.h> |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 20 | #include <linux/sched.h> |
| 21 | #include <linux/proc_fs.h> |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 22 | #include <linux/interrupt.h> |
| 23 | #include <linux/sysfs.h> |
| 24 | #include <linux/module.h> |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 25 | #include <linux/delay.h> |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 26 | |
| 27 | #include <asm/io.h> |
| 28 | #include <asm/irq.h> |
| 29 | #include <asm/atomic.h> |
| 30 | #include <asm/mach/time.h> |
| 31 | #include <asm/mach/irq.h> |
| 32 | #include <asm/mach-types.h> |
| 33 | |
| 34 | #include <asm/arch/irqs.h> |
| 35 | #include <asm/arch/clock.h> |
| 36 | #include <asm/arch/sram.h> |
| 37 | #include <asm/arch/pm.h> |
| 38 | |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 39 | #include "prcm-regs.h" |
| 40 | |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 41 | static struct clk *vclk; |
| 42 | static void (*omap2_sram_idle)(void); |
| 43 | static void (*omap2_sram_suspend)(int dllctrl, int cpu_rev); |
| 44 | static void (*saved_idle)(void); |
| 45 | |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 46 | extern void __init pmdomain_init(void); |
| 47 | extern void pmdomain_set_autoidle(void); |
| 48 | |
| 49 | static unsigned int omap24xx_sleep_save[OMAP24XX_SLEEP_SAVE_SIZE]; |
| 50 | |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 51 | void omap2_pm_idle(void) |
| 52 | { |
| 53 | local_irq_disable(); |
| 54 | local_fiq_disable(); |
| 55 | if (need_resched()) { |
| 56 | local_fiq_enable(); |
| 57 | local_irq_enable(); |
| 58 | return; |
| 59 | } |
| 60 | |
| 61 | /* |
| 62 | * Since an interrupt may set up a timer, we don't want to |
| 63 | * reprogram the hardware timer with interrupts enabled. |
| 64 | * Re-enable interrupts only after returning from idle. |
| 65 | */ |
| 66 | timer_dyn_reprogram(); |
| 67 | |
| 68 | omap2_sram_idle(); |
| 69 | local_fiq_enable(); |
| 70 | local_irq_enable(); |
| 71 | } |
| 72 | |
| 73 | static int omap2_pm_prepare(suspend_state_t state) |
| 74 | { |
| 75 | int error = 0; |
| 76 | |
| 77 | /* We cannot sleep in idle until we have resumed */ |
| 78 | saved_idle = pm_idle; |
| 79 | pm_idle = NULL; |
| 80 | |
| 81 | switch (state) |
| 82 | { |
| 83 | case PM_SUSPEND_STANDBY: |
| 84 | case PM_SUSPEND_MEM: |
| 85 | break; |
| 86 | |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 87 | default: |
| 88 | return -EINVAL; |
| 89 | } |
| 90 | |
| 91 | return error; |
| 92 | } |
| 93 | |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 94 | #define INT0_WAKE_MASK (OMAP_IRQ_BIT(INT_24XX_GPIO_BANK1) | \ |
| 95 | OMAP_IRQ_BIT(INT_24XX_GPIO_BANK2) | \ |
| 96 | OMAP_IRQ_BIT(INT_24XX_GPIO_BANK3)) |
| 97 | |
| 98 | #define INT1_WAKE_MASK (OMAP_IRQ_BIT(INT_24XX_GPIO_BANK4)) |
| 99 | |
| 100 | #define INT2_WAKE_MASK (OMAP_IRQ_BIT(INT_24XX_UART1_IRQ) | \ |
| 101 | OMAP_IRQ_BIT(INT_24XX_UART2_IRQ) | \ |
| 102 | OMAP_IRQ_BIT(INT_24XX_UART3_IRQ)) |
| 103 | |
| 104 | #define preg(reg) printk("%s\t(0x%p):\t0x%08x\n", #reg, ®, reg); |
| 105 | |
| 106 | static void omap2_pm_debug(char * desc) |
| 107 | { |
| 108 | printk("%s:\n", desc); |
| 109 | |
| 110 | preg(CM_CLKSTCTRL_MPU); |
| 111 | preg(CM_CLKSTCTRL_CORE); |
| 112 | preg(CM_CLKSTCTRL_GFX); |
| 113 | preg(CM_CLKSTCTRL_DSP); |
| 114 | preg(CM_CLKSTCTRL_MDM); |
| 115 | |
| 116 | preg(PM_PWSTCTRL_MPU); |
| 117 | preg(PM_PWSTCTRL_CORE); |
| 118 | preg(PM_PWSTCTRL_GFX); |
| 119 | preg(PM_PWSTCTRL_DSP); |
| 120 | preg(PM_PWSTCTRL_MDM); |
| 121 | |
| 122 | preg(PM_PWSTST_MPU); |
| 123 | preg(PM_PWSTST_CORE); |
| 124 | preg(PM_PWSTST_GFX); |
| 125 | preg(PM_PWSTST_DSP); |
| 126 | preg(PM_PWSTST_MDM); |
| 127 | |
| 128 | preg(CM_AUTOIDLE1_CORE); |
| 129 | preg(CM_AUTOIDLE2_CORE); |
| 130 | preg(CM_AUTOIDLE3_CORE); |
| 131 | preg(CM_AUTOIDLE4_CORE); |
| 132 | preg(CM_AUTOIDLE_WKUP); |
| 133 | preg(CM_AUTOIDLE_PLL); |
| 134 | preg(CM_AUTOIDLE_DSP); |
| 135 | preg(CM_AUTOIDLE_MDM); |
| 136 | |
| 137 | preg(CM_ICLKEN1_CORE); |
| 138 | preg(CM_ICLKEN2_CORE); |
| 139 | preg(CM_ICLKEN3_CORE); |
| 140 | preg(CM_ICLKEN4_CORE); |
| 141 | preg(CM_ICLKEN_GFX); |
| 142 | preg(CM_ICLKEN_WKUP); |
| 143 | preg(CM_ICLKEN_DSP); |
| 144 | preg(CM_ICLKEN_MDM); |
| 145 | |
| 146 | preg(CM_IDLEST1_CORE); |
| 147 | preg(CM_IDLEST2_CORE); |
| 148 | preg(CM_IDLEST3_CORE); |
| 149 | preg(CM_IDLEST4_CORE); |
| 150 | preg(CM_IDLEST_GFX); |
| 151 | preg(CM_IDLEST_WKUP); |
| 152 | preg(CM_IDLEST_CKGEN); |
| 153 | preg(CM_IDLEST_DSP); |
| 154 | preg(CM_IDLEST_MDM); |
| 155 | |
| 156 | preg(RM_RSTST_MPU); |
| 157 | preg(RM_RSTST_GFX); |
| 158 | preg(RM_RSTST_WKUP); |
| 159 | preg(RM_RSTST_DSP); |
| 160 | preg(RM_RSTST_MDM); |
| 161 | |
| 162 | preg(PM_WKDEP_MPU); |
| 163 | preg(PM_WKDEP_CORE); |
| 164 | preg(PM_WKDEP_GFX); |
| 165 | preg(PM_WKDEP_DSP); |
| 166 | preg(PM_WKDEP_MDM); |
| 167 | |
| 168 | preg(CM_FCLKEN_WKUP); |
| 169 | preg(CM_ICLKEN_WKUP); |
| 170 | preg(CM_IDLEST_WKUP); |
| 171 | preg(CM_AUTOIDLE_WKUP); |
| 172 | preg(CM_CLKSEL_WKUP); |
| 173 | |
| 174 | preg(PM_WKEN_WKUP); |
| 175 | preg(PM_WKST_WKUP); |
| 176 | } |
| 177 | |
| 178 | static inline void omap2_pm_save_registers(void) |
| 179 | { |
| 180 | /* Save interrupt registers */ |
| 181 | OMAP24XX_SAVE(INTC_MIR0); |
| 182 | OMAP24XX_SAVE(INTC_MIR1); |
| 183 | OMAP24XX_SAVE(INTC_MIR2); |
| 184 | |
| 185 | /* Save power control registers */ |
| 186 | OMAP24XX_SAVE(CM_CLKSTCTRL_MPU); |
| 187 | OMAP24XX_SAVE(CM_CLKSTCTRL_CORE); |
| 188 | OMAP24XX_SAVE(CM_CLKSTCTRL_GFX); |
| 189 | OMAP24XX_SAVE(CM_CLKSTCTRL_DSP); |
| 190 | OMAP24XX_SAVE(CM_CLKSTCTRL_MDM); |
| 191 | |
| 192 | /* Save power state registers */ |
| 193 | OMAP24XX_SAVE(PM_PWSTCTRL_MPU); |
| 194 | OMAP24XX_SAVE(PM_PWSTCTRL_CORE); |
| 195 | OMAP24XX_SAVE(PM_PWSTCTRL_GFX); |
| 196 | OMAP24XX_SAVE(PM_PWSTCTRL_DSP); |
| 197 | OMAP24XX_SAVE(PM_PWSTCTRL_MDM); |
| 198 | |
| 199 | /* Save autoidle registers */ |
| 200 | OMAP24XX_SAVE(CM_AUTOIDLE1_CORE); |
| 201 | OMAP24XX_SAVE(CM_AUTOIDLE2_CORE); |
| 202 | OMAP24XX_SAVE(CM_AUTOIDLE3_CORE); |
| 203 | OMAP24XX_SAVE(CM_AUTOIDLE4_CORE); |
| 204 | OMAP24XX_SAVE(CM_AUTOIDLE_WKUP); |
| 205 | OMAP24XX_SAVE(CM_AUTOIDLE_PLL); |
| 206 | OMAP24XX_SAVE(CM_AUTOIDLE_DSP); |
| 207 | OMAP24XX_SAVE(CM_AUTOIDLE_MDM); |
| 208 | |
| 209 | /* Save idle state registers */ |
| 210 | OMAP24XX_SAVE(CM_IDLEST1_CORE); |
| 211 | OMAP24XX_SAVE(CM_IDLEST2_CORE); |
| 212 | OMAP24XX_SAVE(CM_IDLEST3_CORE); |
| 213 | OMAP24XX_SAVE(CM_IDLEST4_CORE); |
| 214 | OMAP24XX_SAVE(CM_IDLEST_GFX); |
| 215 | OMAP24XX_SAVE(CM_IDLEST_WKUP); |
| 216 | OMAP24XX_SAVE(CM_IDLEST_CKGEN); |
| 217 | OMAP24XX_SAVE(CM_IDLEST_DSP); |
| 218 | OMAP24XX_SAVE(CM_IDLEST_MDM); |
| 219 | |
| 220 | /* Save clock registers */ |
| 221 | OMAP24XX_SAVE(CM_FCLKEN1_CORE); |
| 222 | OMAP24XX_SAVE(CM_FCLKEN2_CORE); |
| 223 | OMAP24XX_SAVE(CM_ICLKEN1_CORE); |
| 224 | OMAP24XX_SAVE(CM_ICLKEN2_CORE); |
| 225 | OMAP24XX_SAVE(CM_ICLKEN3_CORE); |
| 226 | OMAP24XX_SAVE(CM_ICLKEN4_CORE); |
| 227 | } |
| 228 | |
| 229 | static inline void omap2_pm_restore_registers(void) |
| 230 | { |
| 231 | /* Restore clock state registers */ |
| 232 | OMAP24XX_RESTORE(CM_CLKSTCTRL_MPU); |
| 233 | OMAP24XX_RESTORE(CM_CLKSTCTRL_CORE); |
| 234 | OMAP24XX_RESTORE(CM_CLKSTCTRL_GFX); |
| 235 | OMAP24XX_RESTORE(CM_CLKSTCTRL_DSP); |
| 236 | OMAP24XX_RESTORE(CM_CLKSTCTRL_MDM); |
| 237 | |
| 238 | /* Restore power state registers */ |
| 239 | OMAP24XX_RESTORE(PM_PWSTCTRL_MPU); |
| 240 | OMAP24XX_RESTORE(PM_PWSTCTRL_CORE); |
| 241 | OMAP24XX_RESTORE(PM_PWSTCTRL_GFX); |
| 242 | OMAP24XX_RESTORE(PM_PWSTCTRL_DSP); |
| 243 | OMAP24XX_RESTORE(PM_PWSTCTRL_MDM); |
| 244 | |
| 245 | /* Restore idle state registers */ |
| 246 | OMAP24XX_RESTORE(CM_IDLEST1_CORE); |
| 247 | OMAP24XX_RESTORE(CM_IDLEST2_CORE); |
| 248 | OMAP24XX_RESTORE(CM_IDLEST3_CORE); |
| 249 | OMAP24XX_RESTORE(CM_IDLEST4_CORE); |
| 250 | OMAP24XX_RESTORE(CM_IDLEST_GFX); |
| 251 | OMAP24XX_RESTORE(CM_IDLEST_WKUP); |
| 252 | OMAP24XX_RESTORE(CM_IDLEST_CKGEN); |
| 253 | OMAP24XX_RESTORE(CM_IDLEST_DSP); |
| 254 | OMAP24XX_RESTORE(CM_IDLEST_MDM); |
| 255 | |
| 256 | /* Restore autoidle registers */ |
| 257 | OMAP24XX_RESTORE(CM_AUTOIDLE1_CORE); |
| 258 | OMAP24XX_RESTORE(CM_AUTOIDLE2_CORE); |
| 259 | OMAP24XX_RESTORE(CM_AUTOIDLE3_CORE); |
| 260 | OMAP24XX_RESTORE(CM_AUTOIDLE4_CORE); |
| 261 | OMAP24XX_RESTORE(CM_AUTOIDLE_WKUP); |
| 262 | OMAP24XX_RESTORE(CM_AUTOIDLE_PLL); |
| 263 | OMAP24XX_RESTORE(CM_AUTOIDLE_DSP); |
| 264 | OMAP24XX_RESTORE(CM_AUTOIDLE_MDM); |
| 265 | |
| 266 | /* Restore clock registers */ |
| 267 | OMAP24XX_RESTORE(CM_FCLKEN1_CORE); |
| 268 | OMAP24XX_RESTORE(CM_FCLKEN2_CORE); |
| 269 | OMAP24XX_RESTORE(CM_ICLKEN1_CORE); |
| 270 | OMAP24XX_RESTORE(CM_ICLKEN2_CORE); |
| 271 | OMAP24XX_RESTORE(CM_ICLKEN3_CORE); |
| 272 | OMAP24XX_RESTORE(CM_ICLKEN4_CORE); |
| 273 | |
| 274 | /* REVISIT: Clear interrupts here */ |
| 275 | |
| 276 | /* Restore interrupt registers */ |
| 277 | OMAP24XX_RESTORE(INTC_MIR0); |
| 278 | OMAP24XX_RESTORE(INTC_MIR1); |
| 279 | OMAP24XX_RESTORE(INTC_MIR2); |
| 280 | } |
| 281 | |
| 282 | static int omap2_pm_suspend(void) |
| 283 | { |
| 284 | int processor_type = 0; |
| 285 | |
| 286 | /* REVISIT: 0x21 or 0x26? */ |
| 287 | if (cpu_is_omap2420()) |
| 288 | processor_type = 0x21; |
| 289 | |
| 290 | if (!processor_type) |
| 291 | return -ENOTSUPP; |
| 292 | |
| 293 | local_irq_disable(); |
| 294 | local_fiq_disable(); |
| 295 | |
| 296 | omap2_pm_save_registers(); |
| 297 | |
| 298 | /* Disable interrupts except for the wake events */ |
| 299 | INTC_MIR_SET0 = 0xffffffff & ~INT0_WAKE_MASK; |
| 300 | INTC_MIR_SET1 = 0xffffffff & ~INT1_WAKE_MASK; |
| 301 | INTC_MIR_SET2 = 0xffffffff & ~INT2_WAKE_MASK; |
| 302 | |
| 303 | pmdomain_set_autoidle(); |
| 304 | |
| 305 | /* Clear old wake-up events */ |
| 306 | PM_WKST1_CORE = 0; |
| 307 | PM_WKST2_CORE = 0; |
| 308 | PM_WKST_WKUP = 0; |
| 309 | |
| 310 | /* Enable wake-up events */ |
| 311 | PM_WKEN1_CORE = (1 << 22) | (1 << 21); /* UART1 & 2 */ |
| 312 | PM_WKEN2_CORE = (1 << 2); /* UART3 */ |
| 313 | PM_WKEN_WKUP = (1 << 2) | (1 << 0); /* GPIO & GPT1 */ |
| 314 | |
| 315 | /* Disable clocks except for CM_ICLKEN2_CORE. It gets disabled |
| 316 | * in the SRAM suspend code */ |
| 317 | CM_FCLKEN1_CORE = 0; |
| 318 | CM_FCLKEN2_CORE = 0; |
| 319 | CM_ICLKEN1_CORE = 0; |
| 320 | CM_ICLKEN3_CORE = 0; |
| 321 | CM_ICLKEN4_CORE = 0; |
| 322 | |
| 323 | omap2_pm_debug("Status before suspend"); |
| 324 | |
| 325 | /* Must wait for serial buffers to clear */ |
| 326 | mdelay(200); |
| 327 | |
| 328 | /* Jump to SRAM suspend code |
| 329 | * REVISIT: When is this SDRC_DLLB_CTRL? |
| 330 | */ |
| 331 | omap2_sram_suspend(SDRC_DLLA_CTRL, processor_type); |
| 332 | |
| 333 | /* Back from sleep */ |
| 334 | omap2_pm_restore_registers(); |
| 335 | |
| 336 | local_fiq_enable(); |
| 337 | local_irq_enable(); |
| 338 | |
| 339 | return 0; |
| 340 | } |
| 341 | |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 342 | static int omap2_pm_enter(suspend_state_t state) |
| 343 | { |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 344 | int ret = 0; |
| 345 | |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 346 | switch (state) |
| 347 | { |
| 348 | case PM_SUSPEND_STANDBY: |
| 349 | case PM_SUSPEND_MEM: |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 350 | ret = omap2_pm_suspend(); |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 351 | break; |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 352 | default: |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 353 | ret = -EINVAL; |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 354 | } |
| 355 | |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 356 | return ret; |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 357 | } |
| 358 | |
| 359 | static int omap2_pm_finish(suspend_state_t state) |
| 360 | { |
| 361 | pm_idle = saved_idle; |
| 362 | return 0; |
| 363 | } |
| 364 | |
| 365 | static struct pm_ops omap_pm_ops = { |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 366 | .prepare = omap2_pm_prepare, |
| 367 | .enter = omap2_pm_enter, |
| 368 | .finish = omap2_pm_finish, |
Johannes Berg | e8c9c50 | 2007-04-30 15:09:54 -0700 | [diff] [blame] | 369 | .valid = pm_valid_only_mem, |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 370 | }; |
| 371 | |
| 372 | int __init omap2_pm_init(void) |
| 373 | { |
| 374 | printk("Power Management for TI OMAP.\n"); |
| 375 | |
| 376 | vclk = clk_get(NULL, "virt_prcm_set"); |
| 377 | if (IS_ERR(vclk)) { |
| 378 | printk(KERN_ERR "Could not get PM vclk\n"); |
| 379 | return -ENODEV; |
| 380 | } |
| 381 | |
| 382 | /* |
| 383 | * We copy the assembler sleep/wakeup routines to SRAM. |
| 384 | * These routines need to be in SRAM as that's the only |
| 385 | * memory the MPU can see when it wakes up. |
| 386 | */ |
| 387 | omap2_sram_idle = omap_sram_push(omap24xx_idle_loop_suspend, |
| 388 | omap24xx_idle_loop_suspend_sz); |
| 389 | |
| 390 | omap2_sram_suspend = omap_sram_push(omap24xx_cpu_suspend, |
| 391 | omap24xx_cpu_suspend_sz); |
| 392 | |
| 393 | pm_set_ops(&omap_pm_ops); |
| 394 | pm_idle = omap2_pm_idle; |
| 395 | |
Tony Lindgren | 22a16f3 | 2006-06-26 16:16:18 -0700 | [diff] [blame] | 396 | pmdomain_init(); |
| 397 | |
Tony Lindgren | 670c104 | 2006-04-02 17:46:25 +0100 | [diff] [blame] | 398 | return 0; |
| 399 | } |
| 400 | |
| 401 | __initcall(omap2_pm_init); |