1/* 2 * otg-wakelock.c 3 * 4 * Copyright (C) 2011 Google, Inc. 5 * 6 * This software is licensed under the terms of the GNU General Public 7 * License version 2, as published by the Free Software Foundation, and 8 * may be copied, distributed, and modified under those terms. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 */ 16 17#include <linux/kernel.h> 18#include <linux/device.h> 19#include <linux/err.h> 20#include <linux/module.h> 21#include <linux/notifier.h> 22#include <linux/wakelock.h> 23#include <linux/spinlock.h> 24#include <linux/usb/otg.h> 25 26#define TEMPORARY_HOLD_TIME 2000 27 28static bool enabled = true; 29static struct usb_phy *otgwl_xceiv; 30static struct notifier_block otgwl_nb; 31 32/* 33 * otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the 34 * held field is updated to match. 35 */ 36 37static DEFINE_SPINLOCK(otgwl_spinlock); 38 39/* 40 * Only one lock, but since these 3 fields are associated with each other... 41 */ 42 43struct otgwl_lock { 44 char name[40]; 45 struct wake_lock wakelock; 46 bool held; 47}; 48 49/* 50 * VBUS present lock. Also used as a timed lock on charger 51 * connect/disconnect and USB host disconnect, to allow the system 52 * to react to the change in power. 53 */ 54 55static struct otgwl_lock vbus_lock; 56 57static void otgwl_hold(struct otgwl_lock *lock) 58{ 59 if (!lock->held) { 60 wake_lock(&lock->wakelock); 61 lock->held = true; 62 } 63} 64 65static void otgwl_temporary_hold(struct otgwl_lock *lock) 66{ 67 wake_lock_timeout(&lock->wakelock, 68 msecs_to_jiffies(TEMPORARY_HOLD_TIME)); 69 lock->held = false; 70} 71 72static void otgwl_drop(struct otgwl_lock *lock) 73{ 74 if (lock->held) { 75 wake_unlock(&lock->wakelock); 76 lock->held = false; 77 } 78} 79 80static void otgwl_handle_event(unsigned long event) 81{ 82 unsigned long irqflags; 83 84 spin_lock_irqsave(&otgwl_spinlock, irqflags); 85 86 if (!enabled) { 87 otgwl_drop(&vbus_lock); 88 spin_unlock_irqrestore(&otgwl_spinlock, irqflags); 89 return; 90 } 91 92 switch (event) { 93 case USB_EVENT_VBUS: 94 case USB_EVENT_ENUMERATED: 95 otgwl_hold(&vbus_lock); 96 break; 97 98 case USB_EVENT_NONE: 99 case USB_EVENT_ID: 100 case USB_EVENT_CHARGER: 101 otgwl_temporary_hold(&vbus_lock); 102 break; 103 104 default: 105 break; 106 } 107 108 spin_unlock_irqrestore(&otgwl_spinlock, irqflags); 109} 110 111static int otgwl_otg_notifications(struct notifier_block *nb, 112 unsigned long event, void *unused) 113{ 114 otgwl_handle_event(event); 115 return NOTIFY_OK; 116} 117 118static int set_enabled(const char *val, const struct kernel_param *kp) 119{ 120 int rv = param_set_bool(val, kp); 121 122 if (rv) 123 return rv; 124 125 if (otgwl_xceiv) 126 otgwl_handle_event(otgwl_xceiv->last_event); 127 128 return 0; 129} 130 131static struct kernel_param_ops enabled_param_ops = { 132 .set = set_enabled, 133 .get = param_get_bool, 134}; 135 136module_param_cb(enabled, &enabled_param_ops, &enabled, 0644); 137MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present"); 138 139static int __init otg_wakelock_init(void) 140{ 141 int ret; 142 struct usb_phy *phy; 143 144 phy = usb_get_phy(USB_PHY_TYPE_USB2); 145 146 if (IS_ERR(phy)) { 147 pr_err("%s: No USB transceiver found\n", __func__); 148 return PTR_ERR(phy); 149 } 150 otgwl_xceiv = phy; 151 152 snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s", 153 dev_name(otgwl_xceiv->dev)); 154 wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND, 155 vbus_lock.name); 156 157 otgwl_nb.notifier_call = otgwl_otg_notifications; 158 ret = usb_register_notifier(otgwl_xceiv, &otgwl_nb); 159 160 if (ret) { 161 pr_err("%s: usb_register_notifier on transceiver %s" 162 " failed\n", __func__, 163 dev_name(otgwl_xceiv->dev)); 164 otgwl_xceiv = NULL; 165 wake_lock_destroy(&vbus_lock.wakelock); 166 return ret; 167 } 168 169 otgwl_handle_event(otgwl_xceiv->last_event); 170 return ret; 171} 172 173late_initcall(otg_wakelock_init); 174