diff options
Diffstat (limited to 'drivers/usb/phy/phy-twl6030-usb.c')
-rw-r--r-- | drivers/usb/phy/phy-twl6030-usb.c | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c new file mode 100644 index 000000000000..9de7ada90a8b --- /dev/null +++ b/drivers/usb/phy/phy-twl6030-usb.c | |||
@@ -0,0 +1,453 @@ | |||
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/musb-omap.h> | ||
29 | #include <linux/usb/phy_companion.h> | ||
30 | #include <linux/usb/omap_usb.h> | ||
31 | #include <linux/i2c/twl.h> | ||
32 | #include <linux/regulator/consumer.h> | ||
33 | #include <linux/err.h> | ||
34 | #include <linux/slab.h> | ||
35 | #include <linux/delay.h> | ||
36 | |||
37 | /* usb register definitions */ | ||
38 | #define USB_VENDOR_ID_LSB 0x00 | ||
39 | #define USB_VENDOR_ID_MSB 0x01 | ||
40 | #define USB_PRODUCT_ID_LSB 0x02 | ||
41 | #define USB_PRODUCT_ID_MSB 0x03 | ||
42 | #define USB_VBUS_CTRL_SET 0x04 | ||
43 | #define USB_VBUS_CTRL_CLR 0x05 | ||
44 | #define USB_ID_CTRL_SET 0x06 | ||
45 | #define USB_ID_CTRL_CLR 0x07 | ||
46 | #define USB_VBUS_INT_SRC 0x08 | ||
47 | #define USB_VBUS_INT_LATCH_SET 0x09 | ||
48 | #define USB_VBUS_INT_LATCH_CLR 0x0A | ||
49 | #define USB_VBUS_INT_EN_LO_SET 0x0B | ||
50 | #define USB_VBUS_INT_EN_LO_CLR 0x0C | ||
51 | #define USB_VBUS_INT_EN_HI_SET 0x0D | ||
52 | #define USB_VBUS_INT_EN_HI_CLR 0x0E | ||
53 | #define USB_ID_INT_SRC 0x0F | ||
54 | #define USB_ID_INT_LATCH_SET 0x10 | ||
55 | #define USB_ID_INT_LATCH_CLR 0x11 | ||
56 | |||
57 | #define USB_ID_INT_EN_LO_SET 0x12 | ||
58 | #define USB_ID_INT_EN_LO_CLR 0x13 | ||
59 | #define USB_ID_INT_EN_HI_SET 0x14 | ||
60 | #define USB_ID_INT_EN_HI_CLR 0x15 | ||
61 | #define USB_OTG_ADP_CTRL 0x16 | ||
62 | #define USB_OTG_ADP_HIGH 0x17 | ||
63 | #define USB_OTG_ADP_LOW 0x18 | ||
64 | #define USB_OTG_ADP_RISE 0x19 | ||
65 | #define USB_OTG_REVISION 0x1A | ||
66 | |||
67 | /* to be moved to LDO */ | ||
68 | #define TWL6030_MISC2 0xE5 | ||
69 | #define TWL6030_CFG_LDO_PD2 0xF5 | ||
70 | #define TWL6030_BACKUP_REG 0xFA | ||
71 | |||
72 | #define STS_HW_CONDITIONS 0x21 | ||
73 | |||
74 | /* In module TWL6030_MODULE_PM_MASTER */ | ||
75 | #define STS_HW_CONDITIONS 0x21 | ||
76 | #define STS_USB_ID BIT(2) | ||
77 | |||
78 | /* In module TWL6030_MODULE_PM_RECEIVER */ | ||
79 | #define VUSB_CFG_TRANS 0x71 | ||
80 | #define VUSB_CFG_STATE 0x72 | ||
81 | #define VUSB_CFG_VOLTAGE 0x73 | ||
82 | |||
83 | /* in module TWL6030_MODULE_MAIN_CHARGE */ | ||
84 | |||
85 | #define CHARGERUSB_CTRL1 0x8 | ||
86 | |||
87 | #define CONTROLLER_STAT1 0x03 | ||
88 | #define VBUS_DET BIT(2) | ||
89 | |||
90 | struct twl6030_usb { | ||
91 | struct phy_companion comparator; | ||
92 | struct device *dev; | ||
93 | |||
94 | /* for vbus reporting with irqs disabled */ | ||
95 | spinlock_t lock; | ||
96 | |||
97 | struct regulator *usb3v3; | ||
98 | |||
99 | /* used to set vbus, in atomic path */ | ||
100 | struct work_struct set_vbus_work; | ||
101 | |||
102 | int irq1; | ||
103 | int irq2; | ||
104 | enum omap_musb_vbus_id_status linkstat; | ||
105 | u8 asleep; | ||
106 | bool irq_enabled; | ||
107 | bool vbus_enable; | ||
108 | const char *regulator; | ||
109 | }; | ||
110 | |||
111 | #define comparator_to_twl(x) container_of((x), struct twl6030_usb, comparator) | ||
112 | |||
113 | /*-------------------------------------------------------------------------*/ | ||
114 | |||
115 | static inline int twl6030_writeb(struct twl6030_usb *twl, u8 module, | ||
116 | u8 data, u8 address) | ||
117 | { | ||
118 | int ret = 0; | ||
119 | |||
120 | ret = twl_i2c_write_u8(module, data, address); | ||
121 | if (ret < 0) | ||
122 | dev_err(twl->dev, | ||
123 | "Write[0x%x] Error %d\n", address, ret); | ||
124 | return ret; | ||
125 | } | ||
126 | |||
127 | static inline u8 twl6030_readb(struct twl6030_usb *twl, u8 module, u8 address) | ||
128 | { | ||
129 | u8 data, ret = 0; | ||
130 | |||
131 | ret = twl_i2c_read_u8(module, &data, address); | ||
132 | if (ret >= 0) | ||
133 | ret = data; | ||
134 | else | ||
135 | dev_err(twl->dev, | ||
136 | "readb[0x%x,0x%x] Error %d\n", | ||
137 | module, address, ret); | ||
138 | return ret; | ||
139 | } | ||
140 | |||
141 | static int twl6030_start_srp(struct phy_companion *comparator) | ||
142 | { | ||
143 | struct twl6030_usb *twl = comparator_to_twl(comparator); | ||
144 | |||
145 | twl6030_writeb(twl, TWL_MODULE_USB, 0x24, USB_VBUS_CTRL_SET); | ||
146 | twl6030_writeb(twl, TWL_MODULE_USB, 0x84, USB_VBUS_CTRL_SET); | ||
147 | |||
148 | mdelay(100); | ||
149 | twl6030_writeb(twl, TWL_MODULE_USB, 0xa0, USB_VBUS_CTRL_CLR); | ||
150 | |||
151 | return 0; | ||
152 | } | ||
153 | |||
154 | static int twl6030_usb_ldo_init(struct twl6030_usb *twl) | ||
155 | { | ||
156 | /* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */ | ||
157 | twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG); | ||
158 | |||
159 | /* Program CFG_LDO_PD2 register and set VUSB bit */ | ||
160 | twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_CFG_LDO_PD2); | ||
161 | |||
162 | /* Program MISC2 register and set bit VUSB_IN_VBAT */ | ||
163 | twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2); | ||
164 | |||
165 | twl->usb3v3 = regulator_get(twl->dev, twl->regulator); | ||
166 | if (IS_ERR(twl->usb3v3)) | ||
167 | return -ENODEV; | ||
168 | |||
169 | /* Program the USB_VBUS_CTRL_SET and set VBUS_ACT_COMP bit */ | ||
170 | twl6030_writeb(twl, TWL_MODULE_USB, 0x4, USB_VBUS_CTRL_SET); | ||
171 | |||
172 | /* | ||
173 | * Program the USB_ID_CTRL_SET register to enable GND drive | ||
174 | * and the ID comparators | ||
175 | */ | ||
176 | twl6030_writeb(twl, TWL_MODULE_USB, 0x14, USB_ID_CTRL_SET); | ||
177 | |||
178 | return 0; | ||
179 | } | ||
180 | |||
181 | static ssize_t twl6030_usb_vbus_show(struct device *dev, | ||
182 | struct device_attribute *attr, char *buf) | ||
183 | { | ||
184 | struct twl6030_usb *twl = dev_get_drvdata(dev); | ||
185 | unsigned long flags; | ||
186 | int ret = -EINVAL; | ||
187 | |||
188 | spin_lock_irqsave(&twl->lock, flags); | ||
189 | |||
190 | switch (twl->linkstat) { | ||
191 | case OMAP_MUSB_VBUS_VALID: | ||
192 | ret = snprintf(buf, PAGE_SIZE, "vbus\n"); | ||
193 | break; | ||
194 | case OMAP_MUSB_ID_GROUND: | ||
195 | ret = snprintf(buf, PAGE_SIZE, "id\n"); | ||
196 | break; | ||
197 | case OMAP_MUSB_VBUS_OFF: | ||
198 | ret = snprintf(buf, PAGE_SIZE, "none\n"); | ||
199 | break; | ||
200 | default: | ||
201 | ret = snprintf(buf, PAGE_SIZE, "UNKNOWN\n"); | ||
202 | } | ||
203 | spin_unlock_irqrestore(&twl->lock, flags); | ||
204 | |||
205 | return ret; | ||
206 | } | ||
207 | static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL); | ||
208 | |||
209 | static irqreturn_t twl6030_usb_irq(int irq, void *_twl) | ||
210 | { | ||
211 | struct twl6030_usb *twl = _twl; | ||
212 | enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; | ||
213 | u8 vbus_state, hw_state; | ||
214 | int ret; | ||
215 | |||
216 | hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); | ||
217 | |||
218 | vbus_state = twl6030_readb(twl, TWL_MODULE_MAIN_CHARGE, | ||
219 | CONTROLLER_STAT1); | ||
220 | if (!(hw_state & STS_USB_ID)) { | ||
221 | if (vbus_state & VBUS_DET) { | ||
222 | ret = regulator_enable(twl->usb3v3); | ||
223 | if (ret) | ||
224 | dev_err(twl->dev, "Failed to enable usb3v3\n"); | ||
225 | |||
226 | twl->asleep = 1; | ||
227 | status = OMAP_MUSB_VBUS_VALID; | ||
228 | twl->linkstat = status; | ||
229 | omap_musb_mailbox(status); | ||
230 | } else { | ||
231 | if (twl->linkstat != OMAP_MUSB_UNKNOWN) { | ||
232 | status = OMAP_MUSB_VBUS_OFF; | ||
233 | twl->linkstat = status; | ||
234 | omap_musb_mailbox(status); | ||
235 | if (twl->asleep) { | ||
236 | regulator_disable(twl->usb3v3); | ||
237 | twl->asleep = 0; | ||
238 | } | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | sysfs_notify(&twl->dev->kobj, NULL, "vbus"); | ||
243 | |||
244 | return IRQ_HANDLED; | ||
245 | } | ||
246 | |||
247 | static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) | ||
248 | { | ||
249 | struct twl6030_usb *twl = _twl; | ||
250 | enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; | ||
251 | u8 hw_state; | ||
252 | int ret; | ||
253 | |||
254 | hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); | ||
255 | |||
256 | if (hw_state & STS_USB_ID) { | ||
257 | ret = regulator_enable(twl->usb3v3); | ||
258 | if (ret) | ||
259 | dev_err(twl->dev, "Failed to enable usb3v3\n"); | ||
260 | |||
261 | twl->asleep = 1; | ||
262 | twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_CLR); | ||
263 | twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_SET); | ||
264 | status = OMAP_MUSB_ID_GROUND; | ||
265 | twl->linkstat = status; | ||
266 | omap_musb_mailbox(status); | ||
267 | } else { | ||
268 | twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_CLR); | ||
269 | twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); | ||
270 | } | ||
271 | twl6030_writeb(twl, TWL_MODULE_USB, status, USB_ID_INT_LATCH_CLR); | ||
272 | |||
273 | return IRQ_HANDLED; | ||
274 | } | ||
275 | |||
276 | static int twl6030_enable_irq(struct twl6030_usb *twl) | ||
277 | { | ||
278 | twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); | ||
279 | twl6030_interrupt_unmask(0x05, REG_INT_MSK_LINE_C); | ||
280 | twl6030_interrupt_unmask(0x05, REG_INT_MSK_STS_C); | ||
281 | |||
282 | twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, | ||
283 | REG_INT_MSK_LINE_C); | ||
284 | twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, | ||
285 | REG_INT_MSK_STS_C); | ||
286 | twl6030_usb_irq(twl->irq2, twl); | ||
287 | twl6030_usbotg_irq(twl->irq1, twl); | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | static void otg_set_vbus_work(struct work_struct *data) | ||
293 | { | ||
294 | struct twl6030_usb *twl = container_of(data, struct twl6030_usb, | ||
295 | set_vbus_work); | ||
296 | |||
297 | /* | ||
298 | * Start driving VBUS. Set OPA_MODE bit in CHARGERUSB_CTRL1 | ||
299 | * register. This enables boost mode. | ||
300 | */ | ||
301 | |||
302 | if (twl->vbus_enable) | ||
303 | twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x40, | ||
304 | CHARGERUSB_CTRL1); | ||
305 | else | ||
306 | twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x00, | ||
307 | CHARGERUSB_CTRL1); | ||
308 | } | ||
309 | |||
310 | static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled) | ||
311 | { | ||
312 | struct twl6030_usb *twl = comparator_to_twl(comparator); | ||
313 | |||
314 | twl->vbus_enable = enabled; | ||
315 | schedule_work(&twl->set_vbus_work); | ||
316 | |||
317 | return 0; | ||
318 | } | ||
319 | |||
320 | static int twl6030_usb_probe(struct platform_device *pdev) | ||
321 | { | ||
322 | u32 ret; | ||
323 | struct twl6030_usb *twl; | ||
324 | int status, err; | ||
325 | struct device_node *np = pdev->dev.of_node; | ||
326 | struct device *dev = &pdev->dev; | ||
327 | struct twl4030_usb_data *pdata = dev->platform_data; | ||
328 | |||
329 | twl = devm_kzalloc(dev, sizeof *twl, GFP_KERNEL); | ||
330 | if (!twl) | ||
331 | return -ENOMEM; | ||
332 | |||
333 | twl->dev = &pdev->dev; | ||
334 | twl->irq1 = platform_get_irq(pdev, 0); | ||
335 | twl->irq2 = platform_get_irq(pdev, 1); | ||
336 | twl->linkstat = OMAP_MUSB_UNKNOWN; | ||
337 | |||
338 | twl->comparator.set_vbus = twl6030_set_vbus; | ||
339 | twl->comparator.start_srp = twl6030_start_srp; | ||
340 | |||
341 | ret = omap_usb2_set_comparator(&twl->comparator); | ||
342 | if (ret == -ENODEV) { | ||
343 | dev_info(&pdev->dev, "phy not ready, deferring probe"); | ||
344 | return -EPROBE_DEFER; | ||
345 | } | ||
346 | |||
347 | if (np) { | ||
348 | twl->regulator = "usb"; | ||
349 | } else if (pdata) { | ||
350 | if (pdata->features & TWL6025_SUBCLASS) | ||
351 | twl->regulator = "ldousb"; | ||
352 | else | ||
353 | twl->regulator = "vusb"; | ||
354 | } else { | ||
355 | dev_err(&pdev->dev, "twl6030 initialized without pdata\n"); | ||
356 | return -EINVAL; | ||
357 | } | ||
358 | |||
359 | /* init spinlock for workqueue */ | ||
360 | spin_lock_init(&twl->lock); | ||
361 | |||
362 | err = twl6030_usb_ldo_init(twl); | ||
363 | if (err) { | ||
364 | dev_err(&pdev->dev, "ldo init failed\n"); | ||
365 | return err; | ||
366 | } | ||
367 | |||
368 | platform_set_drvdata(pdev, twl); | ||
369 | if (device_create_file(&pdev->dev, &dev_attr_vbus)) | ||
370 | dev_warn(&pdev->dev, "could not create sysfs file\n"); | ||
371 | |||
372 | INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work); | ||
373 | |||
374 | twl->irq_enabled = true; | ||
375 | status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq, | ||
376 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | ||
377 | "twl6030_usb", twl); | ||
378 | if (status < 0) { | ||
379 | dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", | ||
380 | twl->irq1, status); | ||
381 | device_remove_file(twl->dev, &dev_attr_vbus); | ||
382 | return status; | ||
383 | } | ||
384 | |||
385 | status = request_threaded_irq(twl->irq2, NULL, twl6030_usb_irq, | ||
386 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | ||
387 | "twl6030_usb", twl); | ||
388 | if (status < 0) { | ||
389 | dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", | ||
390 | twl->irq2, status); | ||
391 | free_irq(twl->irq1, twl); | ||
392 | device_remove_file(twl->dev, &dev_attr_vbus); | ||
393 | return status; | ||
394 | } | ||
395 | |||
396 | twl->asleep = 0; | ||
397 | twl6030_enable_irq(twl); | ||
398 | dev_info(&pdev->dev, "Initialized TWL6030 USB module\n"); | ||
399 | |||
400 | return 0; | ||
401 | } | ||
402 | |||
403 | static int twl6030_usb_remove(struct platform_device *pdev) | ||
404 | { | ||
405 | struct twl6030_usb *twl = platform_get_drvdata(pdev); | ||
406 | |||
407 | twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, | ||
408 | REG_INT_MSK_LINE_C); | ||
409 | twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, | ||
410 | REG_INT_MSK_STS_C); | ||
411 | free_irq(twl->irq1, twl); | ||
412 | free_irq(twl->irq2, twl); | ||
413 | regulator_put(twl->usb3v3); | ||
414 | device_remove_file(twl->dev, &dev_attr_vbus); | ||
415 | cancel_work_sync(&twl->set_vbus_work); | ||
416 | |||
417 | return 0; | ||
418 | } | ||
419 | |||
420 | #ifdef CONFIG_OF | ||
421 | static const struct of_device_id twl6030_usb_id_table[] = { | ||
422 | { .compatible = "ti,twl6030-usb" }, | ||
423 | {} | ||
424 | }; | ||
425 | MODULE_DEVICE_TABLE(of, twl6030_usb_id_table); | ||
426 | #endif | ||
427 | |||
428 | static struct platform_driver twl6030_usb_driver = { | ||
429 | .probe = twl6030_usb_probe, | ||
430 | .remove = twl6030_usb_remove, | ||
431 | .driver = { | ||
432 | .name = "twl6030_usb", | ||
433 | .owner = THIS_MODULE, | ||
434 | .of_match_table = of_match_ptr(twl6030_usb_id_table), | ||
435 | }, | ||
436 | }; | ||
437 | |||
438 | static int __init twl6030_usb_init(void) | ||
439 | { | ||
440 | return platform_driver_register(&twl6030_usb_driver); | ||
441 | } | ||
442 | subsys_initcall(twl6030_usb_init); | ||
443 | |||
444 | static void __exit twl6030_usb_exit(void) | ||
445 | { | ||
446 | platform_driver_unregister(&twl6030_usb_driver); | ||
447 | } | ||
448 | module_exit(twl6030_usb_exit); | ||
449 | |||
450 | MODULE_ALIAS("platform:twl6030_usb"); | ||
451 | MODULE_AUTHOR("Hema HK <hemahk@ti.com>"); | ||
452 | MODULE_DESCRIPTION("TWL6030 USB transceiver driver"); | ||
453 | MODULE_LICENSE("GPL"); | ||