/* drivers/base/power/zx-suspend.c | |
* | |
* ZTE suspend manager | |
* | |
* Copyright (C) 2013 ZTE Ltd. | |
* by zxp | |
* | |
*/ | |
#include <linux/module.h> | |
#include <linux/platform_device.h> | |
#include <linux/rtc.h> | |
#include <linux/suspend.h> | |
#include <linux/syscalls.h> /* sys_sync */ | |
#include <linux/wakelock.h> | |
#include "power.h" | |
//#define CONFIG_LINUX_AUTOSLEEP /* may like MTK code to use linux autosleep interface */ | |
static struct workqueue_struct *sync_work_queue; | |
static struct wake_lock sync_wake_lock; | |
static struct workqueue_struct *suspend_work_queue; | |
static struct wake_lock main_wake_lock; | |
static struct wake_lock wakeup_irq_lock; | |
static suspend_state_t requested_suspend_state = PM_SUSPEND_MEM; | |
static bool zx_pm_init = false; | |
static unsigned int suspend_state = 0; | |
static DEFINE_MUTEX(suspend_lock); | |
static DEFINE_SPINLOCK(zx_suspend_lock); | |
extern void pm_debug_wakelocks(void); | |
extern bool pm_disable_suspend(void); | |
enum { | |
DEBUG_EXIT_SUSPEND = 1U << 0, | |
DEBUG_WAKE_LOCK = 1U << 1, | |
DEBUG_SUSPEND = 1U << 2, | |
}; | |
static int debug_mask = DEBUG_EXIT_SUSPEND | /* DEBUG_WAKE_LOCK |*/ DEBUG_SUSPEND; | |
/* /sys/module/zx-suspend/parameters/debug_mask */ | |
module_param(debug_mask, int, 0644); | |
static void sync_system(struct work_struct *work) | |
{ | |
sys_sync(); | |
wake_unlock(&sync_wake_lock); | |
if (debug_mask & DEBUG_SUSPEND) | |
pr_info("[SLP]: sync done\n"); | |
} | |
static DECLARE_WORK(sync_system_work, sync_system); | |
void test_wakelock(void) | |
{ | |
#ifndef CONFIG_ZX_AUTOSLEEP | |
wake_lock(&sync_wake_lock); | |
queue_work(sync_work_queue, &sync_system_work); | |
#endif | |
wake_unlock(&main_wake_lock); | |
} | |
#ifdef CONFIG_ZX_AUTOSLEEP | |
void app_start_done(void) | |
{ | |
wake_unlock(&main_wake_lock); | |
} | |
EXPORT_SYMBOL_GPL(app_start_done); | |
#endif | |
extern volatile int wakelock_current_event_num; | |
static void suspend(struct work_struct *work) | |
{ | |
int ret; | |
int entry_event_num; | |
struct timespec ts_entry, ts_exit; | |
mutex_lock(&suspend_lock); | |
suspend_state = 1; | |
entry_event_num = wakelock_current_event_num; | |
#ifdef CONFIG_ZX_AUTOSLEEP | |
sys_sync(); | |
#endif | |
getnstimeofday(&ts_entry); | |
ret = pm_suspend(requested_suspend_state); | |
getnstimeofday(&ts_exit); | |
if (debug_mask & DEBUG_EXIT_SUSPEND) { | |
struct rtc_time tm; | |
rtc_time_to_tm(ts_exit.tv_sec, &tm); | |
pr_info("[SLP]: exit suspend, ret = %d " | |
"(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", ret, | |
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, | |
tm.tm_hour, tm.tm_min, tm.tm_sec, ts_exit.tv_nsec); | |
} | |
#ifdef CONFIG_ZX_AUTOSLEEP | |
// wake_lock_timeout(&main_wake_lock, 2 ); /* 2 jiffies */ | |
if(wakelock_current_event_num == entry_event_num) | |
wake_lock_timeout(&main_wake_lock, msecs_to_jiffies(1*1000)); | |
#else | |
wake_lock(&main_wake_lock); | |
#endif | |
suspend_state = 0; | |
mutex_unlock(&suspend_lock); | |
} | |
static DECLARE_WORK(suspend_work, suspend); | |
/** | |
* zx_pm_suspend | |
* | |
* try suspend when wakelock is unlocked or expired anytime. | |
* | |
*/ | |
void zx_pm_suspend(void) | |
{ | |
#if 1 | |
//if(!zx_pm_init) | |
return ; | |
#else | |
unsigned int temp_count; | |
unsigned long flags; | |
if(!zx_pm_init) | |
return ; | |
if(pm_disable_suspend()) | |
return; | |
if (debug_mask & DEBUG_WAKE_LOCK) | |
pm_debug_wakelocks(); | |
spin_lock_irqsave(&zx_suspend_lock, flags); | |
if(pm_get_wakeup_count(&temp_count, false)) | |
{ | |
if (pm_save_wakeup_count(temp_count)) | |
{ | |
spin_unlock_irqrestore(&zx_suspend_lock, flags); | |
if (!suspend_state) | |
queue_work(suspend_work_queue, &suspend_work); | |
pm_get_wakeup_count(&temp_count, false); | |
} | |
else | |
{ | |
spin_unlock_irqrestore(&zx_suspend_lock, flags); | |
//error save the count, why? | |
if (debug_mask & DEBUG_SUSPEND) | |
{ | |
pr_info("suspend: error save wakeup_count: %d ", temp_count); | |
} | |
} | |
} | |
else | |
{ | |
spin_unlock_irqrestore(&zx_suspend_lock, flags); | |
} | |
#endif | |
} | |
EXPORT_SYMBOL_GPL(zx_pm_suspend); | |
void zx_pm_wakeup_irq_timeout(void) | |
{ | |
if (!zx_pm_init) | |
return; | |
wake_lock_timeout(&wakeup_irq_lock, msecs_to_jiffies(1*1000)); | |
} | |
EXPORT_SYMBOL_GPL(zx_pm_wakeup_irq_timeout); | |
static int __init zx_suspend_init(void) | |
{ | |
int ret; | |
/* we init a main wakelock for maintain system on until app start ok! */ | |
wake_lock_init(&main_wake_lock, WAKE_LOCK_SUSPEND, "main"); | |
wake_lock_init(&sync_wake_lock, WAKE_LOCK_SUSPEND, "sync_system"); | |
wake_lock_init(&wakeup_irq_lock, WAKE_LOCK_SUSPEND, "wakeup_irq"); | |
// wake_lock(&main_wake_lock); | |
wake_lock_timeout(&main_wake_lock, msecs_to_jiffies(60*1000)); | |
suspend_work_queue = create_singlethread_workqueue("suspend"); | |
if (suspend_work_queue == NULL) | |
{ | |
ret = -ENOMEM; | |
goto err_suspend_work_queue; | |
} | |
sync_work_queue = create_singlethread_workqueue("sync_system_work"); | |
if (sync_work_queue == NULL) { | |
pr_err("%s: failed to create sync_work_queue\n", __func__); | |
ret = -ENOMEM; | |
goto err_sync_work_queue; | |
} | |
zx_pm_init = true; | |
return 0; | |
err_sync_work_queue: | |
destroy_workqueue(suspend_work_queue); | |
err_suspend_work_queue: | |
wake_lock_destroy(&main_wake_lock); | |
wake_lock_destroy(&sync_wake_lock); | |
return ret; | |
} | |
static void __exit zx_suspend_exit(void) | |
{ | |
destroy_workqueue(sync_work_queue); | |
destroy_workqueue(suspend_work_queue); | |
wake_lock_destroy(&sync_wake_lock); | |
wake_lock_destroy(&main_wake_lock); | |
} | |
late_initcall(zx_suspend_init); | |
module_exit(zx_suspend_exit); | |