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