diff options
Diffstat (limited to 'arch/arm/mach-tegra/tegra_usb_modem_power.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra_usb_modem_power.c | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra_usb_modem_power.c b/arch/arm/mach-tegra/tegra_usb_modem_power.c new file mode 100644 index 00000000000..db062ffac34 --- /dev/null +++ b/arch/arm/mach-tegra/tegra_usb_modem_power.c | |||
@@ -0,0 +1,297 @@ | |||
1 | /* | ||
2 | * arch/arm/mach-tegra/tegra_usb_modem_power.c | ||
3 | * | ||
4 | * Copyright (c) 2011, NVIDIA Corporation. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
14 | * more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #include <linux/module.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/workqueue.h> | ||
26 | #include <linux/gpio.h> | ||
27 | #include <linux/usb.h> | ||
28 | #include <linux/err.h> | ||
29 | #include <linux/wakelock.h> | ||
30 | #include <mach/tegra_usb_modem_power.h> | ||
31 | |||
32 | struct tegra_usb_modem { | ||
33 | unsigned int wake_gpio; /* remote wakeup gpio */ | ||
34 | unsigned int wake_cnt; /* remote wakeup counter */ | ||
35 | int irq; /* remote wakeup irq */ | ||
36 | struct mutex lock; | ||
37 | struct wake_lock wake_lock; /* modem wake lock */ | ||
38 | unsigned int vid; /* modem vendor id */ | ||
39 | unsigned int pid; /* modem product id */ | ||
40 | struct usb_device *udev; /* modem usb device */ | ||
41 | struct usb_interface *intf; /* first modem usb interface */ | ||
42 | struct workqueue_struct *wq; /* modem workqueue */ | ||
43 | struct delayed_work recovery_work; /* modem recovery work */ | ||
44 | const struct tegra_modem_operations *ops; /* modem operations */ | ||
45 | unsigned int capability; /* modem capability */ | ||
46 | }; | ||
47 | |||
48 | static struct tegra_usb_modem tegra_mdm; | ||
49 | |||
50 | /* supported modems */ | ||
51 | static const struct usb_device_id modem_list[] = { | ||
52 | {USB_DEVICE(0x1983, 0x0310), /* Icera 450 rev1 */ | ||
53 | .driver_info = TEGRA_MODEM_AUTOSUSPEND, | ||
54 | }, | ||
55 | {USB_DEVICE(0x1983, 0x0321), /* Icera 450 rev2 */ | ||
56 | .driver_info = TEGRA_MODEM_AUTOSUSPEND, | ||
57 | }, | ||
58 | {} | ||
59 | }; | ||
60 | |||
61 | static irqreturn_t tegra_usb_modem_wake_thread(int irq, void *data) | ||
62 | { | ||
63 | struct tegra_usb_modem *modem = (struct tegra_usb_modem *)data; | ||
64 | |||
65 | wake_lock_timeout(&modem->wake_lock, HZ); | ||
66 | mutex_lock(&modem->lock); | ||
67 | if (modem->udev) { | ||
68 | usb_lock_device(modem->udev); | ||
69 | pr_info("modem wake (%u)\n", ++(modem->wake_cnt)); | ||
70 | if (usb_autopm_get_interface(modem->intf) == 0) | ||
71 | usb_autopm_put_interface_async(modem->intf); | ||
72 | usb_unlock_device(modem->udev); | ||
73 | } | ||
74 | mutex_unlock(&modem->lock); | ||
75 | |||
76 | return IRQ_HANDLED; | ||
77 | } | ||
78 | |||
79 | static void tegra_usb_modem_recovery(struct work_struct *ws) | ||
80 | { | ||
81 | struct tegra_usb_modem *modem = container_of(ws, struct tegra_usb_modem, | ||
82 | recovery_work.work); | ||
83 | |||
84 | mutex_lock(&modem->lock); | ||
85 | if (!modem->udev) { /* assume modem crashed */ | ||
86 | if (modem->ops && modem->ops->reset) | ||
87 | modem->ops->reset(); | ||
88 | } | ||
89 | mutex_unlock(&modem->lock); | ||
90 | } | ||
91 | |||
92 | static void device_add_handler(struct usb_device *udev) | ||
93 | { | ||
94 | const struct usb_device_descriptor *desc = &udev->descriptor; | ||
95 | struct usb_interface *intf = usb_ifnum_to_if(udev, 0); | ||
96 | const struct usb_device_id *id = usb_match_id(intf, modem_list); | ||
97 | |||
98 | if (id) { | ||
99 | /* hold wakelock to ensure ril has enough time to restart */ | ||
100 | wake_lock_timeout(&tegra_mdm.wake_lock, HZ*10); | ||
101 | |||
102 | pr_info("Add device %d <%s %s>\n", udev->devnum, | ||
103 | udev->manufacturer, udev->product); | ||
104 | |||
105 | mutex_lock(&tegra_mdm.lock); | ||
106 | tegra_mdm.udev = udev; | ||
107 | tegra_mdm.intf = intf; | ||
108 | tegra_mdm.vid = desc->idVendor; | ||
109 | tegra_mdm.pid = desc->idProduct; | ||
110 | tegra_mdm.wake_cnt = 0; | ||
111 | tegra_mdm.capability = id->driver_info; | ||
112 | mutex_unlock(&tegra_mdm.lock); | ||
113 | |||
114 | pr_info("persist_enabled: %u\n", udev->persist_enabled); | ||
115 | |||
116 | if (tegra_mdm.capability & TEGRA_MODEM_AUTOSUSPEND) { | ||
117 | usb_enable_autosuspend(udev); | ||
118 | pr_info("enable autosuspend for %s %s\n", | ||
119 | udev->manufacturer, udev->product); | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | |||
124 | static void device_remove_handler(struct usb_device *udev) | ||
125 | { | ||
126 | const struct usb_device_descriptor *desc = &udev->descriptor; | ||
127 | |||
128 | if (desc->idVendor == tegra_mdm.vid && | ||
129 | desc->idProduct == tegra_mdm.pid) { | ||
130 | pr_info("Remove device %d <%s %s>\n", udev->devnum, | ||
131 | udev->manufacturer, udev->product); | ||
132 | |||
133 | mutex_lock(&tegra_mdm.lock); | ||
134 | tegra_mdm.udev = NULL; | ||
135 | tegra_mdm.intf = NULL; | ||
136 | tegra_mdm.vid = 0; | ||
137 | mutex_unlock(&tegra_mdm.lock); | ||
138 | |||
139 | if (tegra_mdm.capability & TEGRA_MODEM_RECOVERY) | ||
140 | queue_delayed_work(tegra_mdm.wq, | ||
141 | &tegra_mdm.recovery_work, HZ * 10); | ||
142 | } | ||
143 | } | ||
144 | |||
145 | static int usb_notify(struct notifier_block *self, unsigned long action, | ||
146 | void *blob) | ||
147 | { | ||
148 | switch (action) { | ||
149 | case USB_DEVICE_ADD: | ||
150 | device_add_handler(blob); | ||
151 | break; | ||
152 | case USB_DEVICE_REMOVE: | ||
153 | device_remove_handler(blob); | ||
154 | break; | ||
155 | } | ||
156 | |||
157 | return NOTIFY_OK; | ||
158 | } | ||
159 | |||
160 | static struct notifier_block usb_nb = { | ||
161 | .notifier_call = usb_notify, | ||
162 | }; | ||
163 | |||
164 | static int tegra_usb_modem_probe(struct platform_device *pdev) | ||
165 | { | ||
166 | struct tegra_usb_modem_power_platform_data *pdata = | ||
167 | pdev->dev.platform_data; | ||
168 | int ret; | ||
169 | |||
170 | if (!pdata) { | ||
171 | dev_dbg(&pdev->dev, "platform_data not available\n"); | ||
172 | return -EINVAL; | ||
173 | } | ||
174 | |||
175 | /* get modem operations from platform data */ | ||
176 | tegra_mdm.ops = (const struct tegra_modem_operations *)pdata->ops; | ||
177 | |||
178 | if (tegra_mdm.ops) { | ||
179 | /* modem init */ | ||
180 | if (tegra_mdm.ops->init) { | ||
181 | ret = tegra_mdm.ops->init(); | ||
182 | if (ret) | ||
183 | return ret; | ||
184 | } | ||
185 | |||
186 | /* start modem */ | ||
187 | if (tegra_mdm.ops->start) | ||
188 | tegra_mdm.ops->start(); | ||
189 | } | ||
190 | |||
191 | mutex_init(&(tegra_mdm.lock)); | ||
192 | wake_lock_init(&(tegra_mdm.wake_lock), WAKE_LOCK_SUSPEND, | ||
193 | "tegra_usb_mdm_lock"); | ||
194 | |||
195 | /* create work queue */ | ||
196 | tegra_mdm.wq = create_workqueue("tegra_usb_mdm_queue"); | ||
197 | INIT_DELAYED_WORK(&(tegra_mdm.recovery_work), tegra_usb_modem_recovery); | ||
198 | |||
199 | /* create threaded irq for remote wakeup */ | ||
200 | if (gpio_is_valid(pdata->wake_gpio)) { | ||
201 | /* get remote wakeup gpio from platform data */ | ||
202 | tegra_mdm.wake_gpio = pdata->wake_gpio; | ||
203 | |||
204 | ret = gpio_request(tegra_mdm.wake_gpio, "usb_mdm_wake"); | ||
205 | if (ret) | ||
206 | return ret; | ||
207 | |||
208 | tegra_gpio_enable(tegra_mdm.wake_gpio); | ||
209 | |||
210 | /* enable IRQ for remote wakeup */ | ||
211 | tegra_mdm.irq = gpio_to_irq(tegra_mdm.wake_gpio); | ||
212 | |||
213 | ret = | ||
214 | request_threaded_irq(tegra_mdm.irq, NULL, | ||
215 | tegra_usb_modem_wake_thread, | ||
216 | pdata->flags, "tegra_usb_mdm_wake", | ||
217 | &tegra_mdm); | ||
218 | if (ret < 0) { | ||
219 | dev_err(&pdev->dev, "%s: request_threaded_irq error\n", | ||
220 | __func__); | ||
221 | return ret; | ||
222 | } | ||
223 | |||
224 | ret = enable_irq_wake(tegra_mdm.irq); | ||
225 | if (ret) { | ||
226 | dev_err(&pdev->dev, "%s: enable_irq_wake error\n", | ||
227 | __func__); | ||
228 | free_irq(tegra_mdm.irq, &tegra_mdm); | ||
229 | return ret; | ||
230 | } | ||
231 | } | ||
232 | |||
233 | usb_register_notify(&usb_nb); | ||
234 | dev_info(&pdev->dev, "Initialized tegra_usb_modem_power\n"); | ||
235 | |||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | static int __exit tegra_usb_modem_remove(struct platform_device *pdev) | ||
240 | { | ||
241 | usb_unregister_notify(&usb_nb); | ||
242 | |||
243 | if (tegra_mdm.irq) { | ||
244 | disable_irq_wake(tegra_mdm.irq); | ||
245 | free_irq(tegra_mdm.irq, &tegra_mdm); | ||
246 | } | ||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | #ifdef CONFIG_PM | ||
251 | static int tegra_usb_modem_suspend(struct platform_device *pdev, | ||
252 | pm_message_t state) | ||
253 | { | ||
254 | /* send L3 hint to modem */ | ||
255 | if (tegra_mdm.ops && tegra_mdm.ops->suspend) | ||
256 | tegra_mdm.ops->suspend(); | ||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | static int tegra_usb_modem_resume(struct platform_device *pdev) | ||
261 | { | ||
262 | /* send L3->L0 hint to modem */ | ||
263 | if (tegra_mdm.ops && tegra_mdm.ops->resume) | ||
264 | tegra_mdm.ops->resume(); | ||
265 | return 0; | ||
266 | } | ||
267 | #endif | ||
268 | |||
269 | static struct platform_driver tegra_usb_modem_power_driver = { | ||
270 | .driver = { | ||
271 | .name = "tegra_usb_modem_power", | ||
272 | .owner = THIS_MODULE, | ||
273 | }, | ||
274 | .probe = tegra_usb_modem_probe, | ||
275 | .remove = __exit_p(tegra_usb_modem_remove), | ||
276 | #ifdef CONFIG_PM | ||
277 | .suspend = tegra_usb_modem_suspend, | ||
278 | .resume = tegra_usb_modem_resume, | ||
279 | #endif | ||
280 | }; | ||
281 | |||
282 | static int __init tegra_usb_modem_power_init(void) | ||
283 | { | ||
284 | return platform_driver_register(&tegra_usb_modem_power_driver); | ||
285 | } | ||
286 | |||
287 | subsys_initcall(tegra_usb_modem_power_init); | ||
288 | |||
289 | static void __exit tegra_usb_modem_power_exit(void) | ||
290 | { | ||
291 | platform_driver_unregister(&tegra_usb_modem_power_driver); | ||
292 | } | ||
293 | |||
294 | module_exit(tegra_usb_modem_power_exit); | ||
295 | |||
296 | MODULE_DESCRIPTION("Tegra usb modem power management driver"); | ||
297 | MODULE_LICENSE("GPL"); | ||