diff options
Diffstat (limited to 'arch/arm/plat-s3c/pm.c')
-rw-r--r-- | arch/arm/plat-s3c/pm.c | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/arch/arm/plat-s3c/pm.c b/arch/arm/plat-s3c/pm.c index 122e9b91a7f..fea58bea973 100644 --- a/arch/arm/plat-s3c/pm.c +++ b/arch/arm/plat-s3c/pm.c @@ -15,14 +15,32 @@ #include <linux/init.h> #include <linux/suspend.h> #include <linux/errno.h> +#include <linux/delay.h> +#include <linux/serial_core.h> #include <linux/io.h> +#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> + #include <plat/pm.h> +#include <plat/pm-core.h> /* for external use */ unsigned long s3c_pm_flags; +/* Debug code: + * + * This code supports debug output to the low level UARTs for use on + * resume before the console layer is available. +*/ + #ifdef CONFIG_S3C2410_PM_DEBUG extern void printascii(const char *); @@ -37,8 +55,51 @@ void s3c_pm_dbg(const char *fmt, ...) printascii(buff); } + +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) + #endif /* CONFIG_S3C2410_PM_DEBUG */ +/* Save the UART configurations if we are configured for debug. */ + +#ifdef CONFIG_S3C2410_PM_DEBUG + +#define SAVE_UART(va) \ + SAVE_ITEM((va) + S3C2410_ULCON), \ + SAVE_ITEM((va) + S3C2410_UCON), \ + SAVE_ITEM((va) + S3C2410_UFCON), \ + SAVE_ITEM((va) + S3C2410_UMCON), \ + SAVE_ITEM((va) + S3C2410_UBRDIV) + +static struct sleep_save uart_save[] = { + SAVE_UART(S3C_VA_UART0), + SAVE_UART(S3C_VA_UART1), +#ifndef CONFIG_CPU_S3C2400 + SAVE_UART(S3C_VA_UART2), +#endif +}; + +static void s3c_pm_save_uart(void) +{ + s3c_pm_do_save(uart_save, ARRAY_SIZE(uart_save)); +} + +static void s3c_pm_restore_uart(void) +{ + s3c_pm_do_restore(uart_save, ARRAY_SIZE(uart_save)); +} +#else +static void s3c_pm_save_uart(void) { } +static void s3c_pm_restore_uart(void) { } +#endif + /* helper functions to save and restore register state */ @@ -95,3 +156,153 @@ void s3c_pm_do_restore_core(struct sleep_save *ptr, int count) for (; count > 0; count--, ptr++) __raw_writel(ptr->val, ptr->reg); } + +/* 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; + } + + /* prepare check area if configured */ + + s3c_pm_check_prepare(); + + /* 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(); + s3c_pm_save_uart(); + 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 */ + + if (s3c2410_cpu_save(regs_save) == 0) { + flush_cache_all(); + pm_cpu_sleep(); + } + + /* restore the cpu state using the kernel's cpu init code. */ + + cpu_init(); + + /* restore the system state */ + + s3c_pm_restore_core(); + s3c_pm_restore_uart(); + 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; +} + +static struct platform_suspend_ops s3c_pm_ops = { + .enter = s3c_pm_enter, + .valid = suspend_valid_only_mem, +}; + +/* s3c2410_pm_init + * + * Attach the power management functions. This should be called + * from the board specific initialisation if the board supports + * it. +*/ + +int __init s3c2410_pm_init(void) +{ + printk("S3C Power Management, Copyright 2004 Simtec Electronics\n"); + + suspend_set_ops(&s3c_pm_ops); + return 0; +} |