162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * arch/arm/mach-lpc32xx/pm.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Original authors: Vitaly Wool, Dmitry Chigirev <source@mvista.com> 662306a36Sopenharmony_ci * Modified by Kevin Wells <kevin.wells@nxp.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * 2005 (c) MontaVista Software, Inc. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/* 1262306a36Sopenharmony_ci * LPC32XX CPU and system power management 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * The LPC32XX has three CPU modes for controlling system power: run, 1562306a36Sopenharmony_ci * direct-run, and halt modes. When switching between halt and run modes, 1662306a36Sopenharmony_ci * the CPU transistions through direct-run mode. For Linux, direct-run 1762306a36Sopenharmony_ci * mode is not used in normal operation. Halt mode is used when the 1862306a36Sopenharmony_ci * system is fully suspended. 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * Run mode: 2162306a36Sopenharmony_ci * The ARM CPU clock (HCLK_PLL), HCLK bus clock, and PCLK bus clocks are 2262306a36Sopenharmony_ci * derived from the HCLK PLL. The HCLK and PCLK bus rates are divided from 2362306a36Sopenharmony_ci * the HCLK_PLL rate. Linux runs in this mode. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * Direct-run mode: 2662306a36Sopenharmony_ci * The ARM CPU clock, HCLK bus clock, and PCLK bus clocks are driven from 2762306a36Sopenharmony_ci * SYSCLK. SYSCLK is usually around 13MHz, but may vary based on SYSCLK 2862306a36Sopenharmony_ci * source or the frequency of the main oscillator. In this mode, the 2962306a36Sopenharmony_ci * HCLK_PLL can be safely enabled, changed, or disabled. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * Halt mode: 3262306a36Sopenharmony_ci * SYSCLK is gated off and the CPU and system clocks are halted. 3362306a36Sopenharmony_ci * Peripherals based on the 32KHz oscillator clock (ie, RTC, touch, 3462306a36Sopenharmony_ci * key scanner, etc.) still operate if enabled. In this state, an enabled 3562306a36Sopenharmony_ci * system event (ie, GPIO state change, RTC match, key press, etc.) will 3662306a36Sopenharmony_ci * wake the system up back into direct-run mode. 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * DRAM refresh 3962306a36Sopenharmony_ci * DRAM clocking and refresh are slightly different for systems with DDR 4062306a36Sopenharmony_ci * DRAM or regular SDRAM devices. If SDRAM is used in the system, the 4162306a36Sopenharmony_ci * SDRAM will still be accessible in direct-run mode. In DDR based systems, 4262306a36Sopenharmony_ci * a transition to direct-run mode will stop all DDR accesses (no clocks). 4362306a36Sopenharmony_ci * Because of this, the code to switch power modes and the code to enter 4462306a36Sopenharmony_ci * and exit DRAM self-refresh modes must not be executed in DRAM. A small 4562306a36Sopenharmony_ci * section of IRAM is used instead for this. 4662306a36Sopenharmony_ci * 4762306a36Sopenharmony_ci * Suspend is handled with the following logic: 4862306a36Sopenharmony_ci * Backup a small area of IRAM used for the suspend code 4962306a36Sopenharmony_ci * Copy suspend code to IRAM 5062306a36Sopenharmony_ci * Transfer control to code in IRAM 5162306a36Sopenharmony_ci * Places DRAMs in self-refresh mode 5262306a36Sopenharmony_ci * Enter direct-run mode 5362306a36Sopenharmony_ci * Save state of HCLK_PLL PLL 5462306a36Sopenharmony_ci * Disable HCLK_PLL PLL 5562306a36Sopenharmony_ci * Enter halt mode - CPU and buses will stop 5662306a36Sopenharmony_ci * System enters direct-run mode when an enabled event occurs 5762306a36Sopenharmony_ci * HCLK PLL state is restored 5862306a36Sopenharmony_ci * Run mode is entered 5962306a36Sopenharmony_ci * DRAMS are placed back into normal mode 6062306a36Sopenharmony_ci * Code execution returns from IRAM 6162306a36Sopenharmony_ci * IRAM code are used for suspend is restored 6262306a36Sopenharmony_ci * Suspend mode is exited 6362306a36Sopenharmony_ci */ 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci#include <linux/suspend.h> 6662306a36Sopenharmony_ci#include <linux/io.h> 6762306a36Sopenharmony_ci#include <linux/slab.h> 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci#include <asm/cacheflush.h> 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci#include "lpc32xx.h" 7262306a36Sopenharmony_ci#include "common.h" 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci#define TEMP_IRAM_AREA IO_ADDRESS(LPC32XX_IRAM_BASE) 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* 7762306a36Sopenharmony_ci * Both STANDBY and MEM suspend states are handled the same with no 7862306a36Sopenharmony_ci * loss of CPU or memory state 7962306a36Sopenharmony_ci */ 8062306a36Sopenharmony_cistatic int lpc32xx_pm_enter(suspend_state_t state) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci int (*lpc32xx_suspend_ptr) (void); 8362306a36Sopenharmony_ci void *iram_swap_area; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* Allocate some space for temporary IRAM storage */ 8662306a36Sopenharmony_ci iram_swap_area = kmemdup((void *)TEMP_IRAM_AREA, 8762306a36Sopenharmony_ci lpc32xx_sys_suspend_sz, GFP_KERNEL); 8862306a36Sopenharmony_ci if (!iram_swap_area) 8962306a36Sopenharmony_ci return -ENOMEM; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci /* 9262306a36Sopenharmony_ci * Copy code to suspend system into IRAM. The suspend code 9362306a36Sopenharmony_ci * needs to run from IRAM as DRAM may no longer be available 9462306a36Sopenharmony_ci * when the PLL is stopped. 9562306a36Sopenharmony_ci */ 9662306a36Sopenharmony_ci memcpy((void *) TEMP_IRAM_AREA, &lpc32xx_sys_suspend, 9762306a36Sopenharmony_ci lpc32xx_sys_suspend_sz); 9862306a36Sopenharmony_ci flush_icache_range((unsigned long)TEMP_IRAM_AREA, 9962306a36Sopenharmony_ci (unsigned long)(TEMP_IRAM_AREA) + lpc32xx_sys_suspend_sz); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci /* Transfer to suspend code in IRAM */ 10262306a36Sopenharmony_ci lpc32xx_suspend_ptr = (void *) TEMP_IRAM_AREA; 10362306a36Sopenharmony_ci flush_cache_all(); 10462306a36Sopenharmony_ci (void) lpc32xx_suspend_ptr(); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* Restore original IRAM contents */ 10762306a36Sopenharmony_ci memcpy((void *) TEMP_IRAM_AREA, iram_swap_area, 10862306a36Sopenharmony_ci lpc32xx_sys_suspend_sz); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci kfree(iram_swap_area); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci return 0; 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_cistatic const struct platform_suspend_ops lpc32xx_pm_ops = { 11662306a36Sopenharmony_ci .valid = suspend_valid_only_mem, 11762306a36Sopenharmony_ci .enter = lpc32xx_pm_enter, 11862306a36Sopenharmony_ci}; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci#define EMC_DYN_MEM_CTRL_OFS 0x20 12162306a36Sopenharmony_ci#define EMC_SRMMC (1 << 3) 12262306a36Sopenharmony_ci#define EMC_CTRL_REG io_p2v(LPC32XX_EMC_BASE + EMC_DYN_MEM_CTRL_OFS) 12362306a36Sopenharmony_cistatic int __init lpc32xx_pm_init(void) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci /* 12662306a36Sopenharmony_ci * Setup SDRAM self-refresh clock to automatically disable o 12762306a36Sopenharmony_ci * start of self-refresh. This only needs to be done once. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_ci __raw_writel(__raw_readl(EMC_CTRL_REG) | EMC_SRMMC, EMC_CTRL_REG); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci suspend_set_ops(&lpc32xx_pm_ops); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ciarch_initcall(lpc32xx_pm_init); 136