2008-12-12 00:24:06 +00:00
/* linux/arch/arm/plat-s3c/pm.c
*
* Copyright 2008 Openmoko , Inc .
* Copyright 2004 , 2006 , 2008 Simtec Electronics
* 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 00:24:08 +00:00
# include <linux/delay.h>
# include <linux/serial_core.h>
2008-12-12 00:24:06 +00:00
# include <linux/io.h>
2008-12-12 00:24:08 +00:00
# include <asm/cacheflush.h>
# include <mach/hardware.h>
# include <plat/regs-serial.h>
# include <mach/regs-clock.h>
# include <mach/regs-gpio.h>
# include <mach/regs-mem.h>
# include <mach/regs-irq.h>
2008-12-12 00:24:12 +00:00
# include <asm/irq.h>
2008-12-12 00:24:08 +00:00
2008-12-12 00:24:06 +00:00
# include <plat/pm.h>
2008-12-12 00:24:08 +00:00
# include <plat/pm-core.h>
2008-12-12 00:24:06 +00:00
/* for external use */
unsigned long s3c_pm_flags ;
2008-12-12 00:24:08 +00:00
/* Debug code:
*
* This code supports debug output to the low level UARTs for use on
* resume before the console layer is available .
*/
2008-12-12 00:24:06 +00:00
# ifdef CONFIG_S3C2410_PM_DEBUG
extern void printascii ( const char * ) ;
void s3c_pm_dbg ( const char * fmt , . . . )
{
va_list va ;
char buff [ 256 ] ;
va_start ( va , fmt ) ;
vsprintf ( buff , fmt , va ) ;
va_end ( va ) ;
printascii ( buff ) ;
}
2008-12-12 00:24:08 +00:00
static inline void s3c_pm_debug_init ( void )
{
/* restart uart clocks so we can use them to output */
s3c_pm_debug_init_uart ( ) ;
}
# else
# define s3c_pm_debug_init() do { } while(0)
2008-12-12 00:24:06 +00:00
# endif /* CONFIG_S3C2410_PM_DEBUG */
2008-12-12 00:24:08 +00:00
/* Save the UART configurations if we are configured for debug. */
# ifdef CONFIG_S3C2410_PM_DEBUG
2008-12-12 00:24:20 +00:00
struct pm_uart_save uart_save [ CONFIG_SERIAL_SAMSUNG_UARTS ] ;
static void s3c_pm_save_uart ( unsigned int uart , struct pm_uart_save * save )
{
void __iomem * regs = S3C_VA_UARTx ( uart ) ;
save - > ulcon = __raw_readl ( regs + S3C2410_ULCON ) ;
save - > ucon = __raw_readl ( regs + S3C2410_UCON ) ;
save - > ufcon = __raw_readl ( regs + S3C2410_UFCON ) ;
save - > umcon = __raw_readl ( regs + S3C2410_UMCON ) ;
save - > ubrdiv = __raw_readl ( regs + S3C2410_UBRDIV ) ;
}
2008-12-12 00:24:08 +00:00
2008-12-12 00:24:20 +00:00
static void s3c_pm_save_uarts ( void )
2008-12-12 00:24:08 +00:00
{
2008-12-12 00:24:20 +00:00
struct pm_uart_save * save = uart_save ;
unsigned int uart ;
for ( uart = 0 ; uart < CONFIG_SERIAL_SAMSUNG_UARTS ; uart + + , save + + )
s3c_pm_save_uart ( uart , save ) ;
}
static void s3c_pm_restore_uart ( unsigned int uart , struct pm_uart_save * save )
{
void __iomem * regs = S3C_VA_UARTx ( uart ) ;
__raw_writel ( save - > ulcon , regs + S3C2410_ULCON ) ;
__raw_writel ( save - > ucon , regs + S3C2410_UCON ) ;
__raw_writel ( save - > ufcon , regs + S3C2410_UFCON ) ;
__raw_writel ( save - > umcon , regs + S3C2410_UMCON ) ;
__raw_writel ( save - > ubrdiv , regs + S3C2410_UBRDIV ) ;
2008-12-12 00:24:08 +00:00
}
2008-12-12 00:24:20 +00:00
static void s3c_pm_restore_uarts ( void )
2008-12-12 00:24:08 +00:00
{
2008-12-12 00:24:20 +00:00
struct pm_uart_save * save = uart_save ;
unsigned int uart ;
for ( uart = 0 ; uart < CONFIG_SERIAL_SAMSUNG_UARTS ; uart + + , save + + )
s3c_pm_restore_uart ( uart , save ) ;
2008-12-12 00:24:08 +00:00
}
# else
2008-12-12 00:24:20 +00:00
static void s3c_pm_save_uarts ( void ) { }
static void s3c_pm_restore_uarts ( void ) { }
2008-12-12 00:24:08 +00:00
# endif
2008-12-12 00:24:12 +00: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 ;
int s3c_irqext_wake ( unsigned int irqno , unsigned int state )
{
unsigned long bit = 1L < < IRQ_EINT_BIT ( irqno ) ;
if ( ! ( s3c_irqwake_eintallow & bit ) )
return - ENOENT ;
printk ( KERN_INFO " wake %s for irq %d \n " ,
state ? " enabled " : " disabled " , irqno ) ;
if ( ! state )
s3c_irqwake_eintmask | = bit ;
else
s3c_irqwake_eintmask & = ~ bit ;
return 0 ;
}
2008-12-12 00:24:06 +00:00
/* helper functions to save and restore register state */
/**
* s3c_pm_do_save ( ) - save a set of registers for restoration on resume .
* @ ptr : Pointer to an array of registers .
* @ count : Size of the ptr array .
*
* Run through the list of registers given , saving their contents in the
* array for later restoration when we wakeup .
*/
void s3c_pm_do_save ( struct sleep_save * ptr , int count )
{
for ( ; count > 0 ; count - - , ptr + + ) {
ptr - > val = __raw_readl ( ptr - > reg ) ;
S3C_PMDBG ( " saved %p value %08lx \n " , ptr - > reg , ptr - > val ) ;
}
}
/**
* s3c_pm_do_restore ( ) - restore register values from the save list .
* @ ptr : Pointer to an array of registers .
* @ count : Size of the ptr array .
*
* Restore the register values saved from s3c_pm_do_save ( ) .
*
* Note , we do not use S3C_PMDBG ( ) in here , as the system may not have
* restore the UARTs state yet
*/
void s3c_pm_do_restore ( struct sleep_save * ptr , int count )
{
for ( ; count > 0 ; count - - , ptr + + ) {
printk ( KERN_DEBUG " restore %p (restore %08lx, was %08x) \n " ,
ptr - > reg , ptr - > val , __raw_readl ( ptr - > reg ) ) ;
__raw_writel ( ptr - > val , ptr - > reg ) ;
}
}
/**
* s3c_pm_do_restore_core ( ) - early restore register values from save list .
*
* This is similar to s3c_pm_do_restore ( ) except we try and minimise the
* side effects of the function in case registers that hardware might need
* to work has been restored .
*
* WARNING : Do not put any debug in here that may effect memory or use
* peripherals , as things may be changing !
*/
void s3c_pm_do_restore_core ( struct sleep_save * ptr , int count )
{
for ( ; count > 0 ; count - - , ptr + + )
__raw_writel ( ptr - > val , ptr - > reg ) ;
}
2008-12-12 00:24:08 +00:00
/* s3c2410_pm_show_resume_irqs
*
* print any IRQs asserted at resume time ( ie , we woke from )
*/
static void s3c_pm_show_resume_irqs ( int start , unsigned long which ,
unsigned long mask )
{
int i ;
which & = ~ mask ;
for ( i = 0 ; i < = 31 ; i + + ) {
if ( which & ( 1L < < i ) ) {
S3C_PMDBG ( " IRQ %d asserted at resume \n " , start + i ) ;
}
}
}
void ( * pm_cpu_prep ) ( void ) ;
void ( * pm_cpu_sleep ) ( void ) ;
# 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 )
{
unsigned long regs_save [ 16 ] ;
/* 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 )
*/
if ( ! any_allowed ( s3c_irqwake_intmask , s3c_irqwake_intallow ) & &
! 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 ;
}
/* store the physical address of the register recovery block */
s3c_sleep_save_phys = virt_to_phys ( regs_save ) ;
S3C_PMDBG ( " s3c_sleep_save_phys=0x%08lx \n " , s3c_sleep_save_phys ) ;
/* save all necessary core registers not covered by the drivers */
s3c_pm_save_gpios ( ) ;
2008-12-12 00:24:20 +00:00
s3c_pm_save_uarts ( ) ;
2008-12-12 00:24:08 +00: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 ( ) ;
/* s3c2410_cpu_save will also act as our return point from when
* we resume as it saves its own register state , so use the return
* code to differentiate return from save and return from sleep */
2008-12-12 00:24:19 +00:00
if ( s3c_cpu_save ( regs_save ) = = 0 ) {
2008-12-12 00:24:08 +00:00
flush_cache_all ( ) ;
2008-12-12 00:24:19 +00:00
S3C_PMDBG ( " preparing to sleep \n " ) ;
2008-12-12 00:24:08 +00:00
pm_cpu_sleep ( ) ;
}
/* restore the cpu state using the kernel's cpu init code. */
cpu_init ( ) ;
/* restore the system state */
s3c_pm_restore_core ( ) ;
2008-12-12 00:24:20 +00:00
s3c_pm_restore_uarts ( ) ;
2008-12-12 00:24:08 +00:00
s3c_pm_restore_gpios ( ) ;
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__ ) ;
s3c_pm_check_restore ( ) ;
/* ok, let's return from sleep */
S3C_PMDBG ( " S3C PM Resume (post-restore) \n " ) ;
return 0 ;
}
2008-12-12 00:24:34 +00: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 ( ) ;
}
2008-12-12 00:24:08 +00:00
static struct platform_suspend_ops s3c_pm_ops = {
. enter = s3c_pm_enter ,
2008-12-12 00:24:34 +00:00
. prepare = s3c_pm_prepare ,
. finish = s3c_pm_finish ,
2008-12-12 00:24:08 +00:00
. valid = suspend_valid_only_mem ,
} ;
2008-12-12 00:24:18 +00:00
/* s3c_pm_init
2008-12-12 00:24:08 +00:00
*
* Attach the power management functions . This should be called
* from the board specific initialisation if the board supports
* it .
*/
2008-12-12 00:24:18 +00:00
int __init s3c_pm_init ( void )
2008-12-12 00:24:08 +00:00
{
printk ( " S3C Power Management, Copyright 2004 Simtec Electronics \n " ) ;
suspend_set_ops ( & s3c_pm_ops ) ;
return 0 ;
}