2015-01-24 08:05:50 +03:00
/*
* Copyright ( c ) 2011 - 2014 Samsung Electronics Co . , Ltd .
2011-03-16 01:28:23 +03:00
* http : //www.samsung.com
*
2015-01-24 08:05:50 +03:00
* Coupled cpuidle support based on the work of :
* Colin Cross < ccross @ android . com >
* Daniel Lezcano < daniel . lezcano @ linaro . org >
*
2011-03-16 01:28:23 +03:00
* 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/cpuidle.h>
2012-03-08 14:07:27 +04:00
# include <linux/cpu_pm.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>
2013-08-30 14:15:04 +04:00
# include <linux/platform_device.h>
2015-01-24 08:05:50 +03:00
# include <linux/of.h>
# include <linux/platform_data/cpuidle-exynos.h>
2011-03-16 01:28:23 +03:00
# include <asm/proc-fns.h>
2012-03-08 14:07:27 +04:00
# include <asm/suspend.h>
2012-05-12 11:29:21 +04:00
# include <asm/cpuidle.h>
2012-03-08 14:07:27 +04:00
2015-01-24 08:05:50 +03:00
static atomic_t exynos_idle_barrier ;
static struct cpuidle_exynos_data * exynos_cpuidle_pdata ;
2014-05-09 01:56:29 +04:00
static void ( * exynos_enter_aftr ) ( void ) ;
2012-12-31 22:06:48 +04:00
2015-01-24 08:05:50 +03:00
static int exynos_enter_coupled_lowpower ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
int ret ;
exynos_cpuidle_pdata - > pre_enter_aftr ( ) ;
/*
* Waiting all cpus to reach this point at the same moment
*/
cpuidle_coupled_parallel_barrier ( dev , & exynos_idle_barrier ) ;
/*
* Both cpus will reach this point at the same time
*/
ret = dev - > cpu ? exynos_cpuidle_pdata - > cpu1_powerdown ( )
: exynos_cpuidle_pdata - > cpu0_enter_aftr ( ) ;
if ( ret )
index = ret ;
/*
* Waiting all cpus to finish the power sequence before going further
*/
cpuidle_coupled_parallel_barrier ( dev , & exynos_idle_barrier ) ;
exynos_cpuidle_pdata - > post_enter_aftr ( ) ;
return index ;
}
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 ) ;
2014-08-05 16:43:10 +04:00
exynos_enter_aftr ( ) ;
return 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 ,
. name = " C1 " ,
. desc = " ARM power down " ,
} ,
} ,
. state_count = 2 ,
. safe_state_index = 0 ,
} ;
2015-01-24 08:05:50 +03:00
static struct cpuidle_driver exynos_coupled_idle_driver = {
. name = " exynos_coupled_idle " ,
. owner = THIS_MODULE ,
. states = {
[ 0 ] = ARM_CPUIDLE_WFI_STATE ,
[ 1 ] = {
. enter = exynos_enter_coupled_lowpower ,
. exit_latency = 5000 ,
. target_residency = 10000 ,
. flags = CPUIDLE_FLAG_COUPLED |
CPUIDLE_FLAG_TIMER_STOP ,
. 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
2015-03-17 21:26:11 +03:00
if ( IS_ENABLED ( CONFIG_SMP ) & &
of_machine_is_compatible ( " samsung,exynos4210 " ) ) {
2015-01-24 08:05:50 +03:00
exynos_cpuidle_pdata = pdev - > dev . platform_data ;
ret = cpuidle_register ( & exynos_coupled_idle_driver ,
cpu_possible_mask ) ;
} else {
exynos_enter_aftr = ( void * ) ( pdev - > dev . platform_data ) ;
ret = cpuidle_register ( & exynos_idle_driver , NULL ) ;
}
2014-05-09 01:56:29 +04:00
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 " ,
} ,
} ;
module_platform_driver ( exynos_cpuidle_driver ) ;