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