2012-02-17 04:49:54 +04:00
/*
* Copyright ( c ) 2011 - 2012 Samsung Electronics Co . , Ltd .
2011-03-10 07:33:59 +03:00
* http : //www.samsung.com
*
2012-02-17 04:49:54 +04:00
* EXYNOS - Power Management support
2011-03-10 07:33:59 +03:00
*
* Based on arch / arm / mach - s3c2410 / pm . c
* Copyright ( c ) 2006 Simtec Electronics
* Ben Dooks < ben @ simtec . co . uk >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/init.h>
# include <linux/suspend.h>
2011-04-23 00:03:21 +04:00
# include <linux/syscore_ops.h>
2011-03-10 07:33:59 +03:00
# include <linux/io.h>
2011-07-18 14:25:13 +04:00
# include <linux/err.h>
# include <linux/clk.h>
2011-03-10 07:33:59 +03:00
# include <asm/cacheflush.h>
# include <asm/hardware/cache-l2x0.h>
2011-11-16 20:19:11 +04:00
# include <asm/smp_scu.h>
2011-03-10 07:33:59 +03:00
# include <plat/cpu.h>
# include <plat/pm.h>
2011-07-18 14:25:13 +04:00
# include <plat/pll.h>
2011-07-21 06:25:23 +04:00
# include <plat/regs-srom.h>
2011-03-10 07:33:59 +03:00
# include <mach/regs-irq.h>
# include <mach/regs-gpio.h>
# include <mach/regs-clock.h>
# include <mach/regs-pmu.h>
# include <mach/pm-core.h>
2011-07-18 14:21:27 +04:00
# include <mach/pmu.h>
2011-03-10 07:33:59 +03:00
static struct sleep_save exynos4_set_clksrc [ ] = {
2012-03-10 02:19:10 +04:00
{ . reg = EXYNOS4_CLKSRC_MASK_TOP , . val = 0x00000001 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_CAM , . val = 0x11111111 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_TV , . val = 0x00000111 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_LCD0 , . val = 0x00001111 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_MAUDIO , . val = 0x00000001 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_FSYS , . val = 0x01011111 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_PERIL0 , . val = 0x01111111 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_PERIL1 , . val = 0x01110111 , } ,
{ . reg = EXYNOS4_CLKSRC_MASK_DMC , . val = 0x00010000 , } ,
2011-03-10 07:33:59 +03:00
} ;
2011-08-24 16:52:45 +04:00
static struct sleep_save exynos4210_set_clksrc [ ] = {
2012-03-10 02:19:10 +04:00
{ . reg = EXYNOS4210_CLKSRC_MASK_LCD1 , . val = 0x00001111 , } ,
2011-08-24 16:52:45 +04:00
} ;
2011-07-18 14:25:13 +04:00
static struct sleep_save exynos4_epll_save [ ] = {
2012-03-10 02:19:10 +04:00
SAVE_ITEM ( EXYNOS4_EPLL_CON0 ) ,
SAVE_ITEM ( EXYNOS4_EPLL_CON1 ) ,
2011-07-18 14:25:13 +04:00
} ;
static struct sleep_save exynos4_vpll_save [ ] = {
2012-03-10 02:19:10 +04:00
SAVE_ITEM ( EXYNOS4_VPLL_CON0 ) ,
SAVE_ITEM ( EXYNOS4_VPLL_CON1 ) ,
2011-07-18 14:25:13 +04:00
} ;
2012-11-20 13:20:45 +04:00
static struct sleep_save exynos5_sys_save [ ] = {
SAVE_ITEM ( EXYNOS5_SYS_I2C_CFG ) ,
} ;
2012-02-17 04:49:54 +04:00
static struct sleep_save exynos_core_save [ ] = {
2011-07-21 06:25:23 +04:00
/* SROM side */
SAVE_ITEM ( S5P_SROM_BW ) ,
SAVE_ITEM ( S5P_SROM_BC0 ) ,
SAVE_ITEM ( S5P_SROM_BC1 ) ,
SAVE_ITEM ( S5P_SROM_BC2 ) ,
SAVE_ITEM ( S5P_SROM_BC3 ) ,
2011-03-10 07:33:59 +03:00
} ;
2011-07-18 14:25:03 +04:00
/* For Cortex-A9 Diagnostic and Power control register */
static unsigned int save_arm_register [ 2 ] ;
2012-02-17 04:49:54 +04:00
static int exynos_cpu_suspend ( unsigned long arg )
2011-03-10 07:33:59 +03:00
{
2012-02-17 07:23:51 +04:00
# ifdef CONFIG_CACHE_L2X0
2011-03-10 07:33:59 +03:00
outer_flush_all ( ) ;
2012-02-17 07:23:51 +04:00
# endif
2011-03-10 07:33:59 +03:00
2012-11-22 09:46:40 +04:00
if ( soc_is_exynos5250 ( ) )
flush_cache_all ( ) ;
2011-03-10 07:33:59 +03:00
/* issue the standby signal into the pm unit. */
cpu_do_idle ( ) ;
/* we should never get past here */
panic ( " sleep resumed to originator? " ) ;
}
2012-02-17 04:49:54 +04:00
static void exynos_pm_prepare ( void )
2011-03-10 07:33:59 +03:00
{
2012-02-17 07:23:51 +04:00
unsigned int tmp ;
2011-03-10 07:33:59 +03:00
2012-02-17 04:49:54 +04:00
s3c_pm_do_save ( exynos_core_save , ARRAY_SIZE ( exynos_core_save ) ) ;
2011-03-10 07:33:59 +03:00
2012-02-17 07:23:51 +04:00
if ( ! soc_is_exynos5250 ( ) ) {
s3c_pm_do_save ( exynos4_epll_save , ARRAY_SIZE ( exynos4_epll_save ) ) ;
s3c_pm_do_save ( exynos4_vpll_save , ARRAY_SIZE ( exynos4_vpll_save ) ) ;
} else {
2012-11-20 13:20:45 +04:00
s3c_pm_do_save ( exynos5_sys_save , ARRAY_SIZE ( exynos5_sys_save ) ) ;
2012-02-17 07:23:51 +04:00
/* Disable USE_RETENTION of JPEG_MEM_OPTION */
tmp = __raw_readl ( EXYNOS5_JPEG_MEM_OPTION ) ;
tmp & = ~ EXYNOS5_OPTION_USE_RETENTION ;
__raw_writel ( tmp , EXYNOS5_JPEG_MEM_OPTION ) ;
}
2011-03-10 07:33:59 +03:00
/* Set value of power down register for sleep mode */
2012-02-17 04:51:31 +04:00
exynos_sys_powerdown_conf ( SYS_SLEEP ) ;
2011-03-10 07:33:59 +03:00
__raw_writel ( S5P_CHECK_SLEEP , S5P_INFORM1 ) ;
/* ensure at least INFORM0 has the resume address */
__raw_writel ( virt_to_phys ( s3c_cpu_resume ) , S5P_INFORM0 ) ;
/* Before enter central sequence mode, clock src register have to set */
2012-02-17 07:23:51 +04:00
if ( ! soc_is_exynos5250 ( ) )
s3c_pm_do_restore_core ( exynos4_set_clksrc , ARRAY_SIZE ( exynos4_set_clksrc ) ) ;
2011-03-10 07:33:59 +03:00
2011-08-24 16:52:45 +04:00
if ( soc_is_exynos4210 ( ) )
s3c_pm_do_restore_core ( exynos4210_set_clksrc , ARRAY_SIZE ( exynos4210_set_clksrc ) ) ;
2011-03-10 07:33:59 +03:00
}
2012-02-17 04:49:54 +04:00
static int exynos_pm_add ( struct device * dev , struct subsys_interface * sif )
2011-03-10 07:33:59 +03:00
{
2012-02-17 04:49:54 +04:00
pm_cpu_prep = exynos_pm_prepare ;
pm_cpu_sleep = exynos_cpu_suspend ;
2011-03-10 07:33:59 +03:00
return 0 ;
}
2011-07-18 14:25:13 +04:00
static unsigned long pll_base_rate ;
static void exynos4_restore_pll ( void )
{
unsigned long pll_con , locktime , lockcnt ;
unsigned long pll_in_rate ;
unsigned int p_div , epll_wait = 0 , vpll_wait = 0 ;
if ( pll_base_rate = = 0 )
return ;
pll_in_rate = pll_base_rate ;
/* EPLL */
pll_con = exynos4_epll_save [ 0 ] . val ;
if ( pll_con & ( 1 < < 31 ) ) {
pll_con & = ( PLL46XX_PDIV_MASK < < PLL46XX_PDIV_SHIFT ) ;
p_div = ( pll_con > > PLL46XX_PDIV_SHIFT ) ;
pll_in_rate / = 1000000 ;
locktime = ( 3000 / pll_in_rate ) * p_div ;
lockcnt = locktime * 10000 / ( 10000 / pll_in_rate ) ;
2012-03-10 02:19:10 +04:00
__raw_writel ( lockcnt , EXYNOS4_EPLL_LOCK ) ;
2011-07-18 14:25:13 +04:00
s3c_pm_do_restore_core ( exynos4_epll_save ,
ARRAY_SIZE ( exynos4_epll_save ) ) ;
epll_wait = 1 ;
}
pll_in_rate = pll_base_rate ;
/* VPLL */
pll_con = exynos4_vpll_save [ 0 ] . val ;
if ( pll_con & ( 1 < < 31 ) ) {
pll_in_rate / = 1000000 ;
/* 750us */
locktime = 750 ;
lockcnt = locktime * 10000 / ( 10000 / pll_in_rate ) ;
2012-03-10 02:19:10 +04:00
__raw_writel ( lockcnt , EXYNOS4_VPLL_LOCK ) ;
2011-07-18 14:25:13 +04:00
s3c_pm_do_restore_core ( exynos4_vpll_save ,
ARRAY_SIZE ( exynos4_vpll_save ) ) ;
vpll_wait = 1 ;
}
/* Wait PLL locking */
do {
if ( epll_wait ) {
2012-03-10 02:19:10 +04:00
pll_con = __raw_readl ( EXYNOS4_EPLL_CON0 ) ;
if ( pll_con & ( 1 < < EXYNOS4_EPLLCON0_LOCKED_SHIFT ) )
2011-07-18 14:25:13 +04:00
epll_wait = 0 ;
}
if ( vpll_wait ) {
2012-03-10 02:19:10 +04:00
pll_con = __raw_readl ( EXYNOS4_VPLL_CON0 ) ;
if ( pll_con & ( 1 < < EXYNOS4_VPLLCON0_LOCKED_SHIFT ) )
2011-07-18 14:25:13 +04:00
vpll_wait = 0 ;
}
} while ( epll_wait | | vpll_wait ) ;
}
2012-02-17 04:49:54 +04:00
static struct subsys_interface exynos_pm_interface = {
2012-02-17 07:23:51 +04:00
. name = " exynos_pm " ,
2012-05-15 10:47:40 +04:00
. subsys = & exynos_subsys ,
2012-02-17 04:49:54 +04:00
. add_dev = exynos_pm_add ,
2011-04-23 00:03:21 +04:00
} ;
2012-02-17 04:49:54 +04:00
static __init int exynos_pm_drvinit ( void )
2011-04-23 00:03:21 +04:00
{
2011-07-18 14:25:13 +04:00
struct clk * pll_base ;
2011-04-23 00:03:21 +04:00
unsigned int tmp ;
s3c_pm_init ( ) ;
/* All wakeup disable */
tmp = __raw_readl ( S5P_WAKEUP_MASK ) ;
tmp | = ( ( 0xFF < < 8 ) | ( 0x1F < < 1 ) ) ;
__raw_writel ( tmp , S5P_WAKEUP_MASK ) ;
2012-02-17 04:49:54 +04:00
if ( ! soc_is_exynos5250 ( ) ) {
pll_base = clk_get ( NULL , " xtal " ) ;
2011-07-18 14:25:13 +04:00
2012-02-17 04:49:54 +04:00
if ( ! IS_ERR ( pll_base ) ) {
pll_base_rate = clk_get_rate ( pll_base ) ;
clk_put ( pll_base ) ;
}
2011-07-18 14:25:13 +04:00
}
2012-02-17 04:49:54 +04:00
return subsys_interface_register ( & exynos_pm_interface ) ;
2011-04-23 00:03:21 +04:00
}
2012-02-17 04:49:54 +04:00
arch_initcall ( exynos_pm_drvinit ) ;
2011-04-23 00:03:21 +04:00
2012-02-17 04:49:54 +04:00
static int exynos_pm_suspend ( void )
2011-07-18 14:21:41 +04:00
{
unsigned long tmp ;
/* Setting Central Sequence Register for power down mode */
tmp = __raw_readl ( S5P_CENTRAL_SEQ_CONFIGURATION ) ;
tmp & = ~ S5P_CENTRAL_LOWPWR_CFG ;
__raw_writel ( tmp , S5P_CENTRAL_SEQ_CONFIGURATION ) ;
2012-02-17 07:23:51 +04:00
/* Setting SEQ_OPTION register */
2011-09-27 02:26:04 +04:00
2012-02-17 07:23:51 +04:00
tmp = ( S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0 ) ;
__raw_writel ( tmp , S5P_CENTRAL_SEQ_OPTION ) ;
2011-07-18 14:25:03 +04:00
2012-02-17 07:23:51 +04:00
if ( ! soc_is_exynos5250 ( ) ) {
/* Save Power control register */
asm ( " mrc p15, 0, %0, c15, c0, 0 "
: " =r " ( tmp ) : : " cc " ) ;
save_arm_register [ 0 ] = tmp ;
/* Save Diagnostic register */
asm ( " mrc p15, 0, %0, c15, c0, 1 "
: " =r " ( tmp ) : : " cc " ) ;
save_arm_register [ 1 ] = tmp ;
}
2011-07-18 14:25:03 +04:00
2011-07-18 14:21:41 +04:00
return 0 ;
}
2012-02-17 04:49:54 +04:00
static void exynos_pm_resume ( void )
2011-03-10 07:33:59 +03:00
{
2011-07-18 14:21:34 +04:00
unsigned long tmp ;
/*
* If PMU failed while entering sleep mode , WFI will be
* ignored by PMU and then exiting cpu_do_idle ( ) .
* S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically
* in this situation .
*/
tmp = __raw_readl ( S5P_CENTRAL_SEQ_CONFIGURATION ) ;
if ( ! ( tmp & S5P_CENTRAL_LOWPWR_CFG ) ) {
tmp | = S5P_CENTRAL_LOWPWR_CFG ;
__raw_writel ( tmp , S5P_CENTRAL_SEQ_CONFIGURATION ) ;
/* No need to perform below restore code */
goto early_wakeup ;
}
2012-02-17 07:23:51 +04:00
if ( ! soc_is_exynos5250 ( ) ) {
/* Restore Power control register */
tmp = save_arm_register [ 0 ] ;
asm volatile ( " mcr p15, 0, %0, c15, c0, 0 "
: : " r " ( tmp )
: " cc " ) ;
/* Restore Diagnostic register */
tmp = save_arm_register [ 1 ] ;
asm volatile ( " mcr p15, 0, %0, c15, c0, 1 "
: : " r " ( tmp )
: " cc " ) ;
}
2011-07-18 14:21:34 +04:00
2011-03-10 07:33:59 +03:00
/* For release retention */
__raw_writel ( ( 1 < < 28 ) , S5P_PAD_RET_MAUDIO_OPTION ) ;
__raw_writel ( ( 1 < < 28 ) , S5P_PAD_RET_GPIO_OPTION ) ;
__raw_writel ( ( 1 < < 28 ) , S5P_PAD_RET_UART_OPTION ) ;
__raw_writel ( ( 1 < < 28 ) , S5P_PAD_RET_MMCA_OPTION ) ;
__raw_writel ( ( 1 < < 28 ) , S5P_PAD_RET_MMCB_OPTION ) ;
__raw_writel ( ( 1 < < 28 ) , S5P_PAD_RET_EBIA_OPTION ) ;
__raw_writel ( ( 1 < < 28 ) , S5P_PAD_RET_EBIB_OPTION ) ;
2012-11-20 13:20:45 +04:00
if ( soc_is_exynos5250 ( ) )
s3c_pm_do_restore ( exynos5_sys_save ,
ARRAY_SIZE ( exynos5_sys_save ) ) ;
2012-02-17 04:49:54 +04:00
s3c_pm_do_restore_core ( exynos_core_save , ARRAY_SIZE ( exynos_core_save ) ) ;
2011-03-10 07:33:59 +03:00
2012-02-17 07:23:51 +04:00
if ( ! soc_is_exynos5250 ( ) ) {
exynos4_restore_pll ( ) ;
2011-07-18 14:25:13 +04:00
2012-01-27 09:47:45 +04:00
# ifdef CONFIG_SMP
2012-02-17 07:23:51 +04:00
scu_enable ( S5P_VA_SCU ) ;
2012-01-27 09:47:45 +04:00
# endif
2012-02-17 07:23:51 +04:00
}
2011-03-10 07:33:59 +03:00
2011-07-18 14:21:34 +04:00
early_wakeup :
2012-11-22 09:46:27 +04:00
/* Clear SLEEP mode set in INFORM1 */
__raw_writel ( 0x0 , S5P_INFORM1 ) ;
2011-07-18 14:21:34 +04:00
return ;
2011-03-10 07:33:59 +03:00
}
2012-02-17 04:49:54 +04:00
static struct syscore_ops exynos_pm_syscore_ops = {
. suspend = exynos_pm_suspend ,
. resume = exynos_pm_resume ,
2011-03-10 07:33:59 +03:00
} ;
2012-02-17 07:23:51 +04:00
static __init int exynos_pm_syscore_init ( void )
2011-03-10 07:33:59 +03:00
{
2012-02-17 04:49:54 +04:00
register_syscore_ops ( & exynos_pm_syscore_ops ) ;
2011-04-23 00:03:21 +04:00
return 0 ;
2011-03-10 07:33:59 +03:00
}
2012-02-17 07:23:51 +04:00
arch_initcall ( exynos_pm_syscore_init ) ;