blob: 9caddfd9084b2fa419f4139b27fee109f038e27c [file] [log] [blame]
/* 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);