diff options
Diffstat (limited to 'drivers/phy')
-rw-r--r-- | drivers/phy/phy-twl4030-usb.c | 88 |
1 files changed, 63 insertions, 25 deletions
diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c index e1a6623d4696..0cb872bdbe7e 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/phy-twl4030-usb.c | |||
@@ -34,6 +34,7 @@ | |||
34 | #include <linux/delay.h> | 34 | #include <linux/delay.h> |
35 | #include <linux/usb/otg.h> | 35 | #include <linux/usb/otg.h> |
36 | #include <linux/phy/phy.h> | 36 | #include <linux/phy/phy.h> |
37 | #include <linux/pm_runtime.h> | ||
37 | #include <linux/usb/musb-omap.h> | 38 | #include <linux/usb/musb-omap.h> |
38 | #include <linux/usb/ulpi.h> | 39 | #include <linux/usb/ulpi.h> |
39 | #include <linux/i2c/twl.h> | 40 | #include <linux/i2c/twl.h> |
@@ -422,37 +423,55 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on) | |||
422 | } | 423 | } |
423 | } | 424 | } |
424 | 425 | ||
425 | static int twl4030_phy_power_off(struct phy *phy) | 426 | static int twl4030_usb_runtime_suspend(struct device *dev) |
426 | { | 427 | { |
427 | struct twl4030_usb *twl = phy_get_drvdata(phy); | 428 | struct twl4030_usb *twl = dev_get_drvdata(dev); |
428 | 429 | ||
430 | dev_dbg(twl->dev, "%s\n", __func__); | ||
429 | if (twl->asleep) | 431 | if (twl->asleep) |
430 | return 0; | 432 | return 0; |
431 | 433 | ||
432 | twl4030_phy_power(twl, 0); | 434 | twl4030_phy_power(twl, 0); |
433 | twl->asleep = 1; | 435 | twl->asleep = 1; |
434 | dev_dbg(twl->dev, "%s\n", __func__); | 436 | |
435 | return 0; | 437 | return 0; |
436 | } | 438 | } |
437 | 439 | ||
438 | static void __twl4030_phy_power_on(struct twl4030_usb *twl) | 440 | static int twl4030_usb_runtime_resume(struct device *dev) |
439 | { | 441 | { |
442 | struct twl4030_usb *twl = dev_get_drvdata(dev); | ||
443 | |||
444 | dev_dbg(twl->dev, "%s\n", __func__); | ||
445 | if (!twl->asleep) | ||
446 | return 0; | ||
447 | |||
440 | twl4030_phy_power(twl, 1); | 448 | twl4030_phy_power(twl, 1); |
441 | twl4030_i2c_access(twl, 1); | 449 | twl->asleep = 0; |
442 | twl4030_usb_set_mode(twl, twl->usb_mode); | 450 | |
443 | if (twl->usb_mode == T2_USB_MODE_ULPI) | 451 | return 0; |
444 | twl4030_i2c_access(twl, 0); | 452 | } |
453 | |||
454 | static int twl4030_phy_power_off(struct phy *phy) | ||
455 | { | ||
456 | struct twl4030_usb *twl = phy_get_drvdata(phy); | ||
457 | |||
458 | dev_dbg(twl->dev, "%s\n", __func__); | ||
459 | pm_runtime_mark_last_busy(twl->dev); | ||
460 | pm_runtime_put_autosuspend(twl->dev); | ||
461 | |||
462 | return 0; | ||
445 | } | 463 | } |
446 | 464 | ||
447 | static int twl4030_phy_power_on(struct phy *phy) | 465 | static int twl4030_phy_power_on(struct phy *phy) |
448 | { | 466 | { |
449 | struct twl4030_usb *twl = phy_get_drvdata(phy); | 467 | struct twl4030_usb *twl = phy_get_drvdata(phy); |
450 | 468 | ||
451 | if (!twl->asleep) | ||
452 | return 0; | ||
453 | __twl4030_phy_power_on(twl); | ||
454 | twl->asleep = 0; | ||
455 | dev_dbg(twl->dev, "%s\n", __func__); | 469 | dev_dbg(twl->dev, "%s\n", __func__); |
470 | pm_runtime_get_sync(twl->dev); | ||
471 | twl4030_i2c_access(twl, 1); | ||
472 | twl4030_usb_set_mode(twl, twl->usb_mode); | ||
473 | if (twl->usb_mode == T2_USB_MODE_ULPI) | ||
474 | twl4030_i2c_access(twl, 0); | ||
456 | 475 | ||
457 | /* | 476 | /* |
458 | * XXX When VBUS gets driven after musb goes to A mode, | 477 | * XXX When VBUS gets driven after musb goes to A mode, |
@@ -558,6 +577,16 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) | |||
558 | * USB_LINK_VBUS state. musb_hdrc won't care until it | 577 | * USB_LINK_VBUS state. musb_hdrc won't care until it |
559 | * starts to handle softconnect right. | 578 | * starts to handle softconnect right. |
560 | */ | 579 | */ |
580 | if ((status == OMAP_MUSB_VBUS_VALID) || | ||
581 | (status == OMAP_MUSB_ID_GROUND)) { | ||
582 | if (twl->asleep) | ||
583 | pm_runtime_get_sync(twl->dev); | ||
584 | } else { | ||
585 | if (!twl->asleep) { | ||
586 | pm_runtime_mark_last_busy(twl->dev); | ||
587 | pm_runtime_put_autosuspend(twl->dev); | ||
588 | } | ||
589 | } | ||
561 | omap_musb_mailbox(status); | 590 | omap_musb_mailbox(status); |
562 | } | 591 | } |
563 | sysfs_notify(&twl->dev->kobj, NULL, "vbus"); | 592 | sysfs_notify(&twl->dev->kobj, NULL, "vbus"); |
@@ -599,22 +628,17 @@ static int twl4030_phy_init(struct phy *phy) | |||
599 | struct twl4030_usb *twl = phy_get_drvdata(phy); | 628 | struct twl4030_usb *twl = phy_get_drvdata(phy); |
600 | enum omap_musb_vbus_id_status status; | 629 | enum omap_musb_vbus_id_status status; |
601 | 630 | ||
602 | /* | 631 | pm_runtime_get_sync(twl->dev); |
603 | * Start in sleep state, we'll get called through set_suspend() | ||
604 | * callback when musb is runtime resumed and it's time to start. | ||
605 | */ | ||
606 | __twl4030_phy_power(twl, 0); | ||
607 | twl->asleep = 1; | ||
608 | |||
609 | status = twl4030_usb_linkstat(twl); | 632 | status = twl4030_usb_linkstat(twl); |
610 | twl->linkstat = status; | 633 | twl->linkstat = status; |
611 | 634 | ||
612 | if (status == OMAP_MUSB_ID_GROUND || status == OMAP_MUSB_VBUS_VALID) { | 635 | if (status == OMAP_MUSB_ID_GROUND || status == OMAP_MUSB_VBUS_VALID) |
613 | omap_musb_mailbox(twl->linkstat); | 636 | omap_musb_mailbox(twl->linkstat); |
614 | twl4030_phy_power_on(phy); | ||
615 | } | ||
616 | 637 | ||
617 | sysfs_notify(&twl->dev->kobj, NULL, "vbus"); | 638 | sysfs_notify(&twl->dev->kobj, NULL, "vbus"); |
639 | pm_runtime_mark_last_busy(twl->dev); | ||
640 | pm_runtime_put_autosuspend(twl->dev); | ||
641 | |||
618 | return 0; | 642 | return 0; |
619 | } | 643 | } |
620 | 644 | ||
@@ -650,6 +674,11 @@ static const struct phy_ops ops = { | |||
650 | .owner = THIS_MODULE, | 674 | .owner = THIS_MODULE, |
651 | }; | 675 | }; |
652 | 676 | ||
677 | static const struct dev_pm_ops twl4030_usb_pm_ops = { | ||
678 | SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend, | ||
679 | twl4030_usb_runtime_resume, NULL) | ||
680 | }; | ||
681 | |||
653 | static int twl4030_usb_probe(struct platform_device *pdev) | 682 | static int twl4030_usb_probe(struct platform_device *pdev) |
654 | { | 683 | { |
655 | struct twl4030_usb_data *pdata = dev_get_platdata(&pdev->dev); | 684 | struct twl4030_usb_data *pdata = dev_get_platdata(&pdev->dev); |
@@ -726,6 +755,11 @@ static int twl4030_usb_probe(struct platform_device *pdev) | |||
726 | 755 | ||
727 | ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier); | 756 | ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier); |
728 | 757 | ||
758 | pm_runtime_use_autosuspend(&pdev->dev); | ||
759 | pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); | ||
760 | pm_runtime_enable(&pdev->dev); | ||
761 | pm_runtime_get_sync(&pdev->dev); | ||
762 | |||
729 | /* Our job is to use irqs and status from the power module | 763 | /* Our job is to use irqs and status from the power module |
730 | * to keep the transceiver disabled when nothing's connected. | 764 | * to keep the transceiver disabled when nothing's connected. |
731 | * | 765 | * |
@@ -744,6 +778,9 @@ static int twl4030_usb_probe(struct platform_device *pdev) | |||
744 | return status; | 778 | return status; |
745 | } | 779 | } |
746 | 780 | ||
781 | pm_runtime_mark_last_busy(&pdev->dev); | ||
782 | pm_runtime_put_autosuspend(twl->dev); | ||
783 | |||
747 | dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); | 784 | dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); |
748 | return 0; | 785 | return 0; |
749 | } | 786 | } |
@@ -753,6 +790,7 @@ static int twl4030_usb_remove(struct platform_device *pdev) | |||
753 | struct twl4030_usb *twl = platform_get_drvdata(pdev); | 790 | struct twl4030_usb *twl = platform_get_drvdata(pdev); |
754 | int val; | 791 | int val; |
755 | 792 | ||
793 | pm_runtime_get_sync(twl->dev); | ||
756 | cancel_delayed_work(&twl->id_workaround_work); | 794 | cancel_delayed_work(&twl->id_workaround_work); |
757 | device_remove_file(twl->dev, &dev_attr_vbus); | 795 | device_remove_file(twl->dev, &dev_attr_vbus); |
758 | 796 | ||
@@ -772,9 +810,8 @@ static int twl4030_usb_remove(struct platform_device *pdev) | |||
772 | 810 | ||
773 | /* disable complete OTG block */ | 811 | /* disable complete OTG block */ |
774 | twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); | 812 | twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); |
775 | 813 | pm_runtime_mark_last_busy(twl->dev); | |
776 | if (!twl->asleep) | 814 | pm_runtime_put(twl->dev); |
777 | twl4030_phy_power(twl, 0); | ||
778 | 815 | ||
779 | return 0; | 816 | return 0; |
780 | } | 817 | } |
@@ -792,6 +829,7 @@ static struct platform_driver twl4030_usb_driver = { | |||
792 | .remove = twl4030_usb_remove, | 829 | .remove = twl4030_usb_remove, |
793 | .driver = { | 830 | .driver = { |
794 | .name = "twl4030_usb", | 831 | .name = "twl4030_usb", |
832 | .pm = &twl4030_usb_pm_ops, | ||
795 | .owner = THIS_MODULE, | 833 | .owner = THIS_MODULE, |
796 | .of_match_table = of_match_ptr(twl4030_usb_id_table), | 834 | .of_match_table = of_match_ptr(twl4030_usb_id_table), |
797 | }, | 835 | }, |