diff options
Diffstat (limited to 'drivers/usb/otg/tegra-otg.c')
-rw-r--r-- | drivers/usb/otg/tegra-otg.c | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c new file mode 100644 index 00000000000..4c04e6e183f --- /dev/null +++ b/drivers/usb/otg/tegra-otg.c | |||
@@ -0,0 +1,540 @@ | |||
1 | /* | ||
2 | * drivers/usb/otg/tegra-otg.c | ||
3 | * | ||
4 | * OTG transceiver driver for Tegra UTMI phy | ||
5 | * | ||
6 | * Copyright (C) 2010 NVIDIA Corp. | ||
7 | * Copyright (C) 2010 Google, Inc. | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify it | ||
10 | * under the terms of the GNU General Public License as published by the | ||
11 | * Free Software Foundation; either version 2 of the License, or (at your | ||
12 | * option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
16 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
17 | * more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License along | ||
20 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
22 | */ | ||
23 | |||
24 | #include <linux/usb.h> | ||
25 | #include <linux/usb/otg.h> | ||
26 | #include <linux/usb/gadget.h> | ||
27 | #include <linux/usb/hcd.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/platform_data/tegra_usb.h> | ||
30 | #include <linux/clk.h> | ||
31 | #include <linux/io.h> | ||
32 | #include <linux/delay.h> | ||
33 | #include <linux/err.h> | ||
34 | |||
35 | #define USB_PHY_WAKEUP 0x408 | ||
36 | #define USB_ID_INT_EN (1 << 0) | ||
37 | #define USB_ID_INT_STATUS (1 << 1) | ||
38 | #define USB_ID_STATUS (1 << 2) | ||
39 | #define USB_ID_PIN_WAKEUP_EN (1 << 6) | ||
40 | #define USB_VBUS_WAKEUP_EN (1 << 30) | ||
41 | #define USB_VBUS_INT_EN (1 << 8) | ||
42 | #define USB_VBUS_INT_STATUS (1 << 9) | ||
43 | #define USB_VBUS_STATUS (1 << 10) | ||
44 | #define USB_INTS (USB_VBUS_INT_STATUS | USB_ID_INT_STATUS) | ||
45 | |||
46 | typedef void (*callback_t)(enum usb_otg_state to, | ||
47 | enum usb_otg_state from, void *args); | ||
48 | |||
49 | struct tegra_otg_data { | ||
50 | struct otg_transceiver otg; | ||
51 | unsigned long int_status; | ||
52 | spinlock_t lock; | ||
53 | void __iomem *regs; | ||
54 | struct clk *clk; | ||
55 | int irq; | ||
56 | struct platform_device *pdev; | ||
57 | struct work_struct work; | ||
58 | unsigned int intr_reg_data; | ||
59 | bool detect_vbus; | ||
60 | bool clk_enabled; | ||
61 | callback_t charger_cb; | ||
62 | void *charger_cb_data; | ||
63 | |||
64 | }; | ||
65 | static struct tegra_otg_data *tegra_clone; | ||
66 | |||
67 | static inline unsigned long otg_readl(struct tegra_otg_data *tegra, | ||
68 | unsigned int offset) | ||
69 | { | ||
70 | return readl(tegra->regs + offset); | ||
71 | } | ||
72 | |||
73 | static inline void otg_writel(struct tegra_otg_data *tegra, unsigned long val, | ||
74 | unsigned int offset) | ||
75 | { | ||
76 | writel(val, tegra->regs + offset); | ||
77 | } | ||
78 | |||
79 | static void tegra_otg_enable_clk(void) | ||
80 | { | ||
81 | if (!tegra_clone->clk_enabled) | ||
82 | clk_enable(tegra_clone->clk); | ||
83 | tegra_clone->clk_enabled = true; | ||
84 | } | ||
85 | |||
86 | static void tegra_otg_disable_clk(void) | ||
87 | { | ||
88 | if (tegra_clone->clk_enabled) | ||
89 | clk_disable(tegra_clone->clk); | ||
90 | tegra_clone->clk_enabled = false; | ||
91 | } | ||
92 | |||
93 | static const char *tegra_state_name(enum usb_otg_state state) | ||
94 | { | ||
95 | switch (state) { | ||
96 | case OTG_STATE_A_HOST: | ||
97 | return "HOST"; | ||
98 | case OTG_STATE_B_PERIPHERAL: | ||
99 | return "PERIPHERAL"; | ||
100 | case OTG_STATE_A_SUSPEND: | ||
101 | return "SUSPEND"; | ||
102 | case OTG_STATE_UNDEFINED: | ||
103 | return "UNDEFINED"; | ||
104 | default: | ||
105 | return "INVALID"; | ||
106 | } | ||
107 | } | ||
108 | |||
109 | static struct platform_device * | ||
110 | tegra_usb_otg_host_register(struct platform_device *ehci_device, | ||
111 | struct tegra_ehci_platform_data *pdata) | ||
112 | { | ||
113 | struct platform_device *pdev; | ||
114 | void *platform_data; | ||
115 | int val; | ||
116 | |||
117 | pdev = platform_device_alloc(ehci_device->name, ehci_device->id); | ||
118 | if (!pdev) | ||
119 | return NULL; | ||
120 | |||
121 | val = platform_device_add_resources(pdev, ehci_device->resource, | ||
122 | ehci_device->num_resources); | ||
123 | if (val) | ||
124 | goto error; | ||
125 | |||
126 | pdev->dev.dma_mask = ehci_device->dev.dma_mask; | ||
127 | pdev->dev.coherent_dma_mask = ehci_device->dev.coherent_dma_mask; | ||
128 | |||
129 | platform_data = kmalloc(sizeof(struct tegra_ehci_platform_data), | ||
130 | GFP_KERNEL); | ||
131 | if (!platform_data) | ||
132 | goto error; | ||
133 | |||
134 | memcpy(platform_data, pdata, sizeof(struct tegra_ehci_platform_data)); | ||
135 | pdev->dev.platform_data = platform_data; | ||
136 | |||
137 | val = platform_device_add(pdev); | ||
138 | if (val) | ||
139 | goto error_add; | ||
140 | |||
141 | return pdev; | ||
142 | |||
143 | error_add: | ||
144 | kfree(platform_data); | ||
145 | error: | ||
146 | pr_err("%s: failed to add the host controller device\n", __func__); | ||
147 | platform_device_put(pdev); | ||
148 | return NULL; | ||
149 | } | ||
150 | |||
151 | static void tegra_usb_otg_host_unregister(struct platform_device *pdev) | ||
152 | { | ||
153 | kfree(pdev->dev.platform_data); | ||
154 | pdev->dev.platform_data = NULL; | ||
155 | platform_device_unregister(pdev); | ||
156 | } | ||
157 | |||
158 | void tegra_start_host(struct tegra_otg_data *tegra) | ||
159 | { | ||
160 | struct tegra_otg_platform_data *pdata = tegra->otg.dev->platform_data; | ||
161 | if (!tegra->pdev) { | ||
162 | tegra->pdev = tegra_usb_otg_host_register(pdata->ehci_device, | ||
163 | pdata->ehci_pdata); | ||
164 | } | ||
165 | } | ||
166 | |||
167 | void tegra_stop_host(struct tegra_otg_data *tegra) | ||
168 | { | ||
169 | if (tegra->pdev) { | ||
170 | tegra_usb_otg_host_unregister(tegra->pdev); | ||
171 | tegra->pdev = NULL; | ||
172 | } | ||
173 | } | ||
174 | |||
175 | int register_otg_callback(callback_t cb, void *args) | ||
176 | { | ||
177 | if (!tegra_clone) | ||
178 | return -ENODEV; | ||
179 | tegra_clone->charger_cb = cb; | ||
180 | tegra_clone->charger_cb_data = args; | ||
181 | return 0; | ||
182 | } | ||
183 | EXPORT_SYMBOL_GPL(register_otg_callback); | ||
184 | |||
185 | static void irq_work(struct work_struct *work) | ||
186 | { | ||
187 | struct tegra_otg_data *tegra = | ||
188 | container_of(work, struct tegra_otg_data, work); | ||
189 | struct otg_transceiver *otg = &tegra->otg; | ||
190 | enum usb_otg_state from = otg->state; | ||
191 | enum usb_otg_state to = OTG_STATE_UNDEFINED; | ||
192 | unsigned long flags; | ||
193 | unsigned long status; | ||
194 | |||
195 | if (tegra->detect_vbus) { | ||
196 | tegra->detect_vbus = false; | ||
197 | tegra_otg_enable_clk(); | ||
198 | return; | ||
199 | } | ||
200 | |||
201 | clk_enable(tegra->clk); | ||
202 | |||
203 | spin_lock_irqsave(&tegra->lock, flags); | ||
204 | |||
205 | status = tegra->int_status; | ||
206 | |||
207 | if (tegra->int_status & USB_ID_INT_STATUS) { | ||
208 | if (status & USB_ID_STATUS) { | ||
209 | if ((status & USB_VBUS_STATUS) && (from != OTG_STATE_A_HOST)) | ||
210 | to = OTG_STATE_B_PERIPHERAL; | ||
211 | else | ||
212 | to = OTG_STATE_A_SUSPEND; | ||
213 | } | ||
214 | else | ||
215 | to = OTG_STATE_A_HOST; | ||
216 | } | ||
217 | if (from != OTG_STATE_A_HOST) { | ||
218 | if (tegra->int_status & USB_VBUS_INT_STATUS) { | ||
219 | if (status & USB_VBUS_STATUS) | ||
220 | to = OTG_STATE_B_PERIPHERAL; | ||
221 | else | ||
222 | to = OTG_STATE_A_SUSPEND; | ||
223 | } | ||
224 | } | ||
225 | spin_unlock_irqrestore(&tegra->lock, flags); | ||
226 | |||
227 | if (to != OTG_STATE_UNDEFINED) { | ||
228 | otg->state = to; | ||
229 | |||
230 | dev_info(tegra->otg.dev, "%s --> %s\n", tegra_state_name(from), | ||
231 | tegra_state_name(to)); | ||
232 | |||
233 | if (tegra->charger_cb) | ||
234 | tegra->charger_cb(to, from, tegra->charger_cb_data); | ||
235 | |||
236 | if (to == OTG_STATE_A_SUSPEND) { | ||
237 | if (from == OTG_STATE_A_HOST) | ||
238 | tegra_stop_host(tegra); | ||
239 | else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) | ||
240 | usb_gadget_vbus_disconnect(otg->gadget); | ||
241 | } else if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) { | ||
242 | if (from == OTG_STATE_A_SUSPEND) | ||
243 | usb_gadget_vbus_connect(otg->gadget); | ||
244 | } else if (to == OTG_STATE_A_HOST) { | ||
245 | if (from == OTG_STATE_A_SUSPEND) | ||
246 | tegra_start_host(tegra); | ||
247 | } | ||
248 | } | ||
249 | |||
250 | |||
251 | clk_disable(tegra->clk); | ||
252 | tegra_otg_disable_clk(); | ||
253 | } | ||
254 | |||
255 | static irqreturn_t tegra_otg_irq(int irq, void *data) | ||
256 | { | ||
257 | struct tegra_otg_data *tegra = data; | ||
258 | unsigned long flags; | ||
259 | unsigned long val; | ||
260 | |||
261 | spin_lock_irqsave(&tegra->lock, flags); | ||
262 | |||
263 | val = otg_readl(tegra, USB_PHY_WAKEUP); | ||
264 | if (val & (USB_VBUS_INT_EN | USB_ID_INT_EN)) { | ||
265 | otg_writel(tegra, val, USB_PHY_WAKEUP); | ||
266 | if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) { | ||
267 | tegra->int_status = val; | ||
268 | tegra->detect_vbus = false; | ||
269 | schedule_work(&tegra->work); | ||
270 | } | ||
271 | } | ||
272 | |||
273 | spin_unlock_irqrestore(&tegra->lock, flags); | ||
274 | |||
275 | return IRQ_HANDLED; | ||
276 | } | ||
277 | |||
278 | void tegra_otg_check_vbus_detection(void) | ||
279 | { | ||
280 | tegra_clone->detect_vbus = true; | ||
281 | schedule_work(&tegra_clone->work); | ||
282 | } | ||
283 | EXPORT_SYMBOL(tegra_otg_check_vbus_detection); | ||
284 | |||
285 | static int tegra_otg_set_peripheral(struct otg_transceiver *otg, | ||
286 | struct usb_gadget *gadget) | ||
287 | { | ||
288 | struct tegra_otg_data *tegra; | ||
289 | unsigned long val; | ||
290 | |||
291 | tegra = container_of(otg, struct tegra_otg_data, otg); | ||
292 | otg->gadget = gadget; | ||
293 | |||
294 | clk_enable(tegra->clk); | ||
295 | val = otg_readl(tegra, USB_PHY_WAKEUP); | ||
296 | val |= (USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN); | ||
297 | val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); | ||
298 | otg_writel(tegra, val, USB_PHY_WAKEUP); | ||
299 | /* Add delay to make sure register is updated */ | ||
300 | udelay(1); | ||
301 | clk_disable(tegra->clk); | ||
302 | |||
303 | if ((val & USB_ID_STATUS) && (val & USB_VBUS_STATUS)) { | ||
304 | val |= USB_VBUS_INT_STATUS; | ||
305 | } else if (!(val & USB_ID_STATUS)) { | ||
306 | val |= USB_ID_INT_STATUS; | ||
307 | } else { | ||
308 | val &= ~(USB_ID_INT_STATUS | USB_VBUS_INT_STATUS); | ||
309 | } | ||
310 | |||
311 | if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) { | ||
312 | tegra->int_status = val; | ||
313 | tegra->detect_vbus = false; | ||
314 | schedule_work (&tegra->work); | ||
315 | } | ||
316 | |||
317 | return 0; | ||
318 | } | ||
319 | |||
320 | static int tegra_otg_set_host(struct otg_transceiver *otg, | ||
321 | struct usb_bus *host) | ||
322 | { | ||
323 | struct tegra_otg_data *tegra; | ||
324 | unsigned long val; | ||
325 | |||
326 | tegra = container_of(otg, struct tegra_otg_data, otg); | ||
327 | otg->host = host; | ||
328 | |||
329 | clk_enable(tegra->clk); | ||
330 | val = otg_readl(tegra, USB_PHY_WAKEUP); | ||
331 | val &= ~(USB_VBUS_INT_STATUS | USB_ID_INT_STATUS); | ||
332 | |||
333 | val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); | ||
334 | otg_writel(tegra, val, USB_PHY_WAKEUP); | ||
335 | clk_disable(tegra->clk); | ||
336 | |||
337 | return 0; | ||
338 | } | ||
339 | |||
340 | static int tegra_otg_set_power(struct otg_transceiver *otg, unsigned mA) | ||
341 | { | ||
342 | return 0; | ||
343 | } | ||
344 | |||
345 | static int tegra_otg_set_suspend(struct otg_transceiver *otg, int suspend) | ||
346 | { | ||
347 | return 0; | ||
348 | } | ||
349 | |||
350 | static int tegra_otg_probe(struct platform_device *pdev) | ||
351 | { | ||
352 | struct tegra_otg_data *tegra; | ||
353 | struct tegra_otg_platform_data *otg_pdata; | ||
354 | struct tegra_ehci_platform_data *ehci_pdata; | ||
355 | struct resource *res; | ||
356 | int err; | ||
357 | |||
358 | tegra = kzalloc(sizeof(struct tegra_otg_data), GFP_KERNEL); | ||
359 | if (!tegra) | ||
360 | return -ENOMEM; | ||
361 | |||
362 | tegra->otg.dev = &pdev->dev; | ||
363 | otg_pdata = tegra->otg.dev->platform_data; | ||
364 | ehci_pdata = otg_pdata->ehci_pdata; | ||
365 | tegra->otg.label = "tegra-otg"; | ||
366 | tegra->otg.state = OTG_STATE_UNDEFINED; | ||
367 | tegra->otg.set_host = tegra_otg_set_host; | ||
368 | tegra->otg.set_peripheral = tegra_otg_set_peripheral; | ||
369 | tegra->otg.set_suspend = tegra_otg_set_suspend; | ||
370 | tegra->otg.set_power = tegra_otg_set_power; | ||
371 | spin_lock_init(&tegra->lock); | ||
372 | |||
373 | platform_set_drvdata(pdev, tegra); | ||
374 | tegra_clone = tegra; | ||
375 | tegra->clk_enabled = false; | ||
376 | |||
377 | tegra->clk = clk_get(&pdev->dev, NULL); | ||
378 | if (IS_ERR(tegra->clk)) { | ||
379 | dev_err(&pdev->dev, "Can't get otg clock\n"); | ||
380 | err = PTR_ERR(tegra->clk); | ||
381 | goto err_clk; | ||
382 | } | ||
383 | |||
384 | err = clk_enable(tegra->clk); | ||
385 | if (err) | ||
386 | goto err_clken; | ||
387 | |||
388 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
389 | if (!res) { | ||
390 | dev_err(&pdev->dev, "Failed to get I/O memory\n"); | ||
391 | err = -ENXIO; | ||
392 | goto err_io; | ||
393 | } | ||
394 | tegra->regs = ioremap(res->start, resource_size(res)); | ||
395 | if (!tegra->regs) { | ||
396 | err = -ENOMEM; | ||
397 | goto err_io; | ||
398 | } | ||
399 | |||
400 | tegra->otg.state = OTG_STATE_A_SUSPEND; | ||
401 | |||
402 | err = otg_set_transceiver(&tegra->otg); | ||
403 | if (err) { | ||
404 | dev_err(&pdev->dev, "can't register transceiver (%d)\n", err); | ||
405 | goto err_otg; | ||
406 | } | ||
407 | |||
408 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
409 | if (!res) { | ||
410 | dev_err(&pdev->dev, "Failed to get IRQ\n"); | ||
411 | err = -ENXIO; | ||
412 | goto err_irq; | ||
413 | } | ||
414 | tegra->irq = res->start; | ||
415 | err = request_threaded_irq(tegra->irq, tegra_otg_irq, | ||
416 | NULL, | ||
417 | IRQF_SHARED, "tegra-otg", tegra); | ||
418 | if (err) { | ||
419 | dev_err(&pdev->dev, "Failed to register IRQ\n"); | ||
420 | goto err_irq; | ||
421 | } | ||
422 | INIT_WORK (&tegra->work, irq_work); | ||
423 | |||
424 | if (!ehci_pdata->default_enable) | ||
425 | clk_disable(tegra->clk); | ||
426 | dev_info(&pdev->dev, "otg transceiver registered\n"); | ||
427 | return 0; | ||
428 | |||
429 | err_irq: | ||
430 | otg_set_transceiver(NULL); | ||
431 | err_otg: | ||
432 | iounmap(tegra->regs); | ||
433 | err_io: | ||
434 | clk_disable(tegra->clk); | ||
435 | err_clken: | ||
436 | clk_put(tegra->clk); | ||
437 | err_clk: | ||
438 | platform_set_drvdata(pdev, NULL); | ||
439 | kfree(tegra); | ||
440 | return err; | ||
441 | } | ||
442 | |||
443 | static int __exit tegra_otg_remove(struct platform_device *pdev) | ||
444 | { | ||
445 | struct tegra_otg_data *tegra = platform_get_drvdata(pdev); | ||
446 | |||
447 | free_irq(tegra->irq, tegra); | ||
448 | otg_set_transceiver(NULL); | ||
449 | iounmap(tegra->regs); | ||
450 | clk_disable(tegra->clk); | ||
451 | clk_put(tegra->clk); | ||
452 | platform_set_drvdata(pdev, NULL); | ||
453 | kfree(tegra); | ||
454 | |||
455 | return 0; | ||
456 | } | ||
457 | |||
458 | #ifdef CONFIG_PM | ||
459 | static int tegra_otg_suspend(struct device *dev) | ||
460 | { | ||
461 | struct platform_device *pdev = to_platform_device(dev); | ||
462 | struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev); | ||
463 | struct otg_transceiver *otg = &tegra_otg->otg; | ||
464 | enum usb_otg_state from = otg->state; | ||
465 | /* store the interupt enable for cable ID and VBUS */ | ||
466 | clk_enable(tegra_otg->clk); | ||
467 | tegra_otg->intr_reg_data = readl(tegra_otg->regs + USB_PHY_WAKEUP); | ||
468 | writel(0, (tegra_otg->regs + USB_PHY_WAKEUP)); | ||
469 | clk_disable(tegra_otg->clk); | ||
470 | |||
471 | if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) { | ||
472 | usb_gadget_vbus_disconnect(otg->gadget); | ||
473 | otg->state = OTG_STATE_A_SUSPEND; | ||
474 | } | ||
475 | tegra_otg_disable_clk(); | ||
476 | return 0; | ||
477 | } | ||
478 | |||
479 | static void tegra_otg_resume(struct device *dev) | ||
480 | { | ||
481 | struct platform_device *pdev = to_platform_device(dev); | ||
482 | struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev); | ||
483 | int val; | ||
484 | unsigned long flags; | ||
485 | |||
486 | tegra_otg_enable_clk(); | ||
487 | |||
488 | /* Following delay is intentional. | ||
489 | * It is placed here after observing system hang. | ||
490 | * Root cause is not confirmed. | ||
491 | */ | ||
492 | msleep(1); | ||
493 | /* restore the interupt enable for cable ID and VBUS */ | ||
494 | clk_enable(tegra_otg->clk); | ||
495 | writel(tegra_otg->intr_reg_data, (tegra_otg->regs + USB_PHY_WAKEUP)); | ||
496 | val = readl(tegra_otg->regs + USB_PHY_WAKEUP); | ||
497 | clk_disable(tegra_otg->clk); | ||
498 | |||
499 | /* A device might be connected while CPU is in sleep mode. In this case no interrupt | ||
500 | * will be triggered | ||
501 | * force irq_work to recheck connected devices | ||
502 | */ | ||
503 | if (!(val & USB_ID_STATUS)) { | ||
504 | spin_lock_irqsave(&tegra_otg->lock, flags); | ||
505 | tegra_otg->int_status = (val | USB_ID_INT_STATUS ); | ||
506 | schedule_work(&tegra_otg->work); | ||
507 | spin_unlock_irqrestore(&tegra_otg->lock, flags); | ||
508 | } | ||
509 | |||
510 | return; | ||
511 | } | ||
512 | |||
513 | static const struct dev_pm_ops tegra_otg_pm_ops = { | ||
514 | .complete = tegra_otg_resume, | ||
515 | .suspend = tegra_otg_suspend, | ||
516 | }; | ||
517 | #endif | ||
518 | |||
519 | static struct platform_driver tegra_otg_driver = { | ||
520 | .driver = { | ||
521 | .name = "tegra-otg", | ||
522 | #ifdef CONFIG_PM | ||
523 | .pm = &tegra_otg_pm_ops, | ||
524 | #endif | ||
525 | }, | ||
526 | .remove = __exit_p(tegra_otg_remove), | ||
527 | .probe = tegra_otg_probe, | ||
528 | }; | ||
529 | |||
530 | static int __init tegra_otg_init(void) | ||
531 | { | ||
532 | return platform_driver_register(&tegra_otg_driver); | ||
533 | } | ||
534 | subsys_initcall(tegra_otg_init); | ||
535 | |||
536 | static void __exit tegra_otg_exit(void) | ||
537 | { | ||
538 | platform_driver_unregister(&tegra_otg_driver); | ||
539 | } | ||
540 | module_exit(tegra_otg_exit); | ||