162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// Copyright 2008 Openmoko, Inc. 462306a36Sopenharmony_ci// Copyright 2004-2008 Simtec Electronics 562306a36Sopenharmony_ci// Ben Dooks <ben@simtec.co.uk> 662306a36Sopenharmony_ci// http://armlinux.simtec.co.uk/ 762306a36Sopenharmony_ci// 862306a36Sopenharmony_ci// S3C common power management (suspend to ram) support. 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/suspend.h> 1262306a36Sopenharmony_ci#include <linux/errno.h> 1362306a36Sopenharmony_ci#include <linux/delay.h> 1462306a36Sopenharmony_ci#include <linux/of.h> 1562306a36Sopenharmony_ci#include <linux/serial_s3c.h> 1662306a36Sopenharmony_ci#include <linux/io.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <asm/cacheflush.h> 1962306a36Sopenharmony_ci#include <asm/suspend.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include "map.h" 2262306a36Sopenharmony_ci#include "regs-clock.h" 2362306a36Sopenharmony_ci#include "regs-irq.h" 2462306a36Sopenharmony_ci#include "irqs.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include <asm/irq.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include "cpu.h" 2962306a36Sopenharmony_ci#include "pm.h" 3062306a36Sopenharmony_ci#include "pm-core.h" 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* for external use */ 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ciunsigned long s3c_pm_flags; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* The IRQ ext-int code goes here, it is too small to currently bother 3762306a36Sopenharmony_ci * with its own file. */ 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ciunsigned long s3c_irqwake_intmask = 0xffffffffL; 4062306a36Sopenharmony_ciunsigned long s3c_irqwake_eintmask = 0xffffffffL; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ciint s3c_irqext_wake(struct irq_data *data, unsigned int state) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci unsigned long bit = 1L << IRQ_EINT_BIT(data->irq); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci if (!(s3c_irqwake_eintallow & bit)) 4762306a36Sopenharmony_ci return -ENOENT; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci printk(KERN_INFO "wake %s for irq %d\n", 5062306a36Sopenharmony_ci state ? "enabled" : "disabled", data->irq); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (!state) 5362306a36Sopenharmony_ci s3c_irqwake_eintmask |= bit; 5462306a36Sopenharmony_ci else 5562306a36Sopenharmony_ci s3c_irqwake_eintmask &= ~bit; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci return 0; 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_civoid (*pm_cpu_prep)(void); 6162306a36Sopenharmony_ciint (*pm_cpu_sleep)(unsigned long); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci#define any_allowed(mask, allow) (((mask) & (allow)) != (allow)) 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci/* s3c_pm_enter 6662306a36Sopenharmony_ci * 6762306a36Sopenharmony_ci * central control for sleep/resume process 6862306a36Sopenharmony_ci*/ 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic int s3c_pm_enter(suspend_state_t state) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci int ret; 7362306a36Sopenharmony_ci /* ensure the debug is initialised (if enabled) */ 7462306a36Sopenharmony_ci s3c_pm_debug_init_uart(); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci S3C_PMDBG("%s(%d)\n", __func__, state); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (pm_cpu_prep == NULL || pm_cpu_sleep == NULL) { 7962306a36Sopenharmony_ci printk(KERN_ERR "%s: error: no cpu sleep function\n", __func__); 8062306a36Sopenharmony_ci return -EINVAL; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* check if we have anything to wake-up with... bad things seem 8462306a36Sopenharmony_ci * to happen if you suspend with no wakeup (system will often 8562306a36Sopenharmony_ci * require a full power-cycle) 8662306a36Sopenharmony_ci */ 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (!of_have_populated_dt() && 8962306a36Sopenharmony_ci !any_allowed(s3c_irqwake_intmask, s3c_irqwake_intallow) && 9062306a36Sopenharmony_ci !any_allowed(s3c_irqwake_eintmask, s3c_irqwake_eintallow)) { 9162306a36Sopenharmony_ci printk(KERN_ERR "%s: No wake-up sources!\n", __func__); 9262306a36Sopenharmony_ci printk(KERN_ERR "%s: Aborting sleep\n", __func__); 9362306a36Sopenharmony_ci return -EINVAL; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* save all necessary core registers not covered by the drivers */ 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci if (!of_have_populated_dt()) { 9962306a36Sopenharmony_ci samsung_pm_save_gpios(); 10062306a36Sopenharmony_ci samsung_pm_saved_gpios(); 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci s3c_pm_save_uarts(false); 10462306a36Sopenharmony_ci s3c_pm_save_core(); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* set the irq configuration for wake */ 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci s3c_pm_configure_extint(); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci S3C_PMDBG("sleep: irq wakeup masks: %08lx,%08lx\n", 11162306a36Sopenharmony_ci s3c_irqwake_intmask, s3c_irqwake_eintmask); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci s3c_pm_arch_prepare_irqs(); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* call cpu specific preparation */ 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci pm_cpu_prep(); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci /* flush cache back to ram */ 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci flush_cache_all(); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci s3c_pm_check_store(); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* send the cpu to sleep... */ 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci s3c_pm_arch_stop_clocks(); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* this will also act as our return point from when 13062306a36Sopenharmony_ci * we resume as it saves its own register state and restores it 13162306a36Sopenharmony_ci * during the resume. */ 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci ret = cpu_suspend(0, pm_cpu_sleep); 13462306a36Sopenharmony_ci if (ret) 13562306a36Sopenharmony_ci return ret; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* restore the system state */ 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci s3c_pm_restore_core(); 14062306a36Sopenharmony_ci s3c_pm_restore_uarts(false); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (!of_have_populated_dt()) { 14362306a36Sopenharmony_ci samsung_pm_restore_gpios(); 14462306a36Sopenharmony_ci s3c_pm_restored_gpios(); 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci s3c_pm_debug_init_uart(); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci /* check what irq (if any) restored the system */ 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci s3c_pm_arch_show_resume_irqs(); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci S3C_PMDBG("%s: post sleep, preparing to return\n", __func__); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci s3c_pm_check_restore(); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* ok, let's return from sleep */ 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci S3C_PMDBG("S3C PM Resume (post-restore)\n"); 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int s3c_pm_prepare(void) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci /* prepare check area if configured */ 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci s3c_pm_check_prepare(); 16862306a36Sopenharmony_ci return 0; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic void s3c_pm_finish(void) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci s3c_pm_check_cleanup(); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic const struct platform_suspend_ops s3c_pm_ops = { 17762306a36Sopenharmony_ci .enter = s3c_pm_enter, 17862306a36Sopenharmony_ci .prepare = s3c_pm_prepare, 17962306a36Sopenharmony_ci .finish = s3c_pm_finish, 18062306a36Sopenharmony_ci .valid = suspend_valid_only_mem, 18162306a36Sopenharmony_ci}; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci/* s3c_pm_init 18462306a36Sopenharmony_ci * 18562306a36Sopenharmony_ci * Attach the power management functions. This should be called 18662306a36Sopenharmony_ci * from the board specific initialisation if the board supports 18762306a36Sopenharmony_ci * it. 18862306a36Sopenharmony_ci*/ 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ciint __init s3c_pm_init(void) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci printk("S3C Power Management, Copyright 2004 Simtec Electronics\n"); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci suspend_set_ops(&s3c_pm_ops); 19562306a36Sopenharmony_ci return 0; 19662306a36Sopenharmony_ci} 197