aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-tegra/tegra_usb_modem_power.c
diff options
context:
space:
mode:
authorJonathan Herman <hermanjl@cs.unc.edu>2013-01-22 10:38:37 -0500
committerJonathan Herman <hermanjl@cs.unc.edu>2013-01-22 10:38:37 -0500
commitfcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch)
treea57612d1888735a2ec7972891b68c1ac5ec8faea /arch/arm/mach-tegra/tegra_usb_modem_power.c
parent8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff)
Added missing tegra files.HEADmaster
Diffstat (limited to 'arch/arm/mach-tegra/tegra_usb_modem_power.c')
-rw-r--r--arch/arm/mach-tegra/tegra_usb_modem_power.c297
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
32struct 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
48static struct tegra_usb_modem tegra_mdm;
49
50/* supported modems */
51static 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
61static 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
79static 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
92static 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
124static 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
145static 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
160static struct notifier_block usb_nb = {
161 .notifier_call = usb_notify,
162};
163
164static 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
239static 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
251static 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
260static 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
269static 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
282static int __init tegra_usb_modem_power_init(void)
283{
284 return platform_driver_register(&tegra_usb_modem_power_driver);
285}
286
287subsys_initcall(tegra_usb_modem_power_init);
288
289static void __exit tegra_usb_modem_power_exit(void)
290{
291 platform_driver_unregister(&tegra_usb_modem_power_driver);
292}
293
294module_exit(tegra_usb_modem_power_exit);
295
296MODULE_DESCRIPTION("Tegra usb modem power management driver");
297MODULE_LICENSE("GPL");