18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * arch/arm/mach-lpc32xx/pm.c 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Original authors: Vitaly Wool, Dmitry Chigirev <source@mvista.com> 58c2ecf20Sopenharmony_ci * Modified by Kevin Wells <kevin.wells@nxp.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * 2005 (c) MontaVista Software, Inc. This file is licensed under 88c2ecf20Sopenharmony_ci * the terms of the GNU General Public License version 2. This program 98c2ecf20Sopenharmony_ci * is licensed "as is" without any warranty of any kind, whether express 108c2ecf20Sopenharmony_ci * or implied. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci/* 148c2ecf20Sopenharmony_ci * LPC32XX CPU and system power management 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * The LPC32XX has three CPU modes for controlling system power: run, 178c2ecf20Sopenharmony_ci * direct-run, and halt modes. When switching between halt and run modes, 188c2ecf20Sopenharmony_ci * the CPU transistions through direct-run mode. For Linux, direct-run 198c2ecf20Sopenharmony_ci * mode is not used in normal operation. Halt mode is used when the 208c2ecf20Sopenharmony_ci * system is fully suspended. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * Run mode: 238c2ecf20Sopenharmony_ci * The ARM CPU clock (HCLK_PLL), HCLK bus clock, and PCLK bus clocks are 248c2ecf20Sopenharmony_ci * derived from the HCLK PLL. The HCLK and PCLK bus rates are divided from 258c2ecf20Sopenharmony_ci * the HCLK_PLL rate. Linux runs in this mode. 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * Direct-run mode: 288c2ecf20Sopenharmony_ci * The ARM CPU clock, HCLK bus clock, and PCLK bus clocks are driven from 298c2ecf20Sopenharmony_ci * SYSCLK. SYSCLK is usually around 13MHz, but may vary based on SYSCLK 308c2ecf20Sopenharmony_ci * source or the frequency of the main oscillator. In this mode, the 318c2ecf20Sopenharmony_ci * HCLK_PLL can be safely enabled, changed, or disabled. 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * Halt mode: 348c2ecf20Sopenharmony_ci * SYSCLK is gated off and the CPU and system clocks are halted. 358c2ecf20Sopenharmony_ci * Peripherals based on the 32KHz oscillator clock (ie, RTC, touch, 368c2ecf20Sopenharmony_ci * key scanner, etc.) still operate if enabled. In this state, an enabled 378c2ecf20Sopenharmony_ci * system event (ie, GPIO state change, RTC match, key press, etc.) will 388c2ecf20Sopenharmony_ci * wake the system up back into direct-run mode. 398c2ecf20Sopenharmony_ci * 408c2ecf20Sopenharmony_ci * DRAM refresh 418c2ecf20Sopenharmony_ci * DRAM clocking and refresh are slightly different for systems with DDR 428c2ecf20Sopenharmony_ci * DRAM or regular SDRAM devices. If SDRAM is used in the system, the 438c2ecf20Sopenharmony_ci * SDRAM will still be accessible in direct-run mode. In DDR based systems, 448c2ecf20Sopenharmony_ci * a transition to direct-run mode will stop all DDR accesses (no clocks). 458c2ecf20Sopenharmony_ci * Because of this, the code to switch power modes and the code to enter 468c2ecf20Sopenharmony_ci * and exit DRAM self-refresh modes must not be executed in DRAM. A small 478c2ecf20Sopenharmony_ci * section of IRAM is used instead for this. 488c2ecf20Sopenharmony_ci * 498c2ecf20Sopenharmony_ci * Suspend is handled with the following logic: 508c2ecf20Sopenharmony_ci * Backup a small area of IRAM used for the suspend code 518c2ecf20Sopenharmony_ci * Copy suspend code to IRAM 528c2ecf20Sopenharmony_ci * Transfer control to code in IRAM 538c2ecf20Sopenharmony_ci * Places DRAMs in self-refresh mode 548c2ecf20Sopenharmony_ci * Enter direct-run mode 558c2ecf20Sopenharmony_ci * Save state of HCLK_PLL PLL 568c2ecf20Sopenharmony_ci * Disable HCLK_PLL PLL 578c2ecf20Sopenharmony_ci * Enter halt mode - CPU and buses will stop 588c2ecf20Sopenharmony_ci * System enters direct-run mode when an enabled event occurs 598c2ecf20Sopenharmony_ci * HCLK PLL state is restored 608c2ecf20Sopenharmony_ci * Run mode is entered 618c2ecf20Sopenharmony_ci * DRAMS are placed back into normal mode 628c2ecf20Sopenharmony_ci * Code execution returns from IRAM 638c2ecf20Sopenharmony_ci * IRAM code are used for suspend is restored 648c2ecf20Sopenharmony_ci * Suspend mode is exited 658c2ecf20Sopenharmony_ci */ 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#include <linux/suspend.h> 688c2ecf20Sopenharmony_ci#include <linux/io.h> 698c2ecf20Sopenharmony_ci#include <linux/slab.h> 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci#include <asm/cacheflush.h> 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci#include "lpc32xx.h" 748c2ecf20Sopenharmony_ci#include "common.h" 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci#define TEMP_IRAM_AREA IO_ADDRESS(LPC32XX_IRAM_BASE) 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci/* 798c2ecf20Sopenharmony_ci * Both STANDBY and MEM suspend states are handled the same with no 808c2ecf20Sopenharmony_ci * loss of CPU or memory state 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_cistatic int lpc32xx_pm_enter(suspend_state_t state) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci int (*lpc32xx_suspend_ptr) (void); 858c2ecf20Sopenharmony_ci void *iram_swap_area; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* Allocate some space for temporary IRAM storage */ 888c2ecf20Sopenharmony_ci iram_swap_area = kmemdup((void *)TEMP_IRAM_AREA, 898c2ecf20Sopenharmony_ci lpc32xx_sys_suspend_sz, GFP_KERNEL); 908c2ecf20Sopenharmony_ci if (!iram_swap_area) 918c2ecf20Sopenharmony_ci return -ENOMEM; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci /* 948c2ecf20Sopenharmony_ci * Copy code to suspend system into IRAM. The suspend code 958c2ecf20Sopenharmony_ci * needs to run from IRAM as DRAM may no longer be available 968c2ecf20Sopenharmony_ci * when the PLL is stopped. 978c2ecf20Sopenharmony_ci */ 988c2ecf20Sopenharmony_ci memcpy((void *) TEMP_IRAM_AREA, &lpc32xx_sys_suspend, 998c2ecf20Sopenharmony_ci lpc32xx_sys_suspend_sz); 1008c2ecf20Sopenharmony_ci flush_icache_range((unsigned long)TEMP_IRAM_AREA, 1018c2ecf20Sopenharmony_ci (unsigned long)(TEMP_IRAM_AREA) + lpc32xx_sys_suspend_sz); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci /* Transfer to suspend code in IRAM */ 1048c2ecf20Sopenharmony_ci lpc32xx_suspend_ptr = (void *) TEMP_IRAM_AREA; 1058c2ecf20Sopenharmony_ci flush_cache_all(); 1068c2ecf20Sopenharmony_ci (void) lpc32xx_suspend_ptr(); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci /* Restore original IRAM contents */ 1098c2ecf20Sopenharmony_ci memcpy((void *) TEMP_IRAM_AREA, iram_swap_area, 1108c2ecf20Sopenharmony_ci lpc32xx_sys_suspend_sz); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci kfree(iram_swap_area); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return 0; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic const struct platform_suspend_ops lpc32xx_pm_ops = { 1188c2ecf20Sopenharmony_ci .valid = suspend_valid_only_mem, 1198c2ecf20Sopenharmony_ci .enter = lpc32xx_pm_enter, 1208c2ecf20Sopenharmony_ci}; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci#define EMC_DYN_MEM_CTRL_OFS 0x20 1238c2ecf20Sopenharmony_ci#define EMC_SRMMC (1 << 3) 1248c2ecf20Sopenharmony_ci#define EMC_CTRL_REG io_p2v(LPC32XX_EMC_BASE + EMC_DYN_MEM_CTRL_OFS) 1258c2ecf20Sopenharmony_cistatic int __init lpc32xx_pm_init(void) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci /* 1288c2ecf20Sopenharmony_ci * Setup SDRAM self-refresh clock to automatically disable o 1298c2ecf20Sopenharmony_ci * start of self-refresh. This only needs to be done once. 1308c2ecf20Sopenharmony_ci */ 1318c2ecf20Sopenharmony_ci __raw_writel(__raw_readl(EMC_CTRL_REG) | EMC_SRMMC, EMC_CTRL_REG); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci suspend_set_ops(&lpc32xx_pm_ops); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return 0; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ciarch_initcall(lpc32xx_pm_init); 138