rendered paste body/* drivers/android/power.c
*
* Copyright (C) 2005-2008 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/list.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
//#include <linux/platform_device.h>
#include <linux/sysdev.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/rtc.h>
#include <linux/wait.h>
#include <linux/android_power.h>
#include <linux/suspend.h>
#include <linux/syscalls.h> // sys_sync
#include <linux/console.h>
#include <linux/kbd_kern.h>
#include <linux/vt_kern.h>
#include <linux/freezer.h>
#ifdef CONFIG_ANDROID_POWER_STAT
#include <linux/proc_fs.h>
#endif
enum {
ANDROID_POWER_DEBUG_USER_STATE = 1U << 0,
ANDROID_POWER_DEBUG_EXIT_SUSPEND = 1U << 1,
ANDROID_POWER_DEBUG_SUSPEND = 1U << 2,
ANDROID_POWER_DEBUG_USER_WAKE_LOCK = 1U << 3,
ANDROID_POWER_DEBUG_WAKE_LOCK = 1U << 4,
};
static int android_power_debug_mask =
ANDROID_POWER_DEBUG_USER_STATE | ANDROID_POWER_DEBUG_EXIT_SUSPEND;
module_param_named(debug_mask, android_power_debug_mask,
int, S_IRUGO | S_IWUSR | S_IWGRP);
#define ANDROID_POWER_TEST_EARLY_SUSPEND 0
MODULE_DESCRIPTION("OMAP CSMI Driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");
#define ANDROID_SUSPEND_CONSOLE (MAX_NR_CONSOLES-2)
static spinlock_t g_list_lock = SPIN_LOCK_UNLOCKED;
static DEFINE_MUTEX(g_early_suspend_lock);
wait_queue_head_t g_wait_queue;
static LIST_HEAD(g_inactive_locks);
static LIST_HEAD(g_active_idle_wake_locks);
static LIST_HEAD(g_active_partial_wake_locks);
static LIST_HEAD(g_active_full_wake_locks);
static LIST_HEAD(g_early_suspend_handlers);
static enum {
USER_AWAKE,
USER_NOTIFICATION,
USER_SLEEP
} g_user_suspend_state;
static int g_current_event_num;
static struct workqueue_struct *g_suspend_work_queue;
static void android_power_suspend(struct work_struct *work);
static void android_power_wakeup_locked(int notification, ktime_t time);
static DECLARE_WORK(g_suspend_work, android_power_suspend);
static int g_max_user_lockouts = 16;
//static const char g_free_user_lockout_name[] = "free_user";
static struct {
enum {
USER_WAKE_LOCK_INACTIVE,
USER_WAKE_LOCK_PARTIAL,
USER_WAKE_LOCK_FULL
} state;
android_suspend_lock_t suspend_lock;
char name_buffer[32];
} *g_user_wake_locks;
#ifdef CONFIG_ANDROID_POWER_STAT
android_suspend_lock_t g_deleted_wake_locks;
android_suspend_lock_t g_no_wake_locks;
#endif
static struct kobject *android_power_kobj;
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
static wait_queue_head_t fb_state_wq;
static spinlock_t fb_state_lock = SPIN_LOCK_UNLOCKED;
int fb_state;
#endif
#if 0
android_suspend_lock_t *android_allocate_suspend_lock(const char *debug_name)
{
unsigned long irqflags;
struct android_power *e;
e = kzalloc(sizeof(*e), GFP_KERNEL);
if(e == NULL) {
printk("android_power_allocate: kzalloc failed\n");
return NULL;
}
e->name = debug_name;
spin_lock_irqsave(&g_list_lock, irqflags);
list_add(&e->link, &g_allocated);
spin_unlock_irqrestore(&g_list_lock, irqflags);
return e;
}
#endif
static int android_init_suspend_lock_internal(
android_suspend_lock_t *lock, int has_spin_lock)
{
unsigned long irqflags;
if(lock->name == NULL) {
printk(KERN_ERR "android_init_suspend_lock: error name=NULL, "
"lock=%p\n", lock);
dump_stack();
return -EINVAL;
}
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_init_suspend_lock name=%s\n",
lock->name);
#ifdef CONFIG_ANDROID_POWER_STAT
lock->stat.count = 0;
lock->stat.expire_count = 0;
lock->stat.total_time = ktime_set(0, 0);
lock->stat.max_time = ktime_set(0, 0);
lock->stat.last_time = ktime_set(0, 0);
#endif
lock->flags = 0;
INIT_LIST_HEAD(&lock->link);
if (!has_spin_lock)
spin_lock_irqsave(&g_list_lock, irqflags);
list_add(&lock->link, &g_inactive_locks);
if (!has_spin_lock)
spin_unlock_irqrestore(&g_list_lock, irqflags);
// if(lock->flags & ANDROID_SUSPEND_LOCK_FLAG_USER_VISIBLE_MASK) {
// sysfs_create_file(struct kobject * k, const struct attribute * a)
// }
return 0;
}
int android_init_suspend_lock(android_suspend_lock_t *lock)
{
return android_init_suspend_lock_internal(lock, 0);
}
void android_uninit_suspend_lock(android_suspend_lock_t *lock)
{
unsigned long irqflags;
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_uninit_suspend_lock name=%s\n",
lock->name);
spin_lock_irqsave(&g_list_lock, irqflags);
#ifdef CONFIG_ANDROID_POWER_STAT
if(lock->stat.count) {
if(g_deleted_wake_locks.stat.count == 0) {
g_deleted_wake_locks.name = "deleted_wake_locks";
android_init_suspend_lock_internal(
&g_deleted_wake_locks, 1);
}
g_deleted_wake_locks.stat.count += lock->stat.count;
g_deleted_wake_locks.stat.expire_count += lock->stat.expire_count;
g_deleted_wake_locks.stat.total_time = ktime_add(g_deleted_wake_locks.stat.total_time, lock->stat.total_time);
g_deleted_wake_locks.stat.max_time = ktime_add(g_deleted_wake_locks.stat.max_time, lock->stat.max_time);
}
#endif
list_del(&lock->link);
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
void android_lock_idle(android_suspend_lock_t *lock)
{
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
#ifdef CONFIG_ANDROID_POWER_STAT
if(!(lock->flags & ANDROID_SUSPEND_LOCK_ACTIVE)) {
lock->flags |= ANDROID_SUSPEND_LOCK_ACTIVE;
lock->stat.last_time = ktime_get();
}
#endif
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_power: acquire idle wake lock: %s\n",
lock->name);
lock->expires = INT_MAX;
lock->flags &= ~ANDROID_SUSPEND_LOCK_AUTO_EXPIRE;
list_del(&lock->link);
list_add(&lock->link, &g_active_idle_wake_locks);
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
void android_lock_idle_auto_expire(android_suspend_lock_t *lock, int timeout)
{
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
#ifdef CONFIG_ANDROID_POWER_STAT
if(!(lock->flags & ANDROID_SUSPEND_LOCK_ACTIVE)) {
lock->flags |= ANDROID_SUSPEND_LOCK_ACTIVE;
lock->stat.last_time = ktime_get();
}
#endif
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_power: acquire idle wake lock: %s, "
"timeout %d.%03lu\n", lock->name, timeout / HZ,
(timeout % HZ) * MSEC_PER_SEC / HZ);
lock->expires = jiffies + timeout;
lock->flags |= ANDROID_SUSPEND_LOCK_AUTO_EXPIRE;
list_del(&lock->link);
list_add(&lock->link, &g_active_idle_wake_locks);
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
void android_lock_suspend(android_suspend_lock_t *lock)
{
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
#ifdef CONFIG_ANDROID_POWER_STAT
if(!(lock->flags & ANDROID_SUSPEND_LOCK_ACTIVE)) {
lock->flags |= ANDROID_SUSPEND_LOCK_ACTIVE;
lock->stat.last_time = ktime_get();
}
#endif
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_power: acquire wake lock: %s\n",
lock->name);
lock->expires = INT_MAX;
lock->flags &= ~ANDROID_SUSPEND_LOCK_AUTO_EXPIRE;
list_del(&lock->link);
list_add(&lock->link, &g_active_partial_wake_locks);
g_current_event_num++;
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
void android_lock_suspend_auto_expire(android_suspend_lock_t *lock, int timeout)
{
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
#ifdef CONFIG_ANDROID_POWER_STAT
if(!(lock->flags & ANDROID_SUSPEND_LOCK_ACTIVE)) {
lock->flags |= ANDROID_SUSPEND_LOCK_ACTIVE;
lock->stat.last_time = ktime_get();
}
#endif
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_power: acquire wake lock: %s, "
"timeout %d.%03lu\n", lock->name, timeout / HZ,
(timeout % HZ) * MSEC_PER_SEC / HZ);
lock->expires = jiffies + timeout;
lock->flags |= ANDROID_SUSPEND_LOCK_AUTO_EXPIRE;
list_del(&lock->link);
list_add(&lock->link, &g_active_partial_wake_locks);
g_current_event_num++;
wake_up(&g_wait_queue);
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
void android_lock_partial_suspend_auto_expire(android_suspend_lock_t *lock, int timeout)
{
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
#ifdef CONFIG_ANDROID_POWER_STAT
if(!(lock->flags & ANDROID_SUSPEND_LOCK_ACTIVE)) {
lock->flags |= ANDROID_SUSPEND_LOCK_ACTIVE;
lock->stat.last_time = ktime_get();
}
#endif
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_power: acquire full wake lock: %s, "
"timeout %d.%03lu\n", lock->name, timeout / HZ,
(timeout % HZ) * MSEC_PER_SEC / HZ);
lock->expires = jiffies + timeout;
lock->flags |= ANDROID_SUSPEND_LOCK_AUTO_EXPIRE;
list_del(&lock->link);
list_add(&lock->link, &g_active_full_wake_locks);
g_current_event_num++;
wake_up(&g_wait_queue);
android_power_wakeup_locked(1, ktime_get());
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
#ifdef CONFIG_ANDROID_POWER_STAT
static int print_lock_stat(char *buf, android_suspend_lock_t *lock)
{
ktime_t active_time;
if(lock->flags & ANDROID_SUSPEND_LOCK_ACTIVE)
active_time = ktime_sub(ktime_get(), lock->stat.last_time);
else
active_time = ktime_set(0, 0);
return sprintf(buf, "\"%s\"\t%d\t%d\t%lld\t%lld\t%lld\t%lld\n",
lock->name,
lock->stat.count, lock->stat.expire_count,
ktime_to_ns(active_time),
ktime_to_ns(lock->stat.total_time),
ktime_to_ns(lock->stat.max_time),
ktime_to_ns(lock->stat.last_time));
}
static int wakelocks_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
unsigned long irqflags;
android_suspend_lock_t *lock;
int len = 0;
char *p = page;
spin_lock_irqsave(&g_list_lock, irqflags);
p += sprintf(p, "name\tcount\texpire_count\tactive_since\ttotal_time\tmax_time\tlast_change\n");
list_for_each_entry(lock, &g_inactive_locks, link) {
p += print_lock_stat(p, lock);
}
list_for_each_entry(lock, &g_active_partial_wake_locks, link) {
p += print_lock_stat(p, lock);
}
list_for_each_entry(lock, &g_active_full_wake_locks, link) {
p += print_lock_stat(p, lock);
}
spin_unlock_irqrestore(&g_list_lock, irqflags);
*start = page + off;
len = p - page;
if (len > off)
len -= off;
else
len = 0;
return len < count ? len : count;
}
static void android_unlock_suspend_stat_locked(android_suspend_lock_t *lock)
{
if(lock->flags & ANDROID_SUSPEND_LOCK_ACTIVE) {
ktime_t duration;
lock->flags &= ~ANDROID_SUSPEND_LOCK_ACTIVE;
lock->stat.count++;
duration = ktime_sub(ktime_get(), lock->stat.last_time);
lock->stat.total_time = ktime_add(lock->stat.total_time, duration);
if(ktime_to_ns(duration) > ktime_to_ns(lock->stat.max_time))
lock->stat.max_time = duration;
lock->stat.last_time = ktime_get();
}
}
#endif
void android_unlock_suspend(android_suspend_lock_t *lock)
{
int had_full_wake_locks;
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
#ifdef CONFIG_ANDROID_POWER_STAT
android_unlock_suspend_stat_locked(lock);
#endif
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk(KERN_INFO "android_power: release wake lock: %s\n",
lock->name);
lock->flags &= ~ANDROID_SUSPEND_LOCK_AUTO_EXPIRE;
had_full_wake_locks = !list_empty(&g_active_full_wake_locks);
list_del(&lock->link);
list_add(&lock->link, &g_inactive_locks);
wake_up(&g_wait_queue);
if(had_full_wake_locks && list_empty(&g_active_full_wake_locks)) {
printk("android_unlock_suspend: released at %lld\n", ktime_to_ns(ktime_get()));
if(g_user_suspend_state == USER_NOTIFICATION) {
printk("android sleep state %d->%d at %lld\n", g_user_suspend_state, USER_SLEEP, ktime_to_ns(ktime_get()));
g_user_suspend_state = USER_SLEEP;
queue_work(g_suspend_work_queue, &g_suspend_work);
}
}
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
static void android_power_wakeup_locked(int notification, ktime_t time)
{
int new_state = (notification == 0) ? USER_AWAKE : USER_NOTIFICATION;
if (android_power_debug_mask & ANDROID_POWER_DEBUG_USER_STATE) {
struct timespec ts;
struct rtc_time tm;
getnstimeofday(&ts);
rtc_time_to_tm(ts.tv_sec, &tm);
printk(KERN_INFO "android_power: wakeup (%d->%d) at %lld "
"(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n",
g_user_suspend_state, new_state, ktime_to_ns(time),
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}
if(new_state >= g_user_suspend_state) {
return;
}
g_user_suspend_state = new_state;
g_current_event_num++;
wake_up(&g_wait_queue);
}
static void android_power_wakeup(void)
{
unsigned long irqflags;
ktime_t ktime_now;
spin_lock_irqsave(&g_list_lock, irqflags);
ktime_now = ktime_get();
android_power_wakeup_locked(0, ktime_now);
spin_unlock_irqrestore(&g_list_lock, irqflags);
}
static void android_power_request_sleep(void)
{
unsigned long irqflags;
int already_suspended;
android_suspend_lock_t *lock, *next_lock;
if (android_power_debug_mask & ANDROID_POWER_DEBUG_USER_STATE) {
ktime_t ktime_now;
struct timespec ts;
struct rtc_time tm;
ktime_now = ktime_get();
getnstimeofday(&ts);
rtc_time_to_tm(ts.tv_sec, &tm);
printk(KERN_INFO "android_power: sleep (%d->%d) at %lld "
"(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n",
g_user_suspend_state, USER_SLEEP, ktime_to_ns(ktime_now),
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}
spin_lock_irqsave(&g_list_lock, irqflags);
already_suspended = g_user_suspend_state == USER_SLEEP;
if(!already_suspended) {
g_user_suspend_state = USER_SLEEP;
}
list_for_each_entry_safe(lock, next_lock, &g_active_full_wake_locks, link) {
#ifdef CONFIG_ANDROID_POWER_STAT
android_unlock_suspend_stat_locked(lock);
#endif
list_del(&lock->link);
list_add(&lock->link, &g_inactive_locks);
printk("android_power_suspend: aborted full wake lock %s\n", lock->name);
}
spin_unlock_irqrestore(&g_list_lock, irqflags);
queue_work(g_suspend_work_queue, &g_suspend_work);
}
void android_register_early_suspend(android_early_suspend_t *handler)
{
struct list_head *pos;
mutex_lock(&g_early_suspend_lock);
list_for_each(pos, &g_early_suspend_handlers) {
android_early_suspend_t *e = list_entry(pos, android_early_suspend_t, link);
if(e->level > handler->level)
break;
}
list_add_tail(&handler->link, pos);
mutex_unlock(&g_early_suspend_lock);
}
void android_unregister_early_suspend(android_early_suspend_t *handler)
{
mutex_lock(&g_early_suspend_lock);
list_del(&handler->link);
mutex_unlock(&g_early_suspend_lock);
}
#ifdef CONFIG_FRAMEBUFFER_CONSOLE
static int orig_fgconsole;
static void console_early_suspend(android_early_suspend_t *h)
{
acquire_console_sem();
orig_fgconsole = fg_console;
if (vc_allocate(ANDROID_SUSPEND_CONSOLE))
goto err;
if (set_console(ANDROID_SUSPEND_CONSOLE))
goto err;
release_console_sem();
if (vt_waitactive(ANDROID_SUSPEND_CONSOLE))
pr_warning("console_early_suspend: Can't switch VCs.\n");
return;
err:
pr_warning("console_early_suspend: Can't set console\n");
release_console_sem();
}
static void console_late_resume(android_early_suspend_t *h)
{
int ret;
acquire_console_sem();
ret = set_console(orig_fgconsole);
release_console_sem();
if (ret) {
pr_warning("console_late_resume: Can't set console.\n");
return;
}
if (vt_waitactive(orig_fgconsole))
pr_warning("console_late_resume: Can't switch VCs.\n");
}
static android_early_suspend_t console_early_suspend_desc = {
.level = ANDROID_EARLY_SUSPEND_LEVEL_CONSOLE_SWITCH,
.suspend = console_early_suspend,
.resume = console_late_resume,
};
#else
/* tell userspace to stop drawing, wait for it to stop */
static void stop_drawing_early_suspend(android_early_suspend_t *h)
{
int ret;
unsigned long irq_flags;
spin_lock_irqsave(&fb_state_lock, irq_flags);
fb_state = ANDROID_REQUEST_STOP_DRAWING;
spin_unlock_irqrestore(&fb_state_lock, irq_flags);
wake_up_all(&fb_state_wq);
ret = wait_event_timeout(fb_state_wq,
fb_state == ANDROID_STOPPED_DRAWING,
HZ);
if (unlikely(fb_state != ANDROID_STOPPED_DRAWING))
printk(KERN_WARNING "android_power: timeout waiting for "
"userspace to stop drawing\n");
}
/* tell userspace to start drawing */
static void start_drawing_late_resume(android_early_suspend_t *h)
{
unsigned long irq_flags;
spin_lock_irqsave(&fb_state_lock, irq_flags);
fb_state = ANDROID_DRAWING_OK;
spin_unlock_irqrestore(&fb_state_lock, irq_flags);
wake_up(&fb_state_wq);
}
static android_early_suspend_t stop_drawing_early_suspend_desc = {
.level = ANDROID_EARLY_SUSPEND_LEVEL_CONSOLE_SWITCH,
.suspend = stop_drawing_early_suspend,
.resume = start_drawing_late_resume,
};
#endif
#if ANDROID_POWER_TEST_EARLY_SUSPEND
typedef struct
{
android_early_suspend_t h;
const char *string;
} early_suspend_test_t;
static void early_suspend_test(android_early_suspend_t *h)
{
early_suspend_test_t *est = container_of(h, early_suspend_test_t, h);
printk("early suspend %s (l %d)\n", est->string, h->level);
}
static void late_resume_test(android_early_suspend_t *h)
{
early_suspend_test_t *est = container_of(h, early_suspend_test_t, h);
printk("late resume %s (l %d)\n", est->string, h->level);
}
#define EARLY_SUSPEND_TEST_ENTRY(ilevel, istring) \
{ \
.h = { \
.level = ilevel, \
.suspend = early_suspend_test, \
.resume = late_resume_test \
}, \
.string = istring \
}
static early_suspend_test_t early_suspend_tests[] = {
EARLY_SUSPEND_TEST_ENTRY(10, "1"),
EARLY_SUSPEND_TEST_ENTRY(5, "2"),
EARLY_SUSPEND_TEST_ENTRY(10, "3"),
EARLY_SUSPEND_TEST_ENTRY(15, "4"),
EARLY_SUSPEND_TEST_ENTRY(8, "5")
};
#endif
static int get_wait_timeout(int print_locks, int state, struct list_head *list_head)
{
unsigned long irqflags;
android_suspend_lock_t *lock, *next;
int max_timeout = 0;
spin_lock_irqsave(&g_list_lock, irqflags);
list_for_each_entry_safe(lock, next, list_head, link) {
if(lock->flags & ANDROID_SUSPEND_LOCK_AUTO_EXPIRE) {
int timeout = lock->expires - (int)jiffies;
if(timeout <= 0) {
lock->flags &= ~ANDROID_SUSPEND_LOCK_AUTO_EXPIRE;
#ifdef CONFIG_ANDROID_POWER_STAT
lock->stat.expire_count++;
android_unlock_suspend_stat_locked(lock);
#endif
list_del(&lock->link);
list_add(&lock->link, &g_inactive_locks);
if (android_power_debug_mask & ANDROID_POWER_DEBUG_WAKE_LOCK)
printk("expired wake lock %s\n", lock->name);
}
else {
if(timeout > max_timeout)
max_timeout = timeout;
if(print_locks)
printk("active wake lock %s, time left %d\n", lock->name, timeout);
}
}
else {
if(print_locks)
printk("active wake lock %s\n", lock->name);
}
}
if(g_user_suspend_state != state || list_empty(list_head))
max_timeout = -1;
spin_unlock_irqrestore(&g_list_lock, irqflags);
return max_timeout;
}
#ifdef CONFIG_FRAMEBUFFER_CONSOLE
static int android_power_class_suspend(struct sys_device *sdev, pm_message_t state)
{
int rv = 0;
unsigned long irqflags;
printk("android_power_suspend: enter\n");
spin_lock_irqsave(&g_list_lock, irqflags);
if(!list_empty(&g_active_partial_wake_locks)) {
printk("android_power_suspend: abort for partial wakeup\n");
rv = -EAGAIN;
}
if(g_user_suspend_state != USER_SLEEP) {
printk("android_power_suspend: abort for full wakeup\n");
rv = -EAGAIN;
}
spin_unlock_irqrestore(&g_list_lock, irqflags);
return rv;
}
static int android_power_device_suspend(struct sys_device *sdev, pm_message_t state)
{
int rv = 0;
unsigned long irqflags;
printk("android_power_device_suspend: enter\n");
spin_lock_irqsave(&g_list_lock, irqflags);
if(!list_empty(&g_active_partial_wake_locks)) {
printk("android_power_device_suspend: abort for partial wakeup\n");
rv = -EAGAIN;
}
if(g_user_suspend_state != USER_SLEEP) {
printk("android_power_device_suspend: abort for full wakeup\n");
rv = -EAGAIN;
}
spin_unlock_irqrestore(&g_list_lock, irqflags);
return rv;
}
#endif
int android_power_is_driver_suspended(void)
{
return (get_wait_timeout(0, USER_SLEEP, &g_active_partial_wake_locks) < 0) && (g_user_suspend_state == USER_SLEEP);
}
int android_power_is_low_power_idle_ok(void)
{
get_wait_timeout(0, USER_SLEEP, &g_active_idle_wake_locks);
return list_empty(&g_active_idle_wake_locks);
}
static void android_power_suspend(struct work_struct *work)
{
int entry_event_num;
int ret;
int wait = 0;
android_early_suspend_t *pos;
int print_locks = 0;
unsigned long irqflags;
while(g_user_suspend_state != USER_AWAKE) {
while(g_user_suspend_state == USER_NOTIFICATION) {
wait = get_wait_timeout(print_locks, USER_NOTIFICATION, &g_active_full_wake_locks);
if(wait < 0)
break;
if(wait)
wait_event_interruptible_timeout(g_wait_queue, get_wait_timeout(0, USER_NOTIFICATION, &g_active_full_wake_locks) != wait, wait);
}
spin_lock_irqsave(&g_list_lock, irqflags);
if(g_user_suspend_state == USER_NOTIFICATION && list_empty(&g_active_full_wake_locks)) {
printk("android sleep state %d->%d at %lld\n", g_user_suspend_state, USER_SLEEP, ktime_to_ns(ktime_get()));
g_user_suspend_state = USER_SLEEP;
}
spin_unlock_irqrestore(&g_list_lock, irqflags);
wait = 0;
if(g_user_suspend_state == USER_AWAKE) {
printk("android_power_suspend: suspend aborted\n");
return;
}
mutex_lock(&g_early_suspend_lock);
//printk("android_power_suspend: call early suspend handlers\n");
list_for_each_entry(pos, &g_early_suspend_handlers, link) {
if(pos->suspend != NULL)
pos->suspend(pos);
}
//printk("android_power_suspend: call early suspend handlers\n");
//printk("android_power_suspend: enter\n");
sys_sync();
while(g_user_suspend_state == USER_SLEEP) {
//printk("android_power_suspend: enter wait (%d)\n", wait);
if(wait) {
wait_event_interruptible_timeout(g_wait_queue, g_user_suspend_state != USER_SLEEP, wait);
wait = 0;
}
if (android_power_debug_mask & ANDROID_POWER_DEBUG_SUSPEND)
print_locks = 1;
while(1) {
wait = get_wait_timeout(print_locks, USER_SLEEP, &g_active_partial_wake_locks);
print_locks = 0;
if(wait < 0)
break;
if(wait)
wait_event_interruptible_timeout(g_wait_queue, get_wait_timeout(0, USER_SLEEP, &g_active_partial_wake_locks) != wait, wait);
else
wait_event_interruptible(g_wait_queue, get_wait_timeout(0, USER_SLEEP, &g_active_partial_wake_locks) != wait);
}
wait = 0;
//printk("android_power_suspend: exit wait\n");
entry_event_num = g_current_event_num;
if(g_user_suspend_state != USER_SLEEP)
break;
sys_sync();
if (android_power_debug_mask & ANDROID_POWER_DEBUG_SUSPEND)
printk(KERN_INFO "android_power_suspend: enter suspend\n");
ret = pm_suspend(PM_SUSPEND_MEM);
if (android_power_debug_mask & ANDROID_POWER_DEBUG_EXIT_SUSPEND) {
struct timespec ts;
struct rtc_time tm;
getnstimeofday(&ts);
rtc_time_to_tm(ts.tv_sec, &tm);
printk("android_power_suspend: 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.tv_nsec);
}
if(g_current_event_num == entry_event_num) {
if (android_power_debug_mask & ANDROID_POWER_DEBUG_SUSPEND)
printk(KERN_INFO "android_power_suspend: pm_suspend returned with no event\n");
wait = HZ / 2;
#ifdef CONFIG_ANDROID_POWER_STAT
if(g_no_wake_locks.stat.count == 0) {
g_no_wake_locks.name = "unknown_wakeups";
android_init_suspend_lock(&g_no_wake_locks);
}
g_no_wake_locks.stat.count++;
g_no_wake_locks.stat.total_time = ktime_add(
g_no_wake_locks.stat.total_time,
ktime_set(0, 500 * NSEC_PER_MSEC));
g_no_wake_locks.stat.max_time =
ktime_set(0, 500 * NSEC_PER_MSEC);
#endif
}
}
if (android_power_debug_mask & ANDROID_POWER_DEBUG_USER_STATE)
printk("android_power_suspend: done\n");
//printk("android_power_suspend: call late resume handlers\n");
list_for_each_entry_reverse(pos, &g_early_suspend_handlers, link) {
if(pos->resume != NULL)
pos->resume(pos);
}
//printk("android_power_suspend: call late resume handlers\n");
mutex_unlock(&g_early_suspend_lock);
}
}
#if 0
struct sysdev_class android_power_sysclass = {
set_kset_name("android_power"),
.suspend = android_power_class_suspend
};
static struct sysdev_class *g_android_power_sysclass = NULL;
static struct {
struct sys_device sysdev;
// omap_csmi_gsm_image_info_t *pdata;
} android_power_device = {
.sysdev = {
.id = 0,
.cls = &android_power_sysclass,
// .suspend = android_power_device_suspend
},
// .pdata = &g_gsm_image_info
};
struct sysdev_class *android_power_get_sysclass(void)
{
return g_android_power_sysclass;
}
#endif
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char * buf)
{
char * s = buf;
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
s += sprintf(s, "%d-%d-%d\n", g_user_suspend_state, list_empty(&g_active_full_wake_locks), list_empty(&g_active_partial_wake_locks));
spin_unlock_irqrestore(&g_list_lock, irqflags);
return (s - buf);
}
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, const char * buf, size_t n)
{
if(n >= strlen("standby") &&
strncmp(buf, "standby", strlen("standby")) == 0) {
android_power_request_sleep();
wait_event_interruptible(g_wait_queue, g_user_suspend_state == USER_AWAKE);
return n;
}
if(n >= strlen("wake") &&
strncmp(buf, "wake", strlen("wake")) == 0) {
android_power_wakeup();
return n;
}
printk("android_power state_store: invalid argument\n");
return -EINVAL;
}
static ssize_t request_state_show(struct kobject *kobj, struct kobj_attribute *attr, char * buf)
{
char * s = buf;
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
if(g_user_suspend_state == USER_AWAKE)
s += sprintf(s, "wake\n");
else if(g_user_suspend_state == USER_NOTIFICATION)
s += sprintf(s, "standby (w/full wake lock)\n");
else
s += sprintf(s, "standby\n");
spin_unlock_irqrestore(&g_list_lock, irqflags);
return (s - buf);
}
static ssize_t request_state_store(struct kobject *kobj, struct kobj_attribute *attr, const char * buf, size_t n)
{
if(n >= strlen("standby") &&
strncmp(buf, "standby", strlen("standby")) == 0) {
android_power_request_sleep();
return n;
}
if(n >= strlen("wake") &&
strncmp(buf, "wake", strlen("wake")) == 0) {
android_power_wakeup();
return n;
}
printk("android_power state_store: invalid argument\n");
return -EINVAL;
}
static int lookup_wake_lock_name(const char *buf, size_t n, int allocate, int *timeout)
{
int i;
int free_index = -1;
int inactive_index = -1;
int expires_index = -1;
int expires_time = INT_MAX;
char *tmp_buf[64];
char name[32];
u64 nanoseconds;
int num_arg;
if(n <= 0)
return -EINVAL;
if(n >= sizeof(tmp_buf))
return -EOVERFLOW;
if(n == sizeof(tmp_buf) - 1 && buf[n - 1] != '\0')
return -EOVERFLOW;
memcpy(tmp_buf, buf, n);
if(tmp_buf[n - 1] != '\0')
tmp_buf[n] = '\0';
num_arg = sscanf(buf, "%31s %llu", name, &nanoseconds);
if(num_arg < 1)
return -EINVAL;
if(strlen(name) >= sizeof(g_user_wake_locks[i].name_buffer))
return -EOVERFLOW;
if(timeout != NULL) {
if(num_arg > 1) {
do_div(nanoseconds, (NSEC_PER_SEC / HZ));
if(nanoseconds <= 0)
nanoseconds = 1;
*timeout = nanoseconds;
}
else
*timeout = 0;
}
for(i = 0; i < g_max_user_lockouts; i++) {
if(strcmp(g_user_wake_locks[i].name_buffer, name) == 0)
return i;
if(g_user_wake_locks[i].name_buffer[0] == '\0')
free_index = i;
else if(g_user_wake_locks[i].state == USER_WAKE_LOCK_INACTIVE)
inactive_index = i;
else if(g_user_wake_locks[i].suspend_lock.expires < expires_time)
expires_index = i;
}
if(allocate) {
if(free_index >= 0)
i = free_index;
else if(inactive_index >= 0)
i = inactive_index;
else if(expires_index >= 0) {
i = expires_index;
printk("lookup_wake_lock_name: overwriting expired lock, %s\n", g_user_wake_locks[i].name_buffer);
}
else {
i = 0;
printk("lookup_wake_lock_name: overwriting active lock, %s\n", g_user_wake_locks[i].name_buffer);
}
strcpy(g_user_wake_locks[i].name_buffer, name);
return i;
}
if (android_power_debug_mask & ANDROID_POWER_DEBUG_USER_WAKE_LOCK)
printk(KERN_INFO "lookup_wake_lock_name: %s not found\n", name);
return -EINVAL;
}
static ssize_t acquire_full_wake_lock_show(struct kobject *kobj, struct kobj_attribute *attr, char * buf)
{
int i;
char * s = buf;
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
for(i = 0; i < g_max_user_lockouts; i++) {
if(g_user_wake_locks[i].name_buffer[0] != '\0' && g_user_wake_locks[i].state == USER_WAKE_LOCK_FULL)
s += sprintf(s, "%s ", g_user_wake_locks[i].name_buffer);
}
s += sprintf(s, "\n");
spin_unlock_irqrestore(&g_list_lock, irqflags);
return (s - buf);
}
static ssize_t acquire_full_wake_lock_store(struct kobject *kobj, struct kobj_attribute *attr, const char * buf, size_t n)
{
int i;
unsigned long irqflags;
int timeout;
spin_lock_irqsave(&g_list_lock, irqflags);
i = lookup_wake_lock_name(buf, n, 1, &timeout);
if(i >= 0)
g_user_wake_locks[i].state = USER_WAKE_LOCK_FULL;
spin_unlock_irqrestore(&g_list_lock, irqflags);
if(i < 0)
return i;
if (android_power_debug_mask & ANDROID_POWER_DEBUG_USER_WAKE_LOCK)
printk(KERN_INFO "acquire_full_wake_lock_store: %s, size %d\n",
g_user_wake_locks[i].name_buffer, n);
//android_lock_partial_suspend_auto_expire(&g_user_wake_locks[i].suspend_lock, ktime_to_timespec(g_auto_off_timeout).tv_sec * HZ);
if(timeout == 0)
timeout = INT_MAX;
android_lock_partial_suspend_auto_expire(&g_user_wake_locks[i].suspend_lock, timeout);
return n;
}
static ssize_t acquire_partial_wake_lock_show(struct kobject *kobj, struct kobj_attribute *attr, char * buf)
{
int i;
char * s = buf;
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
for(i = 0; i < g_max_user_lockouts; i++) {
if(g_user_wake_locks[i].name_buffer[0] != '\0' && g_user_wake_locks[i].state == USER_WAKE_LOCK_PARTIAL)
s += sprintf(s, "%s ", g_user_wake_locks[i].name_buffer);
}
s += sprintf(s, "\n");
spin_unlock_irqrestore(&g_list_lock, irqflags);
return (s - buf);
}
static ssize_t acquire_partial_wake_lock_store(struct kobject *kobj, struct kobj_attribute *attr, const char * buf, size_t n)
{
int i;
unsigned long irqflags;
int timeout;
spin_lock_irqsave(&g_list_lock, irqflags);
i = lookup_wake_lock_name(buf, n, 1, &timeout);
if(i >= 0)
g_user_wake_locks[i].state = USER_WAKE_LOCK_PARTIAL;
spin_unlock_irqrestore(&g_list_lock, irqflags);
if(i < 0)
return 0;
if (android_power_debug_mask & ANDROID_POWER_DEBUG_USER_WAKE_LOCK)
printk(KERN_INFO "acquire_partial_wake_lock_store: %s, "
"size %d\n", g_user_wake_locks[i].name_buffer, n);
if(timeout)
android_lock_suspend_auto_expire(&g_user_wake_locks[i].suspend_lock, timeout);
else
android_lock_suspend(&g_user_wake_locks[i].suspend_lock);
return n;
}
static ssize_t release_wake_lock_show(struct kobject *kobj, struct kobj_attribute *attr, char * buf)
{
int i;
char * s = buf;
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
for(i = 0; i < g_max_user_lockouts; i++) {
if(g_user_wake_locks[i].name_buffer[0] != '\0' && g_user_wake_locks[i].state == USER_WAKE_LOCK_INACTIVE)
s += sprintf(s, "%s ", g_user_wake_locks[i].name_buffer);
}
s += sprintf(s, "\n");
spin_unlock_irqrestore(&g_list_lock, irqflags);
return (s - buf);
}
static ssize_t release_wake_lock_store(struct kobject *kobj, struct kobj_attribute *attr, const char * buf, size_t n)
{
int i;
unsigned long irqflags;
spin_lock_irqsave(&g_list_lock, irqflags);
i = lookup_wake_lock_name(buf, n, 1, NULL);
if(i >= 0) {
g_user_wake_locks[i].state = USER_WAKE_LOCK_INACTIVE;
}
spin_unlock_irqrestore(&g_list_lock, irqflags);
if(i < 0)
return i;
if (android_power_debug_mask & ANDROID_POWER_DEBUG_USER_WAKE_LOCK)
printk(KERN_INFO "release_wake_lock_store: %s, size %d\n",
g_user_wake_locks[i].name_buffer, n);
android_unlock_suspend(&g_user_wake_locks[i].suspend_lock);
return n;
}
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
static ssize_t wait_for_fb_sleep_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
char * s = buf;
int ret;
ret = wait_event_interruptible(fb_state_wq,
fb_state != ANDROID_DRAWING_OK);
if (ret && fb_state == ANDROID_DRAWING_OK)
return ret;
else
s += sprintf(buf, "sleeping");
return (s - buf);
}
static ssize_t wait_for_fb_wake_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
char * s = buf;
int ret;
unsigned long irq_flags;
spin_lock_irqsave(&fb_state_lock, irq_flags);
if (fb_state == ANDROID_REQUEST_STOP_DRAWING) {
fb_state = ANDROID_STOPPED_DRAWING;
wake_up(&fb_state_wq);
}
spin_unlock_irqrestore(&fb_state_lock, irq_flags);
ret = wait_event_interruptible(fb_state_wq,
fb_state == ANDROID_DRAWING_OK);
if (ret && fb_state != ANDROID_DRAWING_OK)
return ret;
else
s += sprintf(buf, "awake");
return (s - buf);
}
#endif
#define android_power_attr(_name) \
static struct kobj_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0664, \
}, \
.show = _name##_show, \
.store = _name##_store, \
}
#define android_power_ro_attr(_name) \
static struct kobj_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0444, \
}, \
.show = _name##_show, \
.store = NULL, \
}
android_power_attr(state);
android_power_attr(request_state);
android_power_attr(acquire_full_wake_lock);
android_power_attr(acquire_partial_wake_lock);
android_power_attr(release_wake_lock);
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
android_power_ro_attr(wait_for_fb_sleep);
android_power_ro_attr(wait_for_fb_wake);
#endif
static struct attribute * g[] = {
&state_attr.attr,
&request_state_attr.attr,
&acquire_full_wake_lock_attr.attr,
&acquire_partial_wake_lock_attr.attr,
&release_wake_lock_attr.attr,
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
&wait_for_fb_sleep_attr.attr,
&wait_for_fb_wake_attr.attr,
#endif
NULL,
};
static struct attribute_group attr_group = {
.attrs = g,
};
#if 0
// test code when there is no platform suspend
static android_suspend_lock_t test_pm_ops_suspend_lock = {
.name = "test_pm_ops"
};
int test_pm_op_enter(suspend_state_t state)
{
printk("test_pm_op_enter reached\n");
android_lock_suspend(&test_pm_ops_suspend_lock);
printk("test_pm_op_enter returned\n");
return 0;
}
void test_pm_ops_late_resume_handler(android_early_suspend_t *h)
{
printk("test_pm_ops_late_resume_handler reached\n");
android_unlock_suspend(&test_pm_ops_suspend_lock);
printk("test_pm_ops_late_resume_handler returned\n");
}
static struct pm_ops test_pm_ops = {
.enter = test_pm_op_enter
};
static android_early_suspend_t test_pm_ops_early_suspend_handler = {
.resume = test_pm_ops_late_resume_handler
};
#endif
static int __init android_power_init(void)
{
int ret;
int i;
#if 0
if(pm_ops == NULL) {
printk("android_power_init no pm_ops, installing test code\n");
pm_set_ops(&test_pm_ops);
android_init_suspend_lock(&test_pm_ops_suspend_lock);
android_register_early_suspend(&test_pm_ops_early_suspend_handler);
}
#endif
#ifdef CONFIG_ANDROID_POWER_STAT
g_deleted_wake_locks.stat.count = 0;
#endif
init_waitqueue_head(&g_wait_queue);
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
init_waitqueue_head(&fb_state_wq);
fb_state = ANDROID_DRAWING_OK;
#endif
g_user_wake_locks = kzalloc(sizeof(*g_user_wake_locks) * g_max_user_lockouts, GFP_KERNEL);
if(g_user_wake_locks == NULL) {
ret = -ENOMEM;
goto err1;
}
for(i = 0; i < g_max_user_lockouts; i++) {
g_user_wake_locks[i].suspend_lock.name = g_user_wake_locks[i].name_buffer;
android_init_suspend_lock(&g_user_wake_locks[i].suspend_lock);
}
g_suspend_work_queue = create_workqueue("suspend");
if(g_suspend_work_queue == NULL) {
ret = -ENOMEM;
goto err2;
}
android_power_kobj = kobject_create_and_add("android_power", NULL);
if (android_power_kobj == NULL) {
printk("android_power_init: subsystem_register failed\n");
ret = -ENOMEM;
goto err3;
}
ret = sysfs_create_group(android_power_kobj, &attr_group);
if(ret) {
printk("android_power_init: sysfs_create_group failed\n");
goto err4;
}
#ifdef CONFIG_ANDROID_POWER_STAT
create_proc_read_entry("wakelocks", S_IRUGO, NULL, wakelocks_read_proc, NULL);
#endif
#if ANDROID_POWER_TEST_EARLY_SUSPEND
{
int i;
for(i = 0; i < sizeof(early_suspend_tests) / sizeof(early_suspend_tests[0]); i++)
android_register_early_suspend(&early_suspend_tests[i].h);
}
#endif
#ifdef CONFIG_FRAMEBUFFER_CONSOLE
android_register_early_suspend(&console_early_suspend_desc);
#else
android_register_early_suspend(&stop_drawing_early_suspend_desc);
#endif
#if 0
ret = sysdev_class_register(&android_power_sysclass);
if(ret) {
printk("android_power_init: sysdev_class_register failed\n");
goto err1;
}
ret = sysdev_register(&android_power_device.sysdev);
if(ret < 0)
goto err2;
g_android_power_sysclass = &android_power_sysclass;
#endif
return 0;
//err2:
// sysdev_class_unregister(&android_power_sysclass);
err4:
kobject_del(android_power_kobj);
err3:
destroy_workqueue(g_suspend_work_queue);
err2:
for(i = 0; i < g_max_user_lockouts; i++) {
android_uninit_suspend_lock(&g_user_wake_locks[i].suspend_lock);
}
kfree(g_user_wake_locks);
err1:
return ret;
}
static void __exit android_power_exit(void)
{
int i;
// g_android_power_sysclass = NULL;
// sysdev_unregister(&android_power_device.sysdev);
// sysdev_class_unregister(&android_power_sysclass);
#ifdef CONFIG_FRAMEBUFFER_CONSOLE
android_unregister_early_suspend(&console_early_suspend_desc);
#else
android_unregister_early_suspend(&stop_drawing_early_suspend_desc);
#endif
#ifdef CONFIG_ANDROID_POWER_STAT
remove_proc_entry("wakelocks", NULL);
#endif
sysfs_remove_group(android_power_kobj, &attr_group);
kobject_del(android_power_kobj);
destroy_workqueue(g_suspend_work_queue);
for(i = 0; i < g_max_user_lockouts; i++) {
android_uninit_suspend_lock(&g_user_wake_locks[i].suspend_lock);
}
kfree(g_user_wake_locks);
}
core_initcall(android_power_init);
module_exit(android_power_exit);
//EXPORT_SYMBOL(android_power_get_sysclass);
EXPORT_SYMBOL(android_init_suspend_lock);
EXPORT_SYMBOL(android_uninit_suspend_lock);
EXPORT_SYMBOL(android_lock_suspend);
EXPORT_SYMBOL(android_lock_suspend_auto_expire);
EXPORT_SYMBOL(android_unlock_suspend);
EXPORT_SYMBOL(android_power_wakeup);
EXPORT_SYMBOL(android_register_early_suspend);
EXPORT_SYMBOL(android_unregister_early_suspend);