2014-05-09 01:43:26 +04:00
/* linux/arch/arm/mach-exynos/cpuidle.c
2011-03-16 01:28:23 +03:00
*
* Copyright ( c ) 2011 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com
*
* 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/kernel.h>
# include <linux/init.h>
# include <linux/cpuidle.h>
2012-03-08 14:07:27 +04:00
# include <linux/cpu_pm.h>
2011-03-16 01:28:23 +03:00
# include <linux/io.h>
2011-11-08 14:57:59 +04:00
# include <linux/export.h>
2014-03-19 21:29:36 +04:00
# include <linux/module.h>
2011-11-08 14:57:59 +04:00
# include <linux/time.h>
2013-08-30 14:15:04 +04:00
# include <linux/platform_device.h>
2011-03-16 01:28:23 +03:00
# include <asm/proc-fns.h>
2012-03-08 14:07:27 +04:00
# include <asm/smp_scu.h>
# include <asm/suspend.h>
# include <asm/unified.h>
2012-05-12 11:29:21 +04:00
# include <asm/cpuidle.h>
2012-03-08 14:07:27 +04:00
# include <plat/cpu.h>
2013-07-24 09:06:13 +04:00
# include <plat/pm.h>
2012-03-08 14:07:27 +04:00
2013-12-18 23:22:09 +04:00
# include <mach/map.h>
2012-12-31 22:06:48 +04:00
# include "common.h"
2013-12-18 23:06:56 +04:00
# include "regs-pmu.h"
2012-12-31 22:06:48 +04:00
2012-03-08 14:07:27 +04:00
# define REG_DIRECTGO_ADDR (samsung_rev() == EXYNOS4210_REV_1_1 ? \
S5P_INFORM7 : ( samsung_rev ( ) = = EXYNOS4210_REV_1_0 ? \
( S5P_VA_SYSRAM + 0x24 ) : S5P_INFORM0 ) )
# define REG_DIRECTGO_FLAG (samsung_rev() == EXYNOS4210_REV_1_1 ? \
S5P_INFORM6 : ( samsung_rev ( ) = = EXYNOS4210_REV_1_0 ? \
( S5P_VA_SYSRAM + 0x20 ) : S5P_INFORM1 ) )
# define S5P_CHECK_AFTR 0xFCBA0D10
2011-03-16 01:28:23 +03:00
2012-03-08 14:07:27 +04:00
/* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */
2014-05-09 01:43:26 +04:00
static void exynos_set_wakeupmask ( void )
2012-03-08 14:07:27 +04:00
{
__raw_writel ( 0x0000ff3e , S5P_WAKEUP_MASK ) ;
}
static unsigned int g_pwr_ctrl , g_diag_reg ;
static void save_cpu_arch_register ( void )
{
/*read power control register*/
asm ( " mrc p15, 0, %0, c15, c0, 0 " : " =r " ( g_pwr_ctrl ) : : " cc " ) ;
/*read diagnostic register*/
asm ( " mrc p15, 0, %0, c15, c0, 1 " : " =r " ( g_diag_reg ) : : " cc " ) ;
return ;
}
static void restore_cpu_arch_register ( void )
{
/*write power control register*/
asm ( " mcr p15, 0, %0, c15, c0, 0 " : : " r " ( g_pwr_ctrl ) : " cc " ) ;
/*write diagnostic register*/
asm ( " mcr p15, 0, %0, c15, c0, 1 " : : " r " ( g_diag_reg ) : " cc " ) ;
return ;
}
static int idle_finisher ( unsigned long flags )
{
cpu_do_idle ( ) ;
return 1 ;
}
2014-05-09 01:43:26 +04:00
static int exynos_enter_core0_aftr ( struct cpuidle_device * dev ,
2012-03-08 14:07:27 +04:00
struct cpuidle_driver * drv ,
int index )
{
unsigned long tmp ;
2014-05-09 01:43:26 +04:00
exynos_set_wakeupmask ( ) ;
2012-03-08 14:07:27 +04:00
/* Set value of power down register for aftr mode */
2012-02-17 04:51:31 +04:00
exynos_sys_powerdown_conf ( SYS_AFTR ) ;
2012-03-08 14:07:27 +04:00
2014-03-20 21:59:30 +04:00
__raw_writel ( virt_to_phys ( exynos_cpu_resume ) , REG_DIRECTGO_ADDR ) ;
2012-03-08 14:07:27 +04:00
__raw_writel ( S5P_CHECK_AFTR , REG_DIRECTGO_FLAG ) ;
save_cpu_arch_register ( ) ;
/* 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 ) ;
cpu_pm_enter ( ) ;
cpu_suspend ( 0 , idle_finisher ) ;
# ifdef CONFIG_SMP
2012-11-22 09:46:34 +04:00
if ( ! soc_is_exynos5250 ( ) )
scu_enable ( S5P_VA_SCU ) ;
2012-03-08 14:07:27 +04:00
# endif
cpu_pm_exit ( ) ;
restore_cpu_arch_register ( ) ;
/*
* 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 ) ;
}
/* Clear wakeup state register */
__raw_writel ( 0x0 , S5P_WAKEUP_STAT ) ;
2011-10-28 14:50:09 +04:00
return index ;
2011-03-16 01:28:23 +03:00
}
2014-05-09 01:43:26 +04:00
static int exynos_enter_lowpower ( struct cpuidle_device * dev ,
2012-03-08 14:07:27 +04:00
struct cpuidle_driver * drv ,
int index )
{
int new_index = index ;
2013-12-20 22:47:23 +04:00
/* AFTR can only be entered when cores other than CPU0 are offline */
if ( num_online_cpus ( ) > 1 | | dev - > cpu ! = 0 )
2012-03-08 14:07:27 +04:00
new_index = drv - > safe_state_index ;
if ( new_index = = 0 )
2012-05-12 11:29:21 +04:00
return arm_cpuidle_simple_enter ( dev , drv , new_index ) ;
2012-03-08 14:07:27 +04:00
else
2014-05-09 01:43:26 +04:00
return exynos_enter_core0_aftr ( dev , drv , new_index ) ;
2012-03-08 14:07:27 +04:00
}
2014-05-09 01:43:26 +04:00
static struct cpuidle_driver exynos_idle_driver = {
. name = " exynos_idle " ,
2014-05-09 01:43:26 +04:00
. owner = THIS_MODULE ,
. states = {
[ 0 ] = ARM_CPUIDLE_WFI_STATE ,
[ 1 ] = {
2014-05-09 01:43:26 +04:00
. enter = exynos_enter_lowpower ,
2014-05-09 01:43:26 +04:00
. exit_latency = 300 ,
. target_residency = 100000 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C1 " ,
. desc = " ARM power down " ,
} ,
} ,
. state_count = 2 ,
. safe_state_index = 0 ,
} ;
2013-10-21 05:53:03 +04:00
static int exynos_cpuidle_probe ( struct platform_device * pdev )
2011-03-16 01:28:23 +03:00
{
2014-05-09 01:43:26 +04:00
int ret ;
2011-10-28 14:50:42 +04:00
2013-08-27 19:48:24 +04:00
if ( soc_is_exynos5440 ( ) )
2014-05-09 01:43:26 +04:00
exynos_idle_driver . state_count = 1 ;
2013-08-27 19:48:24 +04:00
2014-05-09 01:43:26 +04:00
ret = cpuidle_register ( & exynos_idle_driver , NULL ) ;
2013-01-19 09:57:58 +04:00
if ( ret ) {
2013-10-21 05:52:15 +04:00
dev_err ( & pdev - > dev , " failed to register cpuidle driver \n " ) ;
2013-01-19 09:57:58 +04:00
return ret ;
2011-10-28 14:50:42 +04:00
}
2011-03-16 01:28:23 +03:00
return 0 ;
}
2013-08-30 14:15:04 +04:00
static struct platform_driver exynos_cpuidle_driver = {
. probe = exynos_cpuidle_probe ,
. driver = {
. name = " exynos_cpuidle " ,
. owner = THIS_MODULE ,
} ,
} ;
module_platform_driver ( exynos_cpuidle_driver ) ;