Skip to content

Instantly share code, notes, and snippets.

@syndtr
Created March 12, 2012 08:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save syndtr/2020710 to your computer and use it in GitHub Desktop.
Save syndtr/2020710 to your computer and use it in GitHub Desktop.
Android PM driver. It is utilize linux wakeup_source and also provide early suspend like implementation.
diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig
index 05f1105..b18990a 100644
--- a/drivers/staging/android/Kconfig
+++ b/drivers/staging/android/Kconfig
@@ -81,6 +81,13 @@ config ANDROID_INTF_ALARM_DEV
elapsed realtime, and a non-wakeup alarm on the monotonic clock.
Also exports the alarm interface to user-space.
+config ANDROID_PM
+ bool "Android PM support"
+ depends on PM && PM_SLEEP
+ default n
+ help
+ Enable the Android PM support.
+
endif # if ANDROID
endmenu
diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile
index 848164a..377a0f6 100644
--- a/drivers/staging/android/Makefile
+++ b/drivers/staging/android/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_ANDROID_TIMED_GPIO) += timed_gpio.o
obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o
obj-$(CONFIG_ANDROID_SWITCH) += switch/
obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o
+obj-$(CONFIG_ANDROID_PM) += android_pm.o
obj-$(CONFIG_PERSISTENT_TRACER) += trace_persistent.o
CFLAGS_REMOVE_trace_persistent.o = -pg
diff --git a/drivers/staging/android/android_pm.c b/drivers/staging/android/android_pm.c
new file mode 100644
index 0000000..bd23383
--- /dev/null
+++ b/drivers/staging/android/android_pm.c
@@ -0,0 +1,536 @@
+/*
+ * Android PM driver
+ *
+ * Copyright (c) 2012 Suryandaru Triandana <syndtr@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/fs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/atomic.h>
+#include <linux/list.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/suspend.h>
+#include <linux/platform_device.h>
+#include <linux/jiffies.h>
+
+#include "android_pm.h"
+
+enum {
+ STATE_RESUMED = 1,
+ STATE_RESUMING,
+ STATE_SUSPENDING,
+ STATE_SUSPENDED
+};
+
+static struct android_pm {
+ struct device *dev;
+ struct kobject *kobj;
+ struct wakeup_source *ws;
+ struct mutex mutex_pm;
+ struct mutex mutex_ws;
+ atomic_t state;
+ struct workqueue_struct *wq;
+ struct work_struct work;
+ struct workqueue_struct *suspend_wq;
+ struct work_struct suspend_work;
+ wait_queue_head_t wait;
+ wait_queue_head_t wait_event;
+ atomic_t wait_event_pending;
+ struct list_head devices;
+ struct list_head suspend_head;
+ struct list_head resume_head;
+ struct rb_root user_ws;
+} android_pm_state;
+
+struct android_pm_dev {
+ struct device *dev;
+ const struct android_pm_ops *ops;
+ struct list_head list;
+ struct list_head work_list;
+};
+
+struct android_pm_user_ws {
+ char *name;
+ struct rb_node node;
+ struct wakeup_source *ws;
+};
+
+static struct android_pm_user_ws *user_ws_lookup(struct android_pm *pm,
+ const char *name)
+{
+ struct rb_root *root = &(pm->user_ws);
+ struct rb_node **new = &(root->rb_node), *parent = NULL;
+ struct android_pm_user_ws *ws;
+ int result;
+
+ mutex_lock(&pm->mutex_ws);
+
+ /* Figure out where to put new node */
+ while (*new) {
+ ws = container_of(*new, struct android_pm_user_ws, node);
+ result = strcmp(name, ws->name + 2);
+
+ parent = *new;
+ if (result < 0)
+ new = &((*new)->rb_left);
+ else if (result > 0)
+ new = &((*new)->rb_right);
+ else
+ goto end;
+ }
+
+ ws = kmalloc(sizeof(struct android_pm_user_ws) + strlen(name) + 3,
+ GFP_KERNEL);
+ if (!ws)
+ goto end;
+
+ ws->name = ((char *)ws + sizeof(struct android_pm_user_ws));
+ sprintf(ws->name, "u/%s", name);
+ ws->ws = wakeup_source_register(ws->name);
+ if (!ws->ws) {
+ kfree(ws);
+ ws = NULL;
+ goto end;
+ }
+
+ /* Add new node and rebalance tree. */
+ rb_init_node(&ws->node);
+ rb_link_node(&ws->node, parent, new);
+ rb_insert_color(&ws->node, root);
+
+end:
+ mutex_unlock(&pm->mutex_ws);
+ return ws;
+}
+
+static void user_ws_free(struct android_pm *pm, struct android_pm_user_ws *ws)
+{
+ mutex_lock(&pm->mutex_ws);
+ wakeup_source_unregister(ws->ws);
+ rb_erase(&ws->node, &pm->user_ws);
+ kfree(ws);
+ mutex_unlock(&pm->mutex_ws);
+}
+
+static void android_pm_schedule(struct android_pm *pm)
+{
+ __pm_stay_awake(pm->ws);
+ queue_work(pm->wq, &pm->work);
+}
+
+int android_pm_enable(struct device *dev, const struct android_pm_ops *ops)
+{
+ struct android_pm *pm = &android_pm_state;
+ struct android_pm_dev *d;
+ int ret = 0;
+
+ if (!dev)
+ return -EINVAL;
+
+ mutex_lock(&pm->mutex_pm);
+
+ list_for_each_entry(d, &pm->devices, list) {
+ if (d->dev == dev)
+ goto done;
+ }
+
+ d = kmalloc(sizeof(struct android_pm_dev), GFP_KERNEL);
+ if (d == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ d->dev = dev;
+ d->ops = ops;
+
+ list_add(&d->list, &pm->devices);
+ list_add(&d->work_list, &pm->suspend_head);
+ if (atomic_cmpxchg(&pm->state, STATE_SUSPENDED, STATE_SUSPENDING) == STATE_SUSPENDED)
+ android_pm_schedule(pm);
+
+done:
+ mutex_unlock(&pm->mutex_pm);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(android_pm_enable);
+
+int android_pm_disable(struct device *dev)
+{
+ struct android_pm *pm = &android_pm_state;
+ struct android_pm_dev *d;
+ int ret = -ENODEV;
+
+ mutex_lock(&pm->mutex_pm);
+
+ list_for_each_entry(d, &pm->devices, list) {
+ if (d->dev == dev) {
+ list_del(&d->list);
+ list_del(&d->work_list);
+ kfree(d);
+ ret = 0;
+ goto done;
+ }
+ }
+
+done:
+ mutex_unlock(&pm->mutex_pm);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(android_pm_disable);
+
+static void do_one_suspend(struct android_pm *pm,
+ struct android_pm_dev *d)
+{
+ int ret;
+ if (d->ops && d->ops->suspend) {
+ ret = d->ops->suspend(d->dev);
+ if (ret < 0)
+ pr_warn("android_pm: failed to suspend '%s' ret=%d\n",
+ dev_name(d->dev), ret);
+ }
+ list_move(&d->work_list, &pm->resume_head);
+}
+
+static void do_one_resume(struct android_pm *pm,
+ struct android_pm_dev *d)
+{
+ int ret;
+ if (d->ops && d->ops->resume) {
+ ret = d->ops->resume(d->dev);
+ if (ret < 0)
+ pr_warn("android_pm: failed to resume '%s' ret=%d\n",
+ dev_name(d->dev), ret);
+ }
+ list_move(&d->work_list, &pm->suspend_head);
+}
+
+static void android_pm_work(struct work_struct *work)
+{
+ struct android_pm *pm = &android_pm_state;
+ struct android_pm_dev *d;
+ int state;
+
+ wait_event(pm->wait_event, !atomic_read(&pm->wait_event_pending));
+
+ mutex_lock(&pm->mutex_pm);
+ while (1) {
+ if ((state = atomic_read(&pm->state)) == STATE_SUSPENDING) {
+ if (list_empty(&pm->suspend_head)) {
+ if (atomic_cmpxchg(&pm->state, STATE_SUSPENDING, STATE_SUSPENDED) == STATE_SUSPENDING)
+ break;
+ else
+ continue;
+ }
+
+ d = list_first_entry(&pm->suspend_head, struct android_pm_dev, work_list);
+ do_one_suspend(pm, d);
+ } else if (state == STATE_RESUMING) {
+ if (list_empty(&pm->resume_head)) {
+ if (atomic_cmpxchg(&pm->state, STATE_RESUMING, STATE_RESUMED) == STATE_RESUMING) {
+ wake_up(&pm->wait);
+ break;
+ } else
+ continue;
+ }
+
+ d = list_first_entry(&pm->resume_head, struct android_pm_dev, work_list);
+ do_one_resume(pm, d);
+ } else
+ break;
+ }
+ mutex_unlock(&pm->mutex_pm);
+
+ __pm_relax(pm->ws);
+ queue_work(pm->suspend_wq, &pm->suspend_work);
+}
+
+static void pm_suspend_work(struct work_struct *work)
+{
+ struct android_pm *pm = &android_pm_state;
+ unsigned int count;
+ int ret;
+
+ if (atomic_read(&pm->state) != STATE_SUSPENDED)
+ return;
+
+ ret = pm_get_wakeup_count(&count);
+ if (atomic_read(&pm->state) == STATE_SUSPENDED) {
+ if (ret) {
+ pm_save_wakeup_count(count);
+ pm_suspend(PM_SUSPEND_MEM);
+ } else
+ queue_work(pm->suspend_wq, &pm->suspend_work);
+ }
+}
+
+static ssize_t android_pm_state_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ struct android_pm *pm = &android_pm_state;
+ const char *state_str = "unknown";
+
+ switch (atomic_read(&pm->state)) {
+ case STATE_RESUMED:
+ state_str = "resumed";
+ break;
+ case STATE_RESUMING:
+ state_str = "resuming";
+ break;
+ case STATE_SUSPENDED:
+ state_str = "suspended";
+ break;
+ case STATE_SUSPENDING:
+ state_str = "suspending";
+ break;
+ default:
+ break;
+ }
+
+ return sprintf(buf, "%s\n", state_str);
+}
+
+static ssize_t android_pm_state_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct android_pm *pm = &android_pm_state;
+
+ if (count < 1)
+ return -EINVAL;
+
+ switch (*buf) {
+ case 'r':
+ if (atomic_cmpxchg(&pm->state, STATE_SUSPENDED, STATE_RESUMING) == STATE_SUSPENDED)
+ android_pm_schedule(pm);
+ else
+ atomic_cmpxchg(&pm->state, STATE_SUSPENDING, STATE_RESUMING);
+ break;
+ case 's':
+ if (atomic_cmpxchg(&pm->state, STATE_RESUMED, STATE_SUSPENDING) == STATE_RESUMED) {
+ wake_up(&pm->wait);
+ android_pm_schedule(pm);
+ } else
+ atomic_cmpxchg(&pm->state, STATE_RESUMING, STATE_SUSPENDING);
+ break;
+ default:
+ pr_err("android_pm: unknown state '%c'\n", *buf);
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t android_pm_wait(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct android_pm *pm = &android_pm_state;
+ int state, ret = 0;
+
+ if (count < 1)
+ return -EINVAL;
+
+ switch (*buf) {
+ case 'r':
+ if (atomic_cmpxchg(&pm->wait_event_pending, 1, 0))
+ wake_up(&pm->wait_event);
+ ret = wait_event_interruptible(pm->wait,
+ (atomic_read(&pm->state) == STATE_RESUMED));
+ break;
+ case 's':
+ atomic_set(&pm->wait_event_pending, 1);
+ ret = wait_event_interruptible(pm->wait,
+ ((state = atomic_read(&pm->state)) == STATE_SUSPENDING ||
+ state == STATE_SUSPENDED));
+ break;
+ default:
+ pr_err("android_pm: unknown wait type '%c'\n", *buf);
+ return -EINVAL;
+ }
+
+ return ((ret < 0) ? ret : count);
+}
+
+static ssize_t android_pm_wakeup_ref(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct android_pm *pm = &android_pm_state;
+ struct android_pm_user_ws *ws;
+
+ if (count == 0 || !buf)
+ return -EINVAL;
+
+ ws = user_ws_lookup(pm, buf);
+ if (!ws)
+ return -ENOMEM;
+
+ __pm_stay_awake(ws->ws);
+
+ return count;
+}
+
+static ssize_t android_pm_wakeup_unref(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct android_pm *pm = &android_pm_state;
+ struct android_pm_user_ws *ws;
+
+ if (count == 0 || !buf)
+ return -EINVAL;
+
+ ws = user_ws_lookup(pm, buf);
+ if (!ws)
+ return -ENOMEM;
+
+ __pm_relax(ws->ws);
+
+ return count;
+}
+
+static struct kobj_attribute state_attribute =
+ __ATTR(state, 0666, android_pm_state_show, android_pm_state_store);
+static struct kobj_attribute wait_attribute =
+ __ATTR(wait, 0666, NULL, android_pm_wait);
+static struct kobj_attribute wakeup_ref_attribute =
+ __ATTR(wakeup_ref, 0666, NULL, android_pm_wakeup_ref);
+static struct kobj_attribute wakeup_unref_attribute =
+ __ATTR(wakeup_unref, 0666, NULL, android_pm_wakeup_unref);
+
+static struct attribute *attrs[] = {
+ &state_attribute.attr,
+ &wait_attribute.attr,
+ &wakeup_ref_attribute.attr,
+ &wakeup_unref_attribute.attr,
+ NULL,
+};
+
+static struct attribute_group attr_group = {
+ .attrs = attrs,
+};
+
+static int __devinit android_pm_probe(struct platform_device *pdev)
+{
+ struct android_pm *pm = &android_pm_state;
+ int ret;
+
+ pm->kobj = kobject_create_and_add("android_pm", power_kobj);
+ if (!pm->kobj)
+ return -ENOMEM;
+
+ ret = sysfs_create_group(pm->kobj, &attr_group);
+ if (ret)
+ goto err;
+
+ pm->ws = wakeup_source_register("android_pm");
+ if (!pm->ws) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ pm->wq = create_singlethread_workqueue("android_pm");
+ if (!pm->wq) {
+ ret = -ENOMEM;
+ goto err_ws;
+ }
+
+ pm->suspend_wq = create_singlethread_workqueue("android_pm_suspend");
+ if (!pm->suspend_wq) {
+ ret = -ENOMEM;
+ goto err_wq;
+ }
+
+ mutex_init(&pm->mutex_pm);
+ mutex_init(&pm->mutex_ws);
+ atomic_set(&pm->state, STATE_RESUMED);
+ INIT_LIST_HEAD(&pm->devices);
+ INIT_LIST_HEAD(&pm->suspend_head);
+ INIT_LIST_HEAD(&pm->resume_head);
+ INIT_WORK(&pm->work, android_pm_work);
+ INIT_WORK(&pm->suspend_work, pm_suspend_work);
+ init_waitqueue_head(&pm->wait);
+ init_waitqueue_head(&pm->wait_event);
+ atomic_set(&pm->wait_event_pending, 0);
+
+ dev_set_drvdata(&pdev->dev, pm);
+
+ return 0;
+
+err_wq:
+ destroy_workqueue(pm->wq);
+err_ws:
+ wakeup_source_unregister(pm->ws);
+err:
+ kobject_put(pm->kobj);
+ return ret;
+}
+
+static void android_pm_complete(struct device *dev)
+{
+ struct android_pm *pm = &android_pm_state;
+
+ __pm_wakeup_event(pm->ws, 200);
+ queue_work(pm->suspend_wq, &pm->suspend_work);
+}
+
+static const struct dev_pm_ops android_pm_ops = {
+ .complete = android_pm_complete,
+};
+
+struct platform_driver android_pm_driver = {
+ .probe = android_pm_probe,
+ .driver = {
+ .name = "android_pm",
+ .pm = &android_pm_ops,
+ .suppress_bind_attrs = true,
+ },
+};
+
+struct platform_device android_pm_pdev = {
+ .name = "android_pm",
+};
+
+static int __init android_pm_init(void)
+{
+ int ret;
+
+ ret = platform_device_register(&android_pm_pdev);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&android_pm_driver);
+ if (ret) {
+ platform_device_unregister(&android_pm_pdev);
+ return ret;
+ }
+
+ return 0;
+}
+postcore_initcall(android_pm_init);
diff --git a/drivers/staging/android/android_pm.h b/drivers/staging/android/android_pm.h
new file mode 100644
index 0000000..a7d4163
--- /dev/null
+++ b/drivers/staging/android/android_pm.h
@@ -0,0 +1,46 @@
+
+#ifndef _ANDROID_PM_H
+#define _ANDROID_PM_H
+
+#ifdef CONFIG_ANDROID_PM
+
+struct android_pm_ops {
+ int (*suspend)(struct device *dev);
+ int (*resume)(struct device *dev);
+};
+
+#define SET_ANDROID_PM_SLEEP_OPS(suspend_fn, resume_fn) \
+ .suspend = suspend_fn, \
+ .resume = resume_fn,
+
+#define ANDROID_PM_OPS(name, suspend_fn, resume_fn) \
+const struct android_pm_ops name = { \
+ SET_ANDROID_PM_SLEEP_OPS(suspend_fn, resume_fn) \
+}
+#define STATIC_ANDROID_PM_OPS(name, suspend_fn, resume_fn) \
+ static ANDROID_PM_OPS(name, suspend_fn, resume_fn)
+
+#define ANDROID_PM_OPS_PROTO(name) \
+ const struct android_pm_ops name
+#define STATIC_ANDROID_PM_OPS_PROTO(name) \
+ static ANDROID_PM_OPS_PROTO(name)
+
+extern int android_pm_enable(struct device *dev, const struct android_pm_ops *ops);
+extern int android_pm_disable(struct device *dev);
+
+#else
+
+#define SET_ANDROID_PM_SLEEP_OPS(suspend_fn, resume_fn)
+
+#define ANDROID_PM_OPS(name, suspend_fn, resume_fn)
+#define STATIC_ANDROID_PM_OPS(name, suspend_fn, resume_fn)
+
+#define ANDROID_PM_OPS_PROTO(name)
+#define STATIC_ANDROID_PM_OPS_PROTO(name)
+
+static inline int android_pm_enable(struct device *dev, const struct android_pm_ops *ops) { return 0; }
+static inline int android_pm_disable(struct device *dev) { return 0; }
+
+#endif
+
+#endif // _ANDROID_PM_H
\ No newline at end of file
@InstigatorX
Copy link

Have you made updates to this? How has it worked out for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment