2011-03-16 01:28:23 +03:00
/* linux/arch/arm/mach-exynos4/cpuidle.c
*
* 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>
# include <linux/time.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 <mach/regs-pmu.h>
# include <mach/pmu.h>
# include <plat/cpu.h>
# 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
static int exynos4_enter_lowpower ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index ) ;
2011-03-16 01:28:23 +03:00
2012-03-08 14:07:27 +04:00
static struct cpuidle_state exynos4_cpuidle_set [ ] __initdata = {
2012-05-12 11:29:21 +04:00
[ 0 ] = ARM_CPUIDLE_WFI_STATE ,
2012-03-08 14:07:27 +04:00
[ 1 ] = {
. enter = exynos4_enter_lowpower ,
. exit_latency = 300 ,
. target_residency = 100000 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C1 " ,
. desc = " ARM power down " ,
} ,
2011-03-16 01:28:23 +03:00
} ;
static DEFINE_PER_CPU ( struct cpuidle_device , exynos4_cpuidle_device ) ;
static struct cpuidle_driver exynos4_idle_driver = {
2012-05-12 11:29:21 +04:00
. name = " exynos4_idle " ,
. owner = THIS_MODULE ,
. en_core_tk_irqen = 1 ,
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 */
static void exynos4_set_wakeupmask ( void )
{
__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 ;
}
static int exynos4_enter_core0_aftr ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
unsigned long tmp ;
exynos4_set_wakeupmask ( ) ;
/* 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
__raw_writel ( virt_to_phys ( s3c_cpu_resume ) , REG_DIRECTGO_ADDR ) ;
__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
scu_enable ( S5P_VA_SCU ) ;
# 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
}
2012-03-08 14:07:27 +04:00
static int exynos4_enter_lowpower ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
int new_index = index ;
/* This mode only can be entered when other core's are offline */
if ( num_online_cpus ( ) > 1 )
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
return exynos4_enter_core0_aftr ( dev , drv , new_index ) ;
}
2011-03-16 01:28:23 +03:00
static int __init exynos4_init_cpuidle ( void )
{
int i , max_cpuidle_state , cpu_id ;
struct cpuidle_device * device ;
2011-10-28 14:50:42 +04:00
struct cpuidle_driver * drv = & exynos4_idle_driver ;
/* Setup cpuidle driver */
drv - > state_count = ( sizeof ( exynos4_cpuidle_set ) /
sizeof ( struct cpuidle_state ) ) ;
max_cpuidle_state = drv - > state_count ;
for ( i = 0 ; i < max_cpuidle_state ; i + + ) {
memcpy ( & drv - > states [ i ] , & exynos4_cpuidle_set [ i ] ,
sizeof ( struct cpuidle_state ) ) ;
}
2012-03-08 14:07:27 +04:00
drv - > safe_state_index = 0 ;
2011-03-16 01:28:23 +03:00
cpuidle_register_driver ( & exynos4_idle_driver ) ;
for_each_cpu ( cpu_id , cpu_online_mask ) {
device = & per_cpu ( exynos4_cpuidle_device , cpu_id ) ;
device - > cpu = cpu_id ;
2012-03-08 14:07:27 +04:00
if ( cpu_id = = 0 )
device - > state_count = ( sizeof ( exynos4_cpuidle_set ) /
sizeof ( struct cpuidle_state ) ) ;
else
device - > state_count = 1 ; /* Support IDLE only */
2011-03-16 01:28:23 +03:00
if ( cpuidle_register_device ( device ) ) {
printk ( KERN_ERR " CPUidle register device failed \n , " ) ;
return - EIO ;
}
}
2012-03-08 14:07:27 +04:00
2011-03-16 01:28:23 +03:00
return 0 ;
}
device_initcall ( exynos4_init_cpuidle ) ;