diff options
| author | Ming Lei <tom.leiming@gmail.com> | 2010-09-06 11:27:09 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-09-20 19:05:00 -0400 |
| commit | fc8f2a7608d855b911e35a33e771e6358c705c43 (patch) | |
| tree | 27c603b3234177c4a580ae3797ee56c45b528968 | |
| parent | fc9282506114d4be188a464af2d373db31dd781c (diff) | |
USB: otg: twl4030: fix phy initialization(v1)
Commit 461c317705eca5cac09a360f488715927fd0a927(into 2.6.36-v3)
is put forward to power down phy if no usb cable is connected,
but does introduce the two issues below:
1), phy is not into work state if usb cable is connected
with PC during poweron, so musb device mode is not usable
in such case, follows the reasons:
-twl4030_phy_resume is not called, so
regulators are not enabled
i2c access are not enabled
usb mode not configurated
2), The kernel warings[1] of regulators 'unbalanced disables'
is caused if poweron without usb cable connected
with PC or b-device.
This patch fixes the two issues above:
-power down phy only if no usb cable is connected with PC
and b-device
-do phy initialization(via __twl4030_phy_resume) if usb cable
is connected with PC(vbus event) or another b-device(ID event) in
twl4030_usb_probe.
This patch also doesn't put VUSB3V1 LDO into active mode in
twl4030_usb_ldo_init until VBUS/ID change detected, so we can
save more power consumption than before.
This patch is verified OK on Beagle board either connected with
usb cable or not when poweron.
[1]. warnings of 'unbalanced disables' of regulators.
[root@OMAP3EVM /]# dmesg
------------[ cut here ]------------
WARNING: at drivers/regulator/core.c:1357 _regulator_disable+0x38/0x128()
unbalanced disables for VUSB1V8
Modules linked in:
Backtrace:
[<c0030c48>] (dump_backtrace+0x0/0x110) from [<c034f5a8>] (dump_stack+0x18/0x1c)
r7:c78179d8 r6:c01ed6b8 r5:c0410822 r4:0000054d
[<c034f590>] (dump_stack+0x0/0x1c) from [<c0057da8>] (warn_slowpath_common+0x54/0x6c)
[<c0057d54>] (warn_slowpath_common+0x0/0x6c) from [<c0057e64>] (warn_slowpath_fmt+0x38/0x40)
r9:00000000 r8:00000000 r7:c78e6608 r6:00000000 r5:fffffffb
r4:c78e6c00
[<c0057e2c>] (warn_slowpath_fmt+0x0/0x40) from [<c01ed6b8>] (_regulator_disable+0x38/0x128)
r3:c0410e53 r2:c0410ad5
[<c01ed680>] (_regulator_disable+0x0/0x128) from [<c01ed87c>] (regulator_disable+0x24/0x38)
r7:c78e6608 r6:00000000 r5:c78e6c40 r4:c78e6c00
[<c01ed858>] (regulator_disable+0x0/0x38) from [<c02382dc>] (twl4030_phy_power+0x15c/0x17c)
r5:c78595c0 r4:00000000
[<c0238180>] (twl4030_phy_power+0x0/0x17c) from [<c023831c>] (twl4030_phy_suspend+0x20/0x2c)
r6:00000000 r5:c78595c0 r4:c78595c0
[<c02382fc>] (twl4030_phy_suspend+0x0/0x2c) from [<c0238638>] (twl4030_usb_irq+0x11c/0x16c)
r5:c78595c0 r4:00000040
[<c023851c>] (twl4030_usb_irq+0x0/0x16c) from [<c034ec18>] (twl4030_usb_probe+0x2c4/0x32c)
r6:00000000 r5:00000000 r4:c78595c0
[<c034e954>] (twl4030_usb_probe+0x0/0x32c) from [<c02152a0>] (platform_drv_probe+0x20/0x24)
r7:00000000 r6:c047d49c r5:c78e6608 r4:c047d49c
[<c0215280>] (platform_drv_probe+0x0/0x24) from [<c0214244>] (driver_probe_device+0xd0/0x190)
[<c0214174>] (driver_probe_device+0x0/0x190) from [<c02143d4>] (__device_attach+0x44/0x48)
r7:00000000 r6:c78e6608 r5:c78e6608 r4:c047d49c
[<c0214390>] (__device_attach+0x0/0x48) from [<c0213694>] (bus_for_each_drv+0x50/0x90)
r5:c0214390 r4:00000000
[<c0213644>] (bus_for_each_drv+0x0/0x90) from [<c0214474>] (device_attach+0x70/0x94)
r6:c78e663c r5:c78e6608 r4:c78e6608
[<c0214404>] (device_attach+0x0/0x94) from [<c02134fc>] (bus_probe_device+0x2c/0x48)
r7:00000000 r6:00000002 r5:c78e6608 r4:c78e6600
[<c02134d0>] (bus_probe_device+0x0/0x48) from [<c0211e48>] (device_add+0x340/0x4b4)
[<c0211b08>] (device_add+0x0/0x4b4) from [<c021597c>] (platform_device_add+0x110/0x16c)
[<c021586c>] (platform_device_add+0x0/0x16c) from [<c0220cb0>] (add_numbered_child+0xd8/0x118)
r7:00000000 r6:c045f15c r5:c78e6600 r4:00000000
[<c0220bd8>] (add_numbered_child+0x0/0x118) from [<c001c618>] (twl_probe+0x3a4/0x72c)
[<c001c274>] (twl_probe+0x0/0x72c) from [<c02601ac>] (i2c_device_probe+0x7c/0xa4)
[<c0260130>] (i2c_device_probe+0x0/0xa4) from [<c0214244>] (driver_probe_device+0xd0/0x190)
r5:c7856e20 r4:c047c860
[<c0214174>] (driver_probe_device+0x0/0x190) from [<c02143d4>] (__device_attach+0x44/0x48)
r7:c7856e04 r6:c7856e20 r5:c7856e20 r4:c047c860
[<c0214390>] (__device_attach+0x0/0x48) from [<c0213694>] (bus_for_each_drv+0x50/0x90)
r5:c0214390 r4:00000000
[<c0213644>] (bus_for_each_drv+0x0/0x90) from [<c0214474>] (device_attach+0x70/0x94)
r6:c7856e54 r5:c7856e20 r4:c7856e20
[<c0214404>] (device_attach+0x0/0x94) from [<c02134fc>] (bus_probe_device+0x2c/0x48)
r7:c7856e04 r6:c78fd048 r5:c7856e20 r4:c7856e20
[<c02134d0>] (bus_probe_device+0x0/0x48) from [<c0211e48>] (device_add+0x340/0x4b4)
[<c0211b08>] (device_add+0x0/0x4b4) from [<c0211fd8>] (device_register+0x1c/0x20)
[<c0211fbc>] (device_register+0x0/0x20) from [<c0260aa8>] (i2c_new_device+0xec/0x150)
r5:c7856e00 r4:c7856e20
[<c02609bc>] (i2c_new_device+0x0/0x150) from [<c0260dc0>] (i2c_register_adapter+0xa0/0x1c4)
r7:00000000 r6:c78fd078 r5:c78fd048 r4:c781d5c0
[<c0260d20>] (i2c_register_adapter+0x0/0x1c4) from [<c0260f80>] (i2c_add_numbered_adapter+0x9c/0xb4)
r7:00000a28 r6:c04600a8 r5:c78fd048 r4:00000000
[<c0260ee4>] (i2c_add_numbered_adapter+0x0/0xb4) from [<c034efa4>] (omap_i2c_probe+0x324/0x3e8)
r5:00000000 r4:c78fd000
[<c034ec80>] (omap_i2c_probe+0x0/0x3e8) from [<c02152a0>] (platform_drv_probe+0x20/0x24)
[<c0215280>] (platform_drv_probe+0x0/0x24) from [<c0214244>] (driver_probe_device+0xd0/0x190)
[<c0214174>] (driver_probe_device+0x0/0x190) from [<c021436c>] (__driver_attach+0x68/0x8c)
r7:c78b2140 r6:c047e214 r5:c04600e4 r4:c04600b0
[<c0214304>] (__driver_attach+0x0/0x8c) from [<c021399c>] (bus_for_each_dev+0x50/0x84)
r7:c78b2140 r6:c047e214 r5:c0214304 r4:00000000
[<c021394c>] (bus_for_each_dev+0x0/0x84) from [<c0214068>] (driver_attach+0x20/0x28)
r6:c047e214 r5:c047e214 r4:c00270d0
[<c0214048>] (driver_attach+0x0/0x28) from [<c0213274>] (bus_add_driver+0xa8/0x228)
[<c02131cc>] (bus_add_driver+0x0/0x228) from [<c02146a4>] (driver_register+0xb0/0x13c)
[<c02145f4>] (driver_register+0x0/0x13c) from [<c0215744>] (platform_driver_register+0x4c/0x60)
r9:00000000 r8:c001f688 r7:00000013 r6:c005b6fc r5:c00083dc
r4:c00270d0
[<c02156f8>] (platform_driver_register+0x0/0x60) from [<c001f69c>] (omap_i2c_init_driver+0x14/0x1c)
[<c001f688>] (omap_i2c_init_driver+0x0/0x1c) from [<c002c460>] (do_one_initcall+0xd0/0x1a4)
[<c002c390>] (do_one_initcall+0x0/0x1a4) from [<c0008478>] (kernel_init+0x9c/0x154)
[<c00083dc>] (kernel_init+0x0/0x154) from [<c005b6fc>] (do_exit+0x0/0x688)
r5:c00083dc r4:00000000
---[ end trace 1b75b31a2719ed1d ]---
Signed-off-by: Ming Lei <tom.leiming@gmail.com>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: Felipe Balbi <me@felipebalbi.com>
Cc: Anand Gadiyar <gadiyar@ti.com>
Cc: Mike Frysinger <vapier@gentoo.org>
Cc: Sergei Shtylyov <sshtylyov@ru.mvista.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
| -rw-r--r-- | drivers/usb/otg/twl4030-usb.c | 78 |
1 files changed, 51 insertions, 27 deletions
diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c index 05aaac1c3861..0bc97698af15 100644 --- a/drivers/usb/otg/twl4030-usb.c +++ b/drivers/usb/otg/twl4030-usb.c | |||
| @@ -347,11 +347,20 @@ static void twl4030_i2c_access(struct twl4030_usb *twl, int on) | |||
| 347 | } | 347 | } |
| 348 | } | 348 | } |
| 349 | 349 | ||
| 350 | static void twl4030_phy_power(struct twl4030_usb *twl, int on) | 350 | static void __twl4030_phy_power(struct twl4030_usb *twl, int on) |
| 351 | { | 351 | { |
| 352 | u8 pwr; | 352 | u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); |
| 353 | |||
| 354 | if (on) | ||
| 355 | pwr &= ~PHY_PWR_PHYPWD; | ||
| 356 | else | ||
| 357 | pwr |= PHY_PWR_PHYPWD; | ||
| 353 | 358 | ||
| 354 | pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); | 359 | WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); |
| 360 | } | ||
| 361 | |||
| 362 | static void twl4030_phy_power(struct twl4030_usb *twl, int on) | ||
| 363 | { | ||
| 355 | if (on) { | 364 | if (on) { |
| 356 | regulator_enable(twl->usb3v1); | 365 | regulator_enable(twl->usb3v1); |
| 357 | regulator_enable(twl->usb1v8); | 366 | regulator_enable(twl->usb1v8); |
| @@ -365,15 +374,13 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on) | |||
| 365 | twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, | 374 | twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, |
| 366 | VUSB_DEDICATED2); | 375 | VUSB_DEDICATED2); |
| 367 | regulator_enable(twl->usb1v5); | 376 | regulator_enable(twl->usb1v5); |
| 368 | pwr &= ~PHY_PWR_PHYPWD; | 377 | __twl4030_phy_power(twl, 1); |
| 369 | WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); | ||
| 370 | twl4030_usb_write(twl, PHY_CLK_CTRL, | 378 | twl4030_usb_write(twl, PHY_CLK_CTRL, |
| 371 | twl4030_usb_read(twl, PHY_CLK_CTRL) | | 379 | twl4030_usb_read(twl, PHY_CLK_CTRL) | |
| 372 | (PHY_CLK_CTRL_CLOCKGATING_EN | | 380 | (PHY_CLK_CTRL_CLOCKGATING_EN | |
| 373 | PHY_CLK_CTRL_CLK32K_EN)); | 381 | PHY_CLK_CTRL_CLK32K_EN)); |
| 374 | } else { | 382 | } else { |
| 375 | pwr |= PHY_PWR_PHYPWD; | 383 | __twl4030_phy_power(twl, 0); |
| 376 | WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); | ||
| 377 | regulator_disable(twl->usb1v5); | 384 | regulator_disable(twl->usb1v5); |
| 378 | regulator_disable(twl->usb1v8); | 385 | regulator_disable(twl->usb1v8); |
| 379 | regulator_disable(twl->usb3v1); | 386 | regulator_disable(twl->usb3v1); |
| @@ -387,19 +394,25 @@ static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off) | |||
| 387 | 394 | ||
| 388 | twl4030_phy_power(twl, 0); | 395 | twl4030_phy_power(twl, 0); |
| 389 | twl->asleep = 1; | 396 | twl->asleep = 1; |
| 397 | dev_dbg(twl->dev, "%s\n", __func__); | ||
| 390 | } | 398 | } |
| 391 | 399 | ||
| 392 | static void twl4030_phy_resume(struct twl4030_usb *twl) | 400 | static void __twl4030_phy_resume(struct twl4030_usb *twl) |
| 393 | { | 401 | { |
| 394 | if (!twl->asleep) | ||
| 395 | return; | ||
| 396 | |||
| 397 | twl4030_phy_power(twl, 1); | 402 | twl4030_phy_power(twl, 1); |
| 398 | twl4030_i2c_access(twl, 1); | 403 | twl4030_i2c_access(twl, 1); |
| 399 | twl4030_usb_set_mode(twl, twl->usb_mode); | 404 | twl4030_usb_set_mode(twl, twl->usb_mode); |
| 400 | if (twl->usb_mode == T2_USB_MODE_ULPI) | 405 | if (twl->usb_mode == T2_USB_MODE_ULPI) |
| 401 | twl4030_i2c_access(twl, 0); | 406 | twl4030_i2c_access(twl, 0); |
| 407 | } | ||
| 408 | |||
| 409 | static void twl4030_phy_resume(struct twl4030_usb *twl) | ||
| 410 | { | ||
| 411 | if (!twl->asleep) | ||
| 412 | return; | ||
| 413 | __twl4030_phy_resume(twl); | ||
| 402 | twl->asleep = 0; | 414 | twl->asleep = 0; |
| 415 | dev_dbg(twl->dev, "%s\n", __func__); | ||
| 403 | } | 416 | } |
| 404 | 417 | ||
| 405 | static int twl4030_usb_ldo_init(struct twl4030_usb *twl) | 418 | static int twl4030_usb_ldo_init(struct twl4030_usb *twl) |
| @@ -408,8 +421,8 @@ static int twl4030_usb_ldo_init(struct twl4030_usb *twl) | |||
| 408 | twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0xC0, PROTECT_KEY); | 421 | twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0xC0, PROTECT_KEY); |
| 409 | twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0x0C, PROTECT_KEY); | 422 | twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0x0C, PROTECT_KEY); |
| 410 | 423 | ||
| 411 | /* put VUSB3V1 LDO in active state */ | 424 | /* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/ |
| 412 | twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); | 425 | /*twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/ |
| 413 | 426 | ||
| 414 | /* input to VUSB3V1 LDO is from VBAT, not VBUS */ | 427 | /* input to VUSB3V1 LDO is from VBAT, not VBUS */ |
| 415 | twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); | 428 | twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); |
| @@ -502,6 +515,26 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) | |||
| 502 | return IRQ_HANDLED; | 515 | return IRQ_HANDLED; |
| 503 | } | 516 | } |
| 504 | 517 | ||
| 518 | static void twl4030_usb_phy_init(struct twl4030_usb *twl) | ||
| 519 | { | ||
| 520 | int status; | ||
| 521 | |||
| 522 | status = twl4030_usb_linkstat(twl); | ||
| 523 | if (status >= 0) { | ||
| 524 | if (status == USB_EVENT_NONE) { | ||
| 525 | __twl4030_phy_power(twl, 0); | ||
| 526 | twl->asleep = 1; | ||
| 527 | } else { | ||
| 528 | __twl4030_phy_resume(twl); | ||
| 529 | twl->asleep = 0; | ||
| 530 | } | ||
| 531 | |||
| 532 | blocking_notifier_call_chain(&twl->otg.notifier, status, | ||
| 533 | twl->otg.gadget); | ||
| 534 | } | ||
| 535 | sysfs_notify(&twl->dev->kobj, NULL, "vbus"); | ||
| 536 | } | ||
| 537 | |||
| 505 | static int twl4030_set_suspend(struct otg_transceiver *x, int suspend) | 538 | static int twl4030_set_suspend(struct otg_transceiver *x, int suspend) |
| 506 | { | 539 | { |
| 507 | struct twl4030_usb *twl = xceiv_to_twl(x); | 540 | struct twl4030_usb *twl = xceiv_to_twl(x); |
| @@ -550,7 +583,6 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev) | |||
| 550 | struct twl4030_usb_data *pdata = pdev->dev.platform_data; | 583 | struct twl4030_usb_data *pdata = pdev->dev.platform_data; |
| 551 | struct twl4030_usb *twl; | 584 | struct twl4030_usb *twl; |
| 552 | int status, err; | 585 | int status, err; |
| 553 | u8 pwr; | ||
| 554 | 586 | ||
| 555 | if (!pdata) { | 587 | if (!pdata) { |
| 556 | dev_dbg(&pdev->dev, "platform_data not available\n"); | 588 | dev_dbg(&pdev->dev, "platform_data not available\n"); |
| @@ -569,10 +601,7 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev) | |||
| 569 | twl->otg.set_peripheral = twl4030_set_peripheral; | 601 | twl->otg.set_peripheral = twl4030_set_peripheral; |
| 570 | twl->otg.set_suspend = twl4030_set_suspend; | 602 | twl->otg.set_suspend = twl4030_set_suspend; |
| 571 | twl->usb_mode = pdata->usb_mode; | 603 | twl->usb_mode = pdata->usb_mode; |
| 572 | 604 | twl->asleep = 1; | |
| 573 | pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); | ||
| 574 | |||
| 575 | twl->asleep = (pwr & PHY_PWR_PHYPWD); | ||
| 576 | 605 | ||
| 577 | /* init spinlock for workqueue */ | 606 | /* init spinlock for workqueue */ |
| 578 | spin_lock_init(&twl->lock); | 607 | spin_lock_init(&twl->lock); |
| @@ -610,15 +639,10 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev) | |||
| 610 | return status; | 639 | return status; |
| 611 | } | 640 | } |
| 612 | 641 | ||
| 613 | /* The IRQ handler just handles changes from the previous states | 642 | /* Power down phy or make it work according to |
| 614 | * of the ID and VBUS pins ... in probe() we must initialize that | 643 | * current link state. |
| 615 | * previous state. The easy way: fake an IRQ. | ||
| 616 | * | ||
| 617 | * REVISIT: a real IRQ might have happened already, if PREEMPT is | ||
| 618 | * enabled. Else the IRQ may not yet be configured or enabled, | ||
| 619 | * because of scheduling delays. | ||
| 620 | */ | 644 | */ |
| 621 | twl4030_usb_irq(twl->irq, twl); | 645 | twl4030_usb_phy_init(twl); |
| 622 | 646 | ||
| 623 | dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); | 647 | dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); |
| 624 | return 0; | 648 | return 0; |
