/* 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 #include #include //#include #include #include #include #include #include #include #include #include #include #include #include // sys_sync #include #include #include #include #ifdef CONFIG_ANDROID_POWER_STAT #include #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);