diff options
author | Todd Poynor <toddpoynor@google.com> | 2011-09-26 20:35:30 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:39:09 -0800 |
commit | 002a954c0ce004f9553c90e1945a5388966abeed (patch) | |
tree | 91b7f9f2396166dcbc0347d95739463ec6067ac2 /drivers | |
parent | 64d8eb004f4179c4d97cd4f6293d135a32d4cc05 (diff) |
usb: otg: Temporarily grab wakelock on charger and disconnect events
Change-Id: If995d4af4adcb08e8369009483f2956ad9627267
Signed-off-by: Todd Poynor <toddpoynor@google.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/otg/otg-wakelock.c | 133 |
1 files changed, 56 insertions, 77 deletions
diff --git a/drivers/usb/otg/otg-wakelock.c b/drivers/usb/otg/otg-wakelock.c index 993162674836..2f11472dd2b3 100644 --- a/drivers/usb/otg/otg-wakelock.c +++ b/drivers/usb/otg/otg-wakelock.c @@ -21,13 +21,15 @@ #include <linux/spinlock.h> #include <linux/usb/otg.h> +#define TEMPORARY_HOLD_TIME 2000 + static bool enabled = true; static struct otg_transceiver *otgwl_xceiv; static struct notifier_block otgwl_nb; /* * otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the - * locked field is updated to match. + * held field is updated to match. */ static DEFINE_SPINLOCK(otgwl_spinlock); @@ -39,51 +41,62 @@ static DEFINE_SPINLOCK(otgwl_spinlock); struct otgwl_lock { char name[40]; struct wake_lock wakelock; - bool locked; + bool held; }; /* - * VBUS present lock. + * VBUS present lock. Also used as a timed lock on charger + * connect/disconnect and USB host disconnect, to allow the system + * to react to the change in power. */ static struct otgwl_lock vbus_lock; -static void otgwl_grab(struct otgwl_lock *lock) +static void otgwl_hold(struct otgwl_lock *lock) { - if (!lock->locked) { + if (!lock->held) { wake_lock(&lock->wakelock); - lock->locked = true; + lock->held = true; } } +static void otgwl_temporary_hold(struct otgwl_lock *lock) +{ + wake_lock_timeout(&lock->wakelock, + msecs_to_jiffies(TEMPORARY_HOLD_TIME)); + lock->held = false; +} + static void otgwl_drop(struct otgwl_lock *lock) { - if (lock->locked) { + if (lock->held) { wake_unlock(&lock->wakelock); - lock->locked = false; + lock->held = false; } } -static int otgwl_otg_notifications(struct notifier_block *nb, - unsigned long event, void *unused) +static void otgwl_handle_event(unsigned long event) { unsigned long irqflags; - if (!enabled) - return NOTIFY_OK; - spin_lock_irqsave(&otgwl_spinlock, irqflags); + if (!enabled) { + otgwl_drop(&vbus_lock); + spin_unlock_irqrestore(&otgwl_spinlock, irqflags); + return; + } + switch (event) { case USB_EVENT_VBUS: case USB_EVENT_ENUMERATED: - otgwl_grab(&vbus_lock); + otgwl_hold(&vbus_lock); break; case USB_EVENT_NONE: case USB_EVENT_ID: case USB_EVENT_CHARGER: - otgwl_drop(&vbus_lock); + otgwl_temporary_hold(&vbus_lock); break; default: @@ -91,71 +104,25 @@ static int otgwl_otg_notifications(struct notifier_block *nb, } spin_unlock_irqrestore(&otgwl_spinlock, irqflags); - return NOTIFY_OK; -} - -static void sync_with_xceiv_state(void) -{ - if ((otgwl_xceiv->last_event == USB_EVENT_VBUS) || - (otgwl_xceiv->last_event == USB_EVENT_ENUMERATED)) - otgwl_grab(&vbus_lock); - else - otgwl_drop(&vbus_lock); } -static int init_for_xceiv(void) +static int otgwl_otg_notifications(struct notifier_block *nb, + unsigned long event, void *unused) { - int rv; - - if (!otgwl_xceiv) { - otgwl_xceiv = otg_get_transceiver(); - - if (!otgwl_xceiv) { - pr_err("%s: No OTG transceiver found\n", __func__); - return -ENODEV; - } - - snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s", - dev_name(otgwl_xceiv->dev)); - wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND, - vbus_lock.name); - - rv = otg_register_notifier(otgwl_xceiv, &otgwl_nb); - - if (rv) { - pr_err("%s: otg_register_notifier on transceiver %s" - " failed\n", __func__, - dev_name(otgwl_xceiv->dev)); - otgwl_xceiv = NULL; - wake_lock_destroy(&vbus_lock.wakelock); - return rv; - } - } - - return 0; + otgwl_handle_event(event); + return NOTIFY_OK; } static int set_enabled(const char *val, const struct kernel_param *kp) { - unsigned long irqflags; int rv = param_set_bool(val, kp); if (rv) return rv; - rv = init_for_xceiv(); - - if (rv) - return rv; - - spin_lock_irqsave(&otgwl_spinlock, irqflags); - - if (enabled) - sync_with_xceiv_state(); - else - otgwl_drop(&vbus_lock); + if (otgwl_xceiv) + otgwl_handle_event(otgwl_xceiv->last_event); - spin_unlock_irqrestore(&otgwl_spinlock, irqflags); return 0; } @@ -169,22 +136,34 @@ MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present"); static int __init otg_wakelock_init(void) { - unsigned long irqflags; + int ret; - otgwl_nb.notifier_call = otgwl_otg_notifications; + otgwl_xceiv = otg_get_transceiver(); - if (!init_for_xceiv()) { - spin_lock_irqsave(&otgwl_spinlock, irqflags); + if (!otgwl_xceiv) { + pr_err("%s: No OTG transceiver found\n", __func__); + return -ENODEV; + } - if (enabled) - sync_with_xceiv_state(); + snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s", + dev_name(otgwl_xceiv->dev)); + wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND, + vbus_lock.name); - spin_unlock_irqrestore(&otgwl_spinlock, irqflags); - } else { - enabled = false; + otgwl_nb.notifier_call = otgwl_otg_notifications; + ret = otg_register_notifier(otgwl_xceiv, &otgwl_nb); + + if (ret) { + pr_err("%s: otg_register_notifier on transceiver %s" + " failed\n", __func__, + dev_name(otgwl_xceiv->dev)); + otgwl_xceiv = NULL; + wake_lock_destroy(&vbus_lock.wakelock); + return ret; } - return 0; + otgwl_handle_event(otgwl_xceiv->last_event); + return ret; } late_initcall(otg_wakelock_init); |