blob: 993162674836afa6bcd3b78af5c0c1544abe683f [file] [log] [blame]
Todd Poynor21b36ea2011-07-01 17:19:56 -07001/*
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/notifier.h>
20#include <linux/wakelock.h>
21#include <linux/spinlock.h>
22#include <linux/usb/otg.h>
23
24static bool enabled = true;
25static struct otg_transceiver *otgwl_xceiv;
26static struct notifier_block otgwl_nb;
27
28/*
29 * otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the
30 * locked field is updated to match.
31 */
32
33static DEFINE_SPINLOCK(otgwl_spinlock);
34
35/*
36 * Only one lock, but since these 3 fields are associated with each other...
37 */
38
39struct otgwl_lock {
40 char name[40];
41 struct wake_lock wakelock;
42 bool locked;
43};
44
45/*
46 * VBUS present lock.
47 */
48
49static struct otgwl_lock vbus_lock;
50
51static void otgwl_grab(struct otgwl_lock *lock)
52{
53 if (!lock->locked) {
54 wake_lock(&lock->wakelock);
55 lock->locked = true;
56 }
57}
58
59static void otgwl_drop(struct otgwl_lock *lock)
60{
61 if (lock->locked) {
62 wake_unlock(&lock->wakelock);
63 lock->locked = false;
64 }
65}
66
67static int otgwl_otg_notifications(struct notifier_block *nb,
68 unsigned long event, void *unused)
69{
70 unsigned long irqflags;
71
72 if (!enabled)
73 return NOTIFY_OK;
74
75 spin_lock_irqsave(&otgwl_spinlock, irqflags);
76
77 switch (event) {
78 case USB_EVENT_VBUS:
79 case USB_EVENT_ENUMERATED:
80 otgwl_grab(&vbus_lock);
81 break;
82
83 case USB_EVENT_NONE:
84 case USB_EVENT_ID:
85 case USB_EVENT_CHARGER:
86 otgwl_drop(&vbus_lock);
87 break;
88
89 default:
90 break;
91 }
92
93 spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
94 return NOTIFY_OK;
95}
96
97static void sync_with_xceiv_state(void)
98{
99 if ((otgwl_xceiv->last_event == USB_EVENT_VBUS) ||
100 (otgwl_xceiv->last_event == USB_EVENT_ENUMERATED))
101 otgwl_grab(&vbus_lock);
102 else
103 otgwl_drop(&vbus_lock);
104}
105
106static int init_for_xceiv(void)
107{
108 int rv;
109
110 if (!otgwl_xceiv) {
111 otgwl_xceiv = otg_get_transceiver();
112
113 if (!otgwl_xceiv) {
114 pr_err("%s: No OTG transceiver found\n", __func__);
115 return -ENODEV;
116 }
117
118 snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s",
119 dev_name(otgwl_xceiv->dev));
120 wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND,
121 vbus_lock.name);
122
123 rv = otg_register_notifier(otgwl_xceiv, &otgwl_nb);
124
125 if (rv) {
126 pr_err("%s: otg_register_notifier on transceiver %s"
127 " failed\n", __func__,
128 dev_name(otgwl_xceiv->dev));
129 otgwl_xceiv = NULL;
130 wake_lock_destroy(&vbus_lock.wakelock);
131 return rv;
132 }
133 }
134
135 return 0;
136}
137
138static int set_enabled(const char *val, const struct kernel_param *kp)
139{
140 unsigned long irqflags;
141 int rv = param_set_bool(val, kp);
142
143 if (rv)
144 return rv;
145
146 rv = init_for_xceiv();
147
148 if (rv)
149 return rv;
150
151 spin_lock_irqsave(&otgwl_spinlock, irqflags);
152
153 if (enabled)
154 sync_with_xceiv_state();
155 else
156 otgwl_drop(&vbus_lock);
157
158 spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
159 return 0;
160}
161
162static struct kernel_param_ops enabled_param_ops = {
163 .set = set_enabled,
164 .get = param_get_bool,
165};
166
167module_param_cb(enabled, &enabled_param_ops, &enabled, 0644);
168MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present");
169
170static int __init otg_wakelock_init(void)
171{
172 unsigned long irqflags;
173
174 otgwl_nb.notifier_call = otgwl_otg_notifications;
175
176 if (!init_for_xceiv()) {
177 spin_lock_irqsave(&otgwl_spinlock, irqflags);
178
179 if (enabled)
180 sync_with_xceiv_state();
181
182 spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
183 } else {
184 enabled = false;
185 }
186
187 return 0;
188}
189
190late_initcall(otg_wakelock_init);