diff options
Diffstat (limited to 'drivers/usb/otg/twl6030-usb.c')
-rw-r--r-- | drivers/usb/otg/twl6030-usb.c | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/drivers/usb/otg/twl6030-usb.c b/drivers/usb/otg/twl6030-usb.c new file mode 100644 index 000000000000..cfb5aa72b196 --- /dev/null +++ b/drivers/usb/otg/twl6030-usb.c | |||
@@ -0,0 +1,526 @@ | |||
1 | /* | ||
2 | * twl6030_usb - TWL6030 USB transceiver, talking to OMAP OTG driver. | ||
3 | * | ||
4 | * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * Author: Hema HK <hemahk@ti.com> | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/interrupt.h> | ||
26 | #include <linux/platform_device.h> | ||
27 | #include <linux/io.h> | ||
28 | #include <linux/usb/otg.h> | ||
29 | #include <linux/i2c/twl.h> | ||
30 | #include <linux/regulator/consumer.h> | ||
31 | #include <linux/err.h> | ||
32 | #include <linux/notifier.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/delay.h> | ||
35 | |||
36 | /* usb register definitions */ | ||
37 | #define USB_VENDOR_ID_LSB 0x00 | ||
38 | #define USB_VENDOR_ID_MSB 0x01 | ||
39 | #define USB_PRODUCT_ID_LSB 0x02 | ||
40 | #define USB_PRODUCT_ID_MSB 0x03 | ||
41 | #define USB_VBUS_CTRL_SET 0x04 | ||
42 | #define USB_VBUS_CTRL_CLR 0x05 | ||
43 | #define USB_ID_CTRL_SET 0x06 | ||
44 | #define USB_ID_CTRL_CLR 0x07 | ||
45 | #define USB_VBUS_INT_SRC 0x08 | ||
46 | #define USB_VBUS_INT_LATCH_SET 0x09 | ||
47 | #define USB_VBUS_INT_LATCH_CLR 0x0A | ||
48 | #define USB_VBUS_INT_EN_LO_SET 0x0B | ||
49 | #define USB_VBUS_INT_EN_LO_CLR 0x0C | ||
50 | #define USB_VBUS_INT_EN_HI_SET 0x0D | ||
51 | #define USB_VBUS_INT_EN_HI_CLR 0x0E | ||
52 | #define USB_ID_INT_SRC 0x0F | ||
53 | #define USB_ID_INT_LATCH_SET 0x10 | ||
54 | #define USB_ID_INT_LATCH_CLR 0x11 | ||
55 | |||
56 | #define USB_ID_INT_EN_LO_SET 0x12 | ||
57 | #define USB_ID_INT_EN_LO_CLR 0x13 | ||
58 | #define USB_ID_INT_EN_HI_SET 0x14 | ||
59 | #define USB_ID_INT_EN_HI_CLR 0x15 | ||
60 | #define USB_OTG_ADP_CTRL 0x16 | ||
61 | #define USB_OTG_ADP_HIGH 0x17 | ||
62 | #define USB_OTG_ADP_LOW 0x18 | ||
63 | #define USB_OTG_ADP_RISE 0x19 | ||
64 | #define USB_OTG_REVISION 0x1A | ||
65 | |||
66 | /* to be moved to LDO */ | ||
67 | #define TWL6030_MISC2 0xE5 | ||
68 | #define TWL6030_CFG_LDO_PD2 0xF5 | ||
69 | #define TWL6030_BACKUP_REG 0xFA | ||
70 | |||
71 | #define STS_HW_CONDITIONS 0x21 | ||
72 | |||
73 | /* In module TWL6030_MODULE_PM_MASTER */ | ||
74 | #define STS_HW_CONDITIONS 0x21 | ||
75 | #define STS_USB_ID BIT(2) | ||
76 | |||
77 | /* In module TWL6030_MODULE_PM_RECEIVER */ | ||
78 | #define VUSB_CFG_TRANS 0x71 | ||
79 | #define VUSB_CFG_STATE 0x72 | ||
80 | #define VUSB_CFG_VOLTAGE 0x73 | ||
81 | |||
82 | /* in module TWL6030_MODULE_MAIN_CHARGE */ | ||
83 | |||
84 | #define CHARGERUSB_CTRL1 0x8 | ||
85 | |||
86 | #define CONTROLLER_STAT1 0x03 | ||
87 | #define VBUS_DET BIT(2) | ||
88 | |||
89 | struct twl6030_usb { | ||
90 | struct otg_transceiver otg; | ||
91 | struct device *dev; | ||
92 | |||
93 | /* for vbus reporting with irqs disabled */ | ||
94 | spinlock_t lock; | ||
95 | |||
96 | struct regulator *usb3v3; | ||
97 | |||
98 | int irq1; | ||
99 | int irq2; | ||
100 | u8 linkstat; | ||
101 | u8 asleep; | ||
102 | bool irq_enabled; | ||
103 | unsigned long features; | ||
104 | }; | ||
105 | |||
106 | #define xceiv_to_twl(x) container_of((x), struct twl6030_usb, otg) | ||
107 | |||
108 | /*-------------------------------------------------------------------------*/ | ||
109 | |||
110 | static inline int twl6030_writeb(struct twl6030_usb *twl, u8 module, | ||
111 | u8 data, u8 address) | ||
112 | { | ||
113 | int ret = 0; | ||
114 | |||
115 | ret = twl_i2c_write_u8(module, data, address); | ||
116 | if (ret < 0) | ||
117 | dev_err(twl->dev, | ||
118 | "Write[0x%x] Error %d\n", address, ret); | ||
119 | return ret; | ||
120 | } | ||
121 | |||
122 | static inline u8 twl6030_readb(struct twl6030_usb *twl, u8 module, u8 address) | ||
123 | { | ||
124 | u8 data, ret = 0; | ||
125 | |||
126 | ret = twl_i2c_read_u8(module, &data, address); | ||
127 | if (ret >= 0) | ||
128 | ret = data; | ||
129 | else | ||
130 | dev_err(twl->dev, | ||
131 | "readb[0x%x,0x%x] Error %d\n", | ||
132 | module, address, ret); | ||
133 | return ret; | ||
134 | } | ||
135 | |||
136 | /*-------------------------------------------------------------------------*/ | ||
137 | static int twl6030_set_phy_clk(struct otg_transceiver *x, int on) | ||
138 | { | ||
139 | struct twl6030_usb *twl; | ||
140 | struct device *dev; | ||
141 | struct twl4030_usb_data *pdata; | ||
142 | |||
143 | twl = xceiv_to_twl(x); | ||
144 | dev = twl->dev; | ||
145 | pdata = dev->platform_data; | ||
146 | |||
147 | pdata->phy_set_clock(twl->dev, on); | ||
148 | |||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | static int twl6030_phy_init(struct otg_transceiver *x) | ||
153 | { | ||
154 | struct twl6030_usb *twl; | ||
155 | struct device *dev; | ||
156 | struct twl4030_usb_data *pdata; | ||
157 | |||
158 | twl = xceiv_to_twl(x); | ||
159 | dev = twl->dev; | ||
160 | pdata = dev->platform_data; | ||
161 | |||
162 | if (twl->linkstat == USB_EVENT_ID) | ||
163 | pdata->phy_power(twl->dev, 1, 1); | ||
164 | else | ||
165 | pdata->phy_power(twl->dev, 0, 1); | ||
166 | |||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static void twl6030_phy_shutdown(struct otg_transceiver *x) | ||
171 | { | ||
172 | struct twl6030_usb *twl; | ||
173 | struct device *dev; | ||
174 | struct twl4030_usb_data *pdata; | ||
175 | |||
176 | twl = xceiv_to_twl(x); | ||
177 | dev = twl->dev; | ||
178 | pdata = dev->platform_data; | ||
179 | pdata->phy_power(twl->dev, 0, 0); | ||
180 | } | ||
181 | |||
182 | static int twl6030_phy_suspend(struct otg_transceiver *x, int suspend) | ||
183 | { | ||
184 | struct twl6030_usb *twl = xceiv_to_twl(x); | ||
185 | struct device *dev = twl->dev; | ||
186 | struct twl4030_usb_data *pdata = dev->platform_data; | ||
187 | |||
188 | pdata->phy_suspend(dev, suspend); | ||
189 | |||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | static int twl6030_start_srp(struct otg_transceiver *x) | ||
194 | { | ||
195 | struct twl6030_usb *twl = xceiv_to_twl(x); | ||
196 | |||
197 | twl6030_writeb(twl, TWL_MODULE_USB, 0x24, USB_VBUS_CTRL_SET); | ||
198 | twl6030_writeb(twl, TWL_MODULE_USB, 0x84, USB_VBUS_CTRL_SET); | ||
199 | |||
200 | mdelay(100); | ||
201 | twl6030_writeb(twl, TWL_MODULE_USB, 0xa0, USB_VBUS_CTRL_CLR); | ||
202 | |||
203 | return 0; | ||
204 | } | ||
205 | |||
206 | static int twl6030_usb_ldo_init(struct twl6030_usb *twl) | ||
207 | { | ||
208 | char *regulator_name; | ||
209 | |||
210 | if (twl->features & TWL6025_SUBCLASS) | ||
211 | regulator_name = "ldousb"; | ||
212 | else | ||
213 | regulator_name = "vusb"; | ||
214 | |||
215 | /* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */ | ||
216 | twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG); | ||
217 | |||
218 | /* Program CFG_LDO_PD2 register and set VUSB bit */ | ||
219 | twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_CFG_LDO_PD2); | ||
220 | |||
221 | /* Program MISC2 register and set bit VUSB_IN_VBAT */ | ||
222 | twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2); | ||
223 | |||
224 | twl->usb3v3 = regulator_get(twl->dev, regulator_name); | ||
225 | if (IS_ERR(twl->usb3v3)) | ||
226 | return -ENODEV; | ||
227 | |||
228 | /* Program the USB_VBUS_CTRL_SET and set VBUS_ACT_COMP bit */ | ||
229 | twl6030_writeb(twl, TWL_MODULE_USB, 0x4, USB_VBUS_CTRL_SET); | ||
230 | |||
231 | /* | ||
232 | * Program the USB_ID_CTRL_SET register to enable GND drive | ||
233 | * and the ID comparators | ||
234 | */ | ||
235 | twl6030_writeb(twl, TWL_MODULE_USB, 0x14, USB_ID_CTRL_SET); | ||
236 | |||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static ssize_t twl6030_usb_vbus_show(struct device *dev, | ||
241 | struct device_attribute *attr, char *buf) | ||
242 | { | ||
243 | struct twl6030_usb *twl = dev_get_drvdata(dev); | ||
244 | unsigned long flags; | ||
245 | int ret = -EINVAL; | ||
246 | |||
247 | spin_lock_irqsave(&twl->lock, flags); | ||
248 | |||
249 | switch (twl->linkstat) { | ||
250 | case USB_EVENT_VBUS: | ||
251 | ret = snprintf(buf, PAGE_SIZE, "vbus\n"); | ||
252 | break; | ||
253 | case USB_EVENT_ID: | ||
254 | ret = snprintf(buf, PAGE_SIZE, "id\n"); | ||
255 | break; | ||
256 | case USB_EVENT_NONE: | ||
257 | ret = snprintf(buf, PAGE_SIZE, "none\n"); | ||
258 | break; | ||
259 | default: | ||
260 | ret = snprintf(buf, PAGE_SIZE, "UNKNOWN\n"); | ||
261 | } | ||
262 | spin_unlock_irqrestore(&twl->lock, flags); | ||
263 | |||
264 | return ret; | ||
265 | } | ||
266 | static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL); | ||
267 | |||
268 | static irqreturn_t twl6030_usb_irq(int irq, void *_twl) | ||
269 | { | ||
270 | struct twl6030_usb *twl = _twl; | ||
271 | int status; | ||
272 | u8 vbus_state, hw_state; | ||
273 | |||
274 | hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); | ||
275 | |||
276 | vbus_state = twl6030_readb(twl, TWL_MODULE_MAIN_CHARGE, | ||
277 | CONTROLLER_STAT1); | ||
278 | if (!(hw_state & STS_USB_ID)) { | ||
279 | if (vbus_state & VBUS_DET) { | ||
280 | regulator_enable(twl->usb3v3); | ||
281 | twl->asleep = 1; | ||
282 | status = USB_EVENT_VBUS; | ||
283 | twl->otg.default_a = false; | ||
284 | twl->otg.state = OTG_STATE_B_IDLE; | ||
285 | twl->linkstat = status; | ||
286 | twl->otg.last_event = status; | ||
287 | atomic_notifier_call_chain(&twl->otg.notifier, | ||
288 | status, twl->otg.gadget); | ||
289 | } else { | ||
290 | status = USB_EVENT_NONE; | ||
291 | twl->linkstat = status; | ||
292 | twl->otg.last_event = status; | ||
293 | atomic_notifier_call_chain(&twl->otg.notifier, | ||
294 | status, twl->otg.gadget); | ||
295 | if (twl->asleep) { | ||
296 | regulator_disable(twl->usb3v3); | ||
297 | twl->asleep = 0; | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | sysfs_notify(&twl->dev->kobj, NULL, "vbus"); | ||
302 | |||
303 | return IRQ_HANDLED; | ||
304 | } | ||
305 | |||
306 | static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) | ||
307 | { | ||
308 | struct twl6030_usb *twl = _twl; | ||
309 | int status = USB_EVENT_NONE; | ||
310 | u8 hw_state; | ||
311 | |||
312 | hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); | ||
313 | |||
314 | if (hw_state & STS_USB_ID) { | ||
315 | |||
316 | regulator_enable(twl->usb3v3); | ||
317 | twl->asleep = 1; | ||
318 | twl6030_writeb(twl, TWL_MODULE_USB, USB_ID_INT_EN_HI_CLR, 0x1); | ||
319 | twl6030_writeb(twl, TWL_MODULE_USB, USB_ID_INT_EN_HI_SET, | ||
320 | 0x10); | ||
321 | status = USB_EVENT_ID; | ||
322 | twl->otg.default_a = true; | ||
323 | twl->otg.state = OTG_STATE_A_IDLE; | ||
324 | twl->linkstat = status; | ||
325 | twl->otg.last_event = status; | ||
326 | atomic_notifier_call_chain(&twl->otg.notifier, status, | ||
327 | twl->otg.gadget); | ||
328 | } else { | ||
329 | twl6030_writeb(twl, TWL_MODULE_USB, USB_ID_INT_EN_HI_CLR, | ||
330 | 0x10); | ||
331 | twl6030_writeb(twl, TWL_MODULE_USB, USB_ID_INT_EN_HI_SET, | ||
332 | 0x1); | ||
333 | } | ||
334 | twl6030_writeb(twl, TWL_MODULE_USB, USB_ID_INT_LATCH_CLR, status); | ||
335 | |||
336 | return IRQ_HANDLED; | ||
337 | } | ||
338 | |||
339 | static int twl6030_set_peripheral(struct otg_transceiver *x, | ||
340 | struct usb_gadget *gadget) | ||
341 | { | ||
342 | struct twl6030_usb *twl; | ||
343 | |||
344 | if (!x) | ||
345 | return -ENODEV; | ||
346 | |||
347 | twl = xceiv_to_twl(x); | ||
348 | twl->otg.gadget = gadget; | ||
349 | if (!gadget) | ||
350 | twl->otg.state = OTG_STATE_UNDEFINED; | ||
351 | |||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | static int twl6030_enable_irq(struct otg_transceiver *x) | ||
356 | { | ||
357 | struct twl6030_usb *twl = xceiv_to_twl(x); | ||
358 | |||
359 | twl6030_writeb(twl, TWL_MODULE_USB, USB_ID_INT_EN_HI_SET, 0x1); | ||
360 | twl6030_interrupt_unmask(0x05, REG_INT_MSK_LINE_C); | ||
361 | twl6030_interrupt_unmask(0x05, REG_INT_MSK_STS_C); | ||
362 | |||
363 | twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, | ||
364 | REG_INT_MSK_LINE_C); | ||
365 | twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, | ||
366 | REG_INT_MSK_STS_C); | ||
367 | twl6030_usb_irq(twl->irq2, twl); | ||
368 | twl6030_usbotg_irq(twl->irq1, twl); | ||
369 | |||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | static int twl6030_set_vbus(struct otg_transceiver *x, bool enabled) | ||
374 | { | ||
375 | struct twl6030_usb *twl = xceiv_to_twl(x); | ||
376 | |||
377 | /* | ||
378 | * Start driving VBUS. Set OPA_MODE bit in CHARGERUSB_CTRL1 | ||
379 | * register. This enables boost mode. | ||
380 | */ | ||
381 | if (enabled) | ||
382 | twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x40, | ||
383 | CHARGERUSB_CTRL1); | ||
384 | else | ||
385 | twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x00, | ||
386 | CHARGERUSB_CTRL1); | ||
387 | return 0; | ||
388 | } | ||
389 | |||
390 | static int twl6030_set_host(struct otg_transceiver *x, struct usb_bus *host) | ||
391 | { | ||
392 | struct twl6030_usb *twl; | ||
393 | |||
394 | if (!x) | ||
395 | return -ENODEV; | ||
396 | |||
397 | twl = xceiv_to_twl(x); | ||
398 | twl->otg.host = host; | ||
399 | if (!host) | ||
400 | twl->otg.state = OTG_STATE_UNDEFINED; | ||
401 | return 0; | ||
402 | } | ||
403 | |||
404 | static int __devinit twl6030_usb_probe(struct platform_device *pdev) | ||
405 | { | ||
406 | struct twl6030_usb *twl; | ||
407 | int status, err; | ||
408 | struct twl4030_usb_data *pdata; | ||
409 | struct device *dev = &pdev->dev; | ||
410 | pdata = dev->platform_data; | ||
411 | |||
412 | twl = kzalloc(sizeof *twl, GFP_KERNEL); | ||
413 | if (!twl) | ||
414 | return -ENOMEM; | ||
415 | |||
416 | twl->dev = &pdev->dev; | ||
417 | twl->irq1 = platform_get_irq(pdev, 0); | ||
418 | twl->irq2 = platform_get_irq(pdev, 1); | ||
419 | twl->features = pdata->features; | ||
420 | twl->otg.dev = twl->dev; | ||
421 | twl->otg.label = "twl6030"; | ||
422 | twl->otg.set_host = twl6030_set_host; | ||
423 | twl->otg.set_peripheral = twl6030_set_peripheral; | ||
424 | twl->otg.set_vbus = twl6030_set_vbus; | ||
425 | twl->otg.init = twl6030_phy_init; | ||
426 | twl->otg.shutdown = twl6030_phy_shutdown; | ||
427 | twl->otg.set_suspend = twl6030_phy_suspend; | ||
428 | twl->otg.start_srp = twl6030_start_srp; | ||
429 | |||
430 | /* init spinlock for workqueue */ | ||
431 | spin_lock_init(&twl->lock); | ||
432 | |||
433 | err = twl6030_usb_ldo_init(twl); | ||
434 | if (err) { | ||
435 | dev_err(&pdev->dev, "ldo init failed\n"); | ||
436 | kfree(twl); | ||
437 | return err; | ||
438 | } | ||
439 | otg_set_transceiver(&twl->otg); | ||
440 | |||
441 | platform_set_drvdata(pdev, twl); | ||
442 | if (device_create_file(&pdev->dev, &dev_attr_vbus)) | ||
443 | dev_warn(&pdev->dev, "could not create sysfs file\n"); | ||
444 | |||
445 | ATOMIC_INIT_NOTIFIER_HEAD(&twl->otg.notifier); | ||
446 | |||
447 | twl->irq_enabled = true; | ||
448 | status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq, | ||
449 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | ||
450 | "twl6030_usb", twl); | ||
451 | if (status < 0) { | ||
452 | dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", | ||
453 | twl->irq1, status); | ||
454 | device_remove_file(twl->dev, &dev_attr_vbus); | ||
455 | kfree(twl); | ||
456 | return status; | ||
457 | } | ||
458 | |||
459 | status = request_threaded_irq(twl->irq2, NULL, twl6030_usb_irq, | ||
460 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | ||
461 | "twl6030_usb", twl); | ||
462 | if (status < 0) { | ||
463 | dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", | ||
464 | twl->irq2, status); | ||
465 | free_irq(twl->irq1, twl); | ||
466 | device_remove_file(twl->dev, &dev_attr_vbus); | ||
467 | kfree(twl); | ||
468 | return status; | ||
469 | } | ||
470 | |||
471 | twl->asleep = 0; | ||
472 | pdata->phy_init(dev); | ||
473 | twl6030_phy_suspend(&twl->otg, 0); | ||
474 | twl6030_enable_irq(&twl->otg); | ||
475 | dev_info(&pdev->dev, "Initialized TWL6030 USB module\n"); | ||
476 | |||
477 | return 0; | ||
478 | } | ||
479 | |||
480 | static int __exit twl6030_usb_remove(struct platform_device *pdev) | ||
481 | { | ||
482 | struct twl6030_usb *twl = platform_get_drvdata(pdev); | ||
483 | |||
484 | struct twl4030_usb_data *pdata; | ||
485 | struct device *dev = &pdev->dev; | ||
486 | pdata = dev->platform_data; | ||
487 | |||
488 | twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, | ||
489 | REG_INT_MSK_LINE_C); | ||
490 | twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, | ||
491 | REG_INT_MSK_STS_C); | ||
492 | free_irq(twl->irq1, twl); | ||
493 | free_irq(twl->irq2, twl); | ||
494 | regulator_put(twl->usb3v3); | ||
495 | pdata->phy_exit(twl->dev); | ||
496 | device_remove_file(twl->dev, &dev_attr_vbus); | ||
497 | kfree(twl); | ||
498 | |||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | static struct platform_driver twl6030_usb_driver = { | ||
503 | .probe = twl6030_usb_probe, | ||
504 | .remove = __exit_p(twl6030_usb_remove), | ||
505 | .driver = { | ||
506 | .name = "twl6030_usb", | ||
507 | .owner = THIS_MODULE, | ||
508 | }, | ||
509 | }; | ||
510 | |||
511 | static int __init twl6030_usb_init(void) | ||
512 | { | ||
513 | return platform_driver_register(&twl6030_usb_driver); | ||
514 | } | ||
515 | subsys_initcall(twl6030_usb_init); | ||
516 | |||
517 | static void __exit twl6030_usb_exit(void) | ||
518 | { | ||
519 | platform_driver_unregister(&twl6030_usb_driver); | ||
520 | } | ||
521 | module_exit(twl6030_usb_exit); | ||
522 | |||
523 | MODULE_ALIAS("platform:twl6030_usb"); | ||
524 | MODULE_AUTHOR("Hema HK <hemahk@ti.com>"); | ||
525 | MODULE_DESCRIPTION("TWL6030 USB transceiver driver"); | ||
526 | MODULE_LICENSE("GPL"); | ||