2008-12-12 03:24:06 +03:00
/* linux/arch/arm/plat-s3c/pm.c
*
* Copyright 2008 Openmoko , Inc .
2009-11-14 01:54:14 +03:00
* Copyright 2004 - 2008 Simtec Electronics
2008-12-12 03:24:06 +03:00
* Ben Dooks < ben @ simtec . co . uk >
* http : //armlinux.simtec.co.uk/
*
* S3C common power management ( suspend to ram ) support .
*
* 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>
# include <linux/errno.h>
2008-12-12 03:24:08 +03:00
# include <linux/delay.h>
2013-05-17 20:24:29 +04:00
# include <linux/of.h>
2014-02-14 05:32:45 +04:00
# include <linux/serial_s3c.h>
2008-12-12 03:24:06 +03:00
# include <linux/io.h>
2008-12-12 03:24:08 +03:00
# include <asm/cacheflush.h>
2011-06-22 20:41:48 +04:00
# include <asm/suspend.h>
2008-12-12 03:24:08 +03:00
2013-06-15 04:17:33 +04:00
# ifdef CONFIG_SAMSUNG_ATAGS
# include <mach/map.h>
2013-12-18 23:07:32 +04:00
# ifndef CONFIG_ARCH_EXYNOS
2013-12-18 23:22:40 +04:00
# include <mach/regs-clock.h>
2008-12-12 03:24:08 +03:00
# include <mach/regs-irq.h>
2013-12-18 23:07:32 +04:00
# endif
2013-03-05 14:28:29 +04:00
# include <mach/irqs.h>
2013-06-15 04:17:33 +04:00
# endif
2008-12-12 03:24:12 +03:00
# include <asm/irq.h>
2008-12-12 03:24:08 +03:00
2008-12-12 03:24:06 +03:00
# include <plat/pm.h>
2010-02-05 15:52:53 +03:00
# include <mach/pm-core.h>
2008-12-12 03:24:06 +03:00
/* for external use */
unsigned long s3c_pm_flags ;
2008-12-12 03:24:12 +03:00
/* The IRQ ext-int code goes here, it is too small to currently bother
* with its own file . */
unsigned long s3c_irqwake_intmask = 0xffffffffL ;
unsigned long s3c_irqwake_eintmask = 0xffffffffL ;
2010-12-02 08:35:38 +03:00
int s3c_irqext_wake ( struct irq_data * data , unsigned int state )
2008-12-12 03:24:12 +03:00
{
2010-12-02 08:35:38 +03:00
unsigned long bit = 1L < < IRQ_EINT_BIT ( data - > irq ) ;
2008-12-12 03:24:12 +03:00
if ( ! ( s3c_irqwake_eintallow & bit ) )
return - ENOENT ;
printk ( KERN_INFO " wake %s for irq %d \n " ,
2010-12-02 08:35:38 +03:00
state ? " enabled " : " disabled " , data - > irq ) ;
2008-12-12 03:24:12 +03:00
if ( ! state )
s3c_irqwake_eintmask | = bit ;
else
s3c_irqwake_eintmask & = ~ bit ;
return 0 ;
}
2008-12-12 03:24:06 +03:00
2008-12-12 03:24:08 +03:00
void ( * pm_cpu_prep ) ( void ) ;
2011-07-02 12:54:01 +04:00
int ( * pm_cpu_sleep ) ( unsigned long ) ;
2008-12-12 03:24:08 +03:00
# define any_allowed(mask, allow) (((mask) & (allow)) != (allow))
/* s3c_pm_enter
*
* central control for sleep / resume process
*/
static int s3c_pm_enter ( suspend_state_t state )
{
2013-01-25 22:40:19 +04:00
int ret ;
2008-12-12 03:24:08 +03:00
/* ensure the debug is initialised (if enabled) */
s3c_pm_debug_init ( ) ;
S3C_PMDBG ( " %s(%d) \n " , __func__ , state ) ;
if ( pm_cpu_prep = = NULL | | pm_cpu_sleep = = NULL ) {
printk ( KERN_ERR " %s: error: no cpu sleep function \n " , __func__ ) ;
return - EINVAL ;
}
/* check if we have anything to wake-up with... bad things seem
* to happen if you suspend with no wakeup ( system will often
* require a full power - cycle )
*/
2013-05-17 20:24:29 +04:00
if ( ! of_have_populated_dt ( ) & &
! any_allowed ( s3c_irqwake_intmask , s3c_irqwake_intallow ) & &
2008-12-12 03:24:08 +03:00
! any_allowed ( s3c_irqwake_eintmask , s3c_irqwake_eintallow ) ) {
printk ( KERN_ERR " %s: No wake-up sources! \n " , __func__ ) ;
printk ( KERN_ERR " %s: Aborting sleep \n " , __func__ ) ;
return - EINVAL ;
}
/* save all necessary core registers not covered by the drivers */
2013-05-17 20:24:29 +04:00
if ( ! of_have_populated_dt ( ) ) {
samsung_pm_save_gpios ( ) ;
samsung_pm_saved_gpios ( ) ;
}
2008-12-12 03:24:20 +03:00
s3c_pm_save_uarts ( ) ;
2008-12-12 03:24:08 +03:00
s3c_pm_save_core ( ) ;
/* set the irq configuration for wake */
s3c_pm_configure_extint ( ) ;
S3C_PMDBG ( " sleep: irq wakeup masks: %08lx,%08lx \n " ,
s3c_irqwake_intmask , s3c_irqwake_eintmask ) ;
s3c_pm_arch_prepare_irqs ( ) ;
/* call cpu specific preparation */
pm_cpu_prep ( ) ;
/* flush cache back to ram */
flush_cache_all ( ) ;
s3c_pm_check_store ( ) ;
/* send the cpu to sleep... */
s3c_pm_arch_stop_clocks ( ) ;
2011-06-21 22:29:26 +04:00
/* this will also act as our return point from when
2009-03-10 14:48:07 +03:00
* we resume as it saves its own register state and restores it
* during the resume . */
2008-12-12 03:24:08 +03:00
2013-01-25 22:40:19 +04:00
ret = cpu_suspend ( 0 , pm_cpu_sleep ) ;
if ( ret )
return ret ;
2008-12-12 03:24:08 +03:00
/* restore the system state */
s3c_pm_restore_core ( ) ;
2008-12-12 03:24:20 +03:00
s3c_pm_restore_uarts ( ) ;
2013-05-17 20:24:29 +04:00
if ( ! of_have_populated_dt ( ) ) {
samsung_pm_restore_gpios ( ) ;
s3c_pm_restored_gpios ( ) ;
}
2008-12-12 03:24:08 +03:00
s3c_pm_debug_init ( ) ;
/* check what irq (if any) restored the system */
s3c_pm_arch_show_resume_irqs ( ) ;
S3C_PMDBG ( " %s: post sleep, preparing to return \n " , __func__ ) ;
2009-03-10 21:19:35 +03:00
/* LEDs should now be 1110 */
s3c_pm_debug_smdkled ( 1 < < 1 , 0 ) ;
2008-12-12 03:24:08 +03:00
s3c_pm_check_restore ( ) ;
/* ok, let's return from sleep */
S3C_PMDBG ( " S3C PM Resume (post-restore) \n " ) ;
return 0 ;
}
2008-12-12 03:24:34 +03:00
static int s3c_pm_prepare ( void )
{
/* prepare check area if configured */
s3c_pm_check_prepare ( ) ;
return 0 ;
}
static void s3c_pm_finish ( void )
{
s3c_pm_check_cleanup ( ) ;
}
2010-11-16 16:14:02 +03:00
static const struct platform_suspend_ops s3c_pm_ops = {
2008-12-12 03:24:08 +03:00
. enter = s3c_pm_enter ,
2008-12-12 03:24:34 +03:00
. prepare = s3c_pm_prepare ,
. finish = s3c_pm_finish ,
2008-12-12 03:24:08 +03:00
. valid = suspend_valid_only_mem ,
} ;
2008-12-12 03:24:18 +03:00
/* s3c_pm_init
2008-12-12 03:24:08 +03:00
*
* Attach the power management functions . This should be called
* from the board specific initialisation if the board supports
* it .
*/
2008-12-12 03:24:18 +03:00
int __init s3c_pm_init ( void )
2008-12-12 03:24:08 +03:00
{
printk ( " S3C Power Management, Copyright 2004 Simtec Electronics \n " ) ;
suspend_set_ops ( & s3c_pm_ops ) ;
return 0 ;
}