2012-02-17 09:49:54 +09:00
/*
2014-09-25 18:02:45 +09:00
* Copyright ( c ) 2011 - 2014 Samsung Electronics Co . , Ltd .
2011-03-10 13:33:59 +09:00
* http : //www.samsung.com
*
2012-02-17 09:49:54 +09:00
* EXYNOS - Power Management support
2011-03-10 13:33:59 +09: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>
2014-05-09 06:43:27 +09:00
# include <linux/cpu_pm.h>
2011-03-10 13:33:59 +09:00
# include <linux/io.h>
2011-07-18 19:25:13 +09:00
# include <linux/err.h>
2011-03-10 13:33:59 +09:00
2014-09-24 01:24:39 +09:00
# include <asm/firmware.h>
2011-11-17 01:19:11 +09:00
# include <asm/smp_scu.h>
2014-03-18 07:28:27 +09:00
# include <asm/suspend.h>
2015-03-18 14:09:57 +01:00
# include <asm/cacheflush.h>
2011-03-10 13:33:59 +09:00
2015-01-09 01:14:23 +09:00
# include <mach/map.h>
2014-03-18 07:28:27 +09:00
# include <plat/pm-common.h>
2012-12-31 10:06:48 -08:00
# include "common.h"
2014-11-07 09:26:47 +09:00
# include "exynos-pmu.h"
2013-12-19 04:06:56 +09:00
# include "regs-pmu.h"
2011-03-10 13:33:59 +09:00
2014-09-25 17:59:40 +09:00
static inline void __iomem * exynos_boot_vector_addr ( void )
{
if ( samsung_rev ( ) = = EXYNOS4210_REV_1_1 )
return pmu_base_addr + S5P_INFORM7 ;
else if ( samsung_rev ( ) = = EXYNOS4210_REV_1_0 )
return sysram_base_addr + 0x24 ;
return pmu_base_addr + S5P_INFORM0 ;
}
static inline void __iomem * exynos_boot_vector_flag ( void )
{
if ( samsung_rev ( ) = = EXYNOS4210_REV_1_1 )
return pmu_base_addr + S5P_INFORM6 ;
else if ( samsung_rev ( ) = = EXYNOS4210_REV_1_0 )
return sysram_base_addr + 0x20 ;
return pmu_base_addr + S5P_INFORM1 ;
}
2014-05-09 06:53:00 +09:00
2014-05-09 06:56:24 +09:00
# define S5P_CHECK_AFTR 0xFCBA0D10
2014-05-09 06:53:00 +09:00
2011-07-18 19:25:03 +09:00
/* For Cortex-A9 Diagnostic and Power control register */
static unsigned int save_arm_register [ 2 ] ;
2014-09-25 18:02:45 +09:00
void exynos_cpu_save_register ( void )
2014-05-09 06:43:27 +09:00
{
unsigned long tmp ;
/* 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 ;
}
2014-09-25 18:02:45 +09:00
void exynos_cpu_restore_register ( void )
2014-05-09 06:43:27 +09:00
{
unsigned long tmp ;
/* 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 " ) ;
}
2014-09-25 18:02:45 +09:00
void exynos_pm_central_suspend ( void )
2014-08-05 14:43:10 +02:00
{
unsigned long tmp ;
/* Setting Central Sequence Register for power down mode */
tmp = pmu_raw_readl ( S5P_CENTRAL_SEQ_CONFIGURATION ) ;
tmp & = ~ S5P_CENTRAL_LOWPWR_CFG ;
pmu_raw_writel ( tmp , S5P_CENTRAL_SEQ_CONFIGURATION ) ;
}
2014-09-25 18:02:45 +09:00
int exynos_pm_central_resume ( void )
2014-08-05 14:43:10 +02: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 = pmu_raw_readl ( S5P_CENTRAL_SEQ_CONFIGURATION ) ;
if ( ! ( tmp & S5P_CENTRAL_LOWPWR_CFG ) ) {
tmp | = S5P_CENTRAL_LOWPWR_CFG ;
pmu_raw_writel ( tmp , S5P_CENTRAL_SEQ_CONFIGURATION ) ;
/* clear the wakeup state register */
pmu_raw_writel ( 0x0 , S5P_WAKEUP_STAT ) ;
/* No need to perform below restore code */
return - 1 ;
}
return 0 ;
}
/* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */
static void exynos_set_wakeupmask ( long mask )
{
pmu_raw_writel ( mask , S5P_WAKEUP_MASK ) ;
2015-03-27 02:35:48 +09:00
if ( soc_is_exynos3250 ( ) )
pmu_raw_writel ( 0x0 , S5P_WAKEUP_MASK2 ) ;
2014-08-05 14:43:10 +02:00
}
static void exynos_cpu_set_boot_vector ( long flags )
{
2014-09-25 17:59:40 +09:00
__raw_writel ( virt_to_phys ( exynos_cpu_resume ) ,
exynos_boot_vector_addr ( ) ) ;
__raw_writel ( flags , exynos_boot_vector_flag ( ) ) ;
2014-08-05 14:43:10 +02:00
}
static int exynos_aftr_finisher ( unsigned long flags )
{
2014-09-25 17:59:41 +09:00
int ret ;
2015-03-27 02:35:48 +09:00
exynos_set_wakeupmask ( soc_is_exynos3250 ( ) ? 0x40003ffe : 0x0000ff3e ) ;
2014-08-05 14:43:10 +02:00
/* Set value of power down register for aftr mode */
exynos_sys_powerdown_conf ( SYS_AFTR ) ;
2014-09-25 17:59:41 +09:00
ret = call_firmware_op ( do_idle , FW_DO_IDLE_AFTR ) ;
if ( ret = = - ENOSYS ) {
if ( read_cpuid_part ( ) = = ARM_CPU_PART_CORTEX_A9 )
exynos_cpu_save_register ( ) ;
exynos_cpu_set_boot_vector ( S5P_CHECK_AFTR ) ;
cpu_do_idle ( ) ;
}
2014-08-05 14:43:10 +02:00
return 1 ;
}
void exynos_enter_aftr ( void )
{
2015-03-27 02:35:48 +09:00
unsigned int cpuid = smp_processor_id ( ) ;
2014-08-05 14:43:10 +02:00
cpu_pm_enter ( ) ;
2015-03-27 02:35:48 +09:00
if ( soc_is_exynos3250 ( ) )
exynos_set_boot_flag ( cpuid , C2_STATE ) ;
2014-08-05 14:43:10 +02:00
exynos_pm_central_suspend ( ) ;
2015-01-24 14:05:50 +09:00
if ( of_machine_is_compatible ( " samsung,exynos4212 " ) | |
of_machine_is_compatible ( " samsung,exynos4412 " ) ) {
/* Setting SEQ_OPTION register */
pmu_raw_writel ( S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0 ,
S5P_CENTRAL_SEQ_OPTION ) ;
}
2014-08-05 14:43:10 +02:00
cpu_suspend ( 0 , exynos_aftr_finisher ) ;
if ( read_cpuid_part ( ) = = ARM_CPU_PART_CORTEX_A9 ) {
scu_enable ( S5P_VA_SCU ) ;
2014-09-25 17:59:41 +09:00
if ( call_firmware_op ( resume ) = = - ENOSYS )
exynos_cpu_restore_register ( ) ;
2014-08-05 14:43:10 +02:00
}
exynos_pm_central_resume ( ) ;
2015-03-27 02:35:48 +09:00
if ( soc_is_exynos3250 ( ) )
exynos_clear_boot_flag ( cpuid , C2_STATE ) ;
2014-08-05 14:43:10 +02:00
cpu_pm_exit ( ) ;
}
2015-01-24 14:05:50 +09:00
2015-03-18 03:26:11 +09:00
# if defined(CONFIG_SMP) && defined(CONFIG_ARM_EXYNOS_CPUIDLE)
2015-01-24 14:05:50 +09:00
static atomic_t cpu1_wakeup = ATOMIC_INIT ( 0 ) ;
static int exynos_cpu0_enter_aftr ( void )
{
int ret = - 1 ;
/*
* If the other cpu is powered on , we have to power it off , because
* the AFTR state won ' t work otherwise
*/
if ( cpu_online ( 1 ) ) {
/*
* We reach a sync point with the coupled idle state , we know
* the other cpu will power down itself or will abort the
* sequence , let ' s wait for one of these to happen
*/
while ( exynos_cpu_power_state ( 1 ) ) {
2015-03-18 14:09:57 +01:00
unsigned long boot_addr ;
2015-01-24 14:05:50 +09:00
/*
* The other cpu may skip idle and boot back
* up again
*/
if ( atomic_read ( & cpu1_wakeup ) )
goto abort ;
/*
* The other cpu may bounce through idle and
* boot back up again , getting stuck in the
* boot rom code
*/
2015-03-18 14:09:57 +01:00
ret = exynos_get_boot_addr ( 1 , & boot_addr ) ;
if ( ret )
goto fail ;
ret = - 1 ;
if ( boot_addr = = 0 )
2015-01-24 14:05:50 +09:00
goto abort ;
cpu_relax ( ) ;
}
}
exynos_enter_aftr ( ) ;
ret = 0 ;
abort :
if ( cpu_online ( 1 ) ) {
2015-03-18 14:09:57 +01:00
unsigned long boot_addr = virt_to_phys ( exynos_cpu_resume ) ;
2015-01-24 14:05:50 +09:00
/*
* Set the boot vector to something non - zero
*/
2015-03-18 14:09:57 +01:00
ret = exynos_set_boot_addr ( 1 , boot_addr ) ;
if ( ret )
goto fail ;
2015-01-24 14:05:50 +09:00
dsb ( ) ;
/*
* Turn on cpu1 and wait for it to be on
*/
exynos_cpu_power_up ( 1 ) ;
while ( exynos_cpu_power_state ( 1 ) ! = S5P_CORE_LOCAL_PWR_EN )
cpu_relax ( ) ;
2015-03-18 14:09:57 +01:00
if ( soc_is_exynos3250 ( ) ) {
while ( ! pmu_raw_readl ( S5P_PMU_SPARE2 ) & &
! atomic_read ( & cpu1_wakeup ) )
cpu_relax ( ) ;
if ( ! atomic_read ( & cpu1_wakeup ) )
exynos_core_restart ( 1 ) ;
}
2015-01-24 14:05:50 +09:00
while ( ! atomic_read ( & cpu1_wakeup ) ) {
2015-03-18 14:09:57 +01:00
smp_rmb ( ) ;
2015-01-24 14:05:50 +09:00
/*
* Poke cpu1 out of the boot rom
*/
2015-03-18 14:09:57 +01:00
ret = exynos_set_boot_addr ( 1 , boot_addr ) ;
if ( ret )
goto fail ;
call_firmware_op ( cpu_boot , 1 ) ;
if ( soc_is_exynos3250 ( ) )
dsb_sev ( ) ;
else
arch_send_wakeup_ipi_mask ( cpumask_of ( 1 ) ) ;
2015-01-24 14:05:50 +09:00
}
}
2015-03-18 14:09:57 +01:00
fail :
2015-01-24 14:05:50 +09:00
return ret ;
}
static int exynos_wfi_finisher ( unsigned long flags )
{
2015-03-18 14:09:57 +01:00
if ( soc_is_exynos3250 ( ) )
flush_cache_all ( ) ;
2015-01-24 14:05:50 +09:00
cpu_do_idle ( ) ;
return - 1 ;
}
static int exynos_cpu1_powerdown ( void )
{
int ret = - 1 ;
/*
* Idle sequence for cpu1
*/
if ( cpu_pm_enter ( ) )
goto cpu1_aborted ;
/*
* Turn off cpu 1
*/
exynos_cpu_power_down ( 1 ) ;
2015-03-18 14:09:57 +01:00
if ( soc_is_exynos3250 ( ) )
pmu_raw_writel ( 0 , S5P_PMU_SPARE2 ) ;
2015-01-24 14:05:50 +09:00
ret = cpu_suspend ( 0 , exynos_wfi_finisher ) ;
cpu_pm_exit ( ) ;
cpu1_aborted :
dsb ( ) ;
/*
* Notify cpu 0 that cpu 1 is awake
*/
atomic_set ( & cpu1_wakeup , 1 ) ;
return ret ;
}
static void exynos_pre_enter_aftr ( void )
{
2015-03-18 14:09:57 +01:00
unsigned long boot_addr = virt_to_phys ( exynos_cpu_resume ) ;
( void ) exynos_set_boot_addr ( 1 , boot_addr ) ;
2015-01-24 14:05:50 +09:00
}
static void exynos_post_enter_aftr ( void )
{
atomic_set ( & cpu1_wakeup , 0 ) ;
}
struct cpuidle_exynos_data cpuidle_coupled_exynos_data = {
. cpu0_enter_aftr = exynos_cpu0_enter_aftr ,
. cpu1_powerdown = exynos_cpu1_powerdown ,
. pre_enter_aftr = exynos_pre_enter_aftr ,
. post_enter_aftr = exynos_post_enter_aftr ,
} ;
2015-03-18 03:26:11 +09:00
# endif /* CONFIG_SMP && CONFIG_ARM_EXYNOS_CPUIDLE */