Commit | Line | Data |
---|---|---|
6fa3eb70 S |
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 | ||
28 | static bool enabled = true; | |
29 | static struct usb_phy *otgwl_xceiv; | |
30 | static 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 | ||
37 | static DEFINE_SPINLOCK(otgwl_spinlock); | |
38 | ||
39 | /* | |
40 | * Only one lock, but since these 3 fields are associated with each other... | |
41 | */ | |
42 | ||
43 | struct 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 | ||
55 | static struct otgwl_lock vbus_lock; | |
56 | ||
57 | static void otgwl_hold(struct otgwl_lock *lock) | |
58 | { | |
59 | if (!lock->held) { | |
60 | wake_lock(&lock->wakelock); | |
61 | lock->held = true; | |
62 | } | |
63 | } | |
64 | ||
65 | static 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 | ||
72 | static void otgwl_drop(struct otgwl_lock *lock) | |
73 | { | |
74 | if (lock->held) { | |
75 | wake_unlock(&lock->wakelock); | |
76 | lock->held = false; | |
77 | } | |
78 | } | |
79 | ||
80 | static 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 | ||
111 | static 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 | ||
118 | static 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 | ||
131 | static struct kernel_param_ops enabled_param_ops = { | |
132 | .set = set_enabled, | |
133 | .get = param_get_bool, | |
134 | }; | |
135 | ||
136 | module_param_cb(enabled, &enabled_param_ops, &enabled, 0644); | |
137 | MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present"); | |
138 | ||
139 | static 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 | ||
173 | late_initcall(otg_wakelock_init); |