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>
2011-03-10 13:33:59 +09:00
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"
2014-07-08 07:54:08 +09:00
# include "regs-sys.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 ) ;
}
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 ;
2014-08-05 14:43:10 +02:00
exynos_set_wakeupmask ( 0x0000ff3e ) ;
/* 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 )
{
cpu_pm_enter ( ) ;
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 ( ) ;
cpu_pm_exit ( ) ;
}