arm: tegra: baseband: Enable autosuspend for Icera modem
[linux-2.6.git] / arch / arm / mach-tegra / tegra_usb_modem_power.c
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                 pr_info("Add device %d <%s %s>\n", udev->devnum,
100                         udev->manufacturer, udev->product);
101
102                 mutex_lock(&tegra_mdm.lock);
103                 tegra_mdm.udev = udev;
104                 tegra_mdm.intf = intf;
105                 tegra_mdm.vid = desc->idVendor;
106                 tegra_mdm.pid = desc->idProduct;
107                 tegra_mdm.wake_cnt = 0;
108                 tegra_mdm.capability = id->driver_info;
109                 mutex_unlock(&tegra_mdm.lock);
110
111                 pr_info("persist_enabled: %u\n", udev->persist_enabled);
112
113                 if (tegra_mdm.capability & TEGRA_MODEM_AUTOSUSPEND) {
114                         usb_enable_autosuspend(udev);
115                         pr_info("enable autosuspend for %s %s\n",
116                                 udev->manufacturer, udev->product);
117                 }
118         }
119 }
120
121 static void device_remove_handler(struct usb_device *udev)
122 {
123         const struct usb_device_descriptor *desc = &udev->descriptor;
124
125         if (desc->idVendor == tegra_mdm.vid &&
126             desc->idProduct == tegra_mdm.pid) {
127                 pr_info("Remove device %d <%s %s>\n", udev->devnum,
128                         udev->manufacturer, udev->product);
129
130                 mutex_lock(&tegra_mdm.lock);
131                 tegra_mdm.udev = NULL;
132                 tegra_mdm.intf = NULL;
133                 tegra_mdm.vid = 0;
134                 mutex_unlock(&tegra_mdm.lock);
135
136                 if (tegra_mdm.capability & TEGRA_MODEM_RECOVERY)
137                         queue_delayed_work(tegra_mdm.wq,
138                                            &tegra_mdm.recovery_work, HZ * 10);
139         }
140 }
141
142 static int usb_notify(struct notifier_block *self, unsigned long action,
143                       void *blob)
144 {
145         switch (action) {
146         case USB_DEVICE_ADD:
147                 device_add_handler(blob);
148                 break;
149         case USB_DEVICE_REMOVE:
150                 device_remove_handler(blob);
151                 break;
152         }
153
154         return NOTIFY_OK;
155 }
156
157 static struct notifier_block usb_nb = {
158         .notifier_call = usb_notify,
159 };
160
161 static int tegra_usb_modem_probe(struct platform_device *pdev)
162 {
163         struct tegra_usb_modem_power_platform_data *pdata =
164             pdev->dev.platform_data;
165         int ret;
166
167         if (!pdata) {
168                 dev_dbg(&pdev->dev, "platform_data not available\n");
169                 return -EINVAL;
170         }
171
172         /* get modem operations from platform data */
173         tegra_mdm.ops = (const struct tegra_modem_operations *)pdata->ops;
174
175         if (tegra_mdm.ops) {
176                 /* modem init */
177                 if (tegra_mdm.ops->init) {
178                         ret = tegra_mdm.ops->init();
179                         if (ret)
180                                 return ret;
181                 }
182
183                 /* start modem */
184                 if (tegra_mdm.ops->start)
185                         tegra_mdm.ops->start();
186         }
187
188         mutex_init(&(tegra_mdm.lock));
189         wake_lock_init(&(tegra_mdm.wake_lock), WAKE_LOCK_SUSPEND,
190                        "tegra_usb_mdm_lock");
191
192         /* create work queue */
193         tegra_mdm.wq = create_workqueue("tegra_usb_mdm_queue");
194         INIT_DELAYED_WORK(&(tegra_mdm.recovery_work), tegra_usb_modem_recovery);
195
196         /* create threaded irq for remote wakeup */
197         if (pdata->wake_gpio) {
198                 /* get remote wakeup gpio from platform data */
199                 tegra_mdm.wake_gpio = pdata->wake_gpio;
200
201                 ret = gpio_request(tegra_mdm.wake_gpio, "usb_mdm_wake");
202                 if (ret)
203                         return ret;
204
205                 tegra_gpio_enable(tegra_mdm.wake_gpio);
206
207                 /* enable IRQ for remote wakeup */
208                 tegra_mdm.irq = gpio_to_irq(tegra_mdm.wake_gpio);
209
210                 ret =
211                     request_threaded_irq(tegra_mdm.irq, NULL,
212                                          tegra_usb_modem_wake_thread,
213                                          pdata->flags, "tegra_usb_mdm_wake",
214                                          &tegra_mdm);
215                 if (ret < 0) {
216                         dev_err(&pdev->dev, "%s: request_threaded_irq error\n",
217                                 __func__);
218                         return ret;
219                 }
220
221                 ret = enable_irq_wake(tegra_mdm.irq);
222                 if (ret) {
223                         dev_err(&pdev->dev, "%s: enable_irq_wake error\n",
224                                 __func__);
225                         free_irq(tegra_mdm.irq, &tegra_mdm);
226                         return ret;
227                 }
228         }
229
230         usb_register_notify(&usb_nb);
231         dev_info(&pdev->dev, "Initialized tegra_usb_modem_power\n");
232
233         return 0;
234 }
235
236 static int __exit tegra_usb_modem_remove(struct platform_device *pdev)
237 {
238         usb_unregister_notify(&usb_nb);
239         free_irq(tegra_mdm.irq, &tegra_mdm);
240         return 0;
241 }
242
243 #ifdef CONFIG_PM
244 static int tegra_usb_modem_suspend(struct platform_device *pdev,
245                                    pm_message_t state)
246 {
247         /* send L3 hint to modem */
248         if (tegra_mdm.ops && tegra_mdm.ops->suspend)
249                 tegra_mdm.ops->suspend();
250         return 0;
251 }
252
253 static int tegra_usb_modem_resume(struct platform_device *pdev)
254 {
255         /* send L3->L0 hint to modem */
256         if (tegra_mdm.ops && tegra_mdm.ops->resume)
257                 tegra_mdm.ops->resume();
258         return 0;
259 }
260 #endif
261
262 static struct platform_driver tegra_usb_modem_power_driver = {
263         .driver = {
264                    .name = "tegra_usb_modem_power",
265                    .owner = THIS_MODULE,
266                    },
267         .probe = tegra_usb_modem_probe,
268         .remove = __exit_p(tegra_usb_modem_remove),
269 #ifdef CONFIG_PM
270         .suspend = tegra_usb_modem_suspend,
271         .resume = tegra_usb_modem_resume,
272 #endif
273 };
274
275 static int __init tegra_usb_modem_power_init(void)
276 {
277         return platform_driver_register(&tegra_usb_modem_power_driver);
278 }
279
280 subsys_initcall(tegra_usb_modem_power_init);
281
282 static void __exit tegra_usb_modem_power_exit(void)
283 {
284         platform_driver_unregister(&tegra_usb_modem_power_driver);
285 }
286
287 module_exit(tegra_usb_modem_power_exit);
288
289 MODULE_DESCRIPTION("Tegra usb modem power management driver");
290 MODULE_LICENSE("GPL");