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