diff options
author | Peter Chen <peter.chen@freescale.com> | 2015-02-10 23:44:45 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-03-18 11:19:08 -0400 |
commit | 1f874edcb7318c5dd71025df9f3849715b4e4f71 (patch) | |
tree | 740938fad546965bbe8a371fbd1062fc39046af1 | |
parent | a4cf1b14cfbc57a12ea2d997b93735a99f70d810 (diff) |
usb: chipidea: add runtime power management support
Add runtime power management support.
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/chipidea/ci.h | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 100 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg.c | 2 | ||||
-rw-r--r-- | include/linux/usb/chipidea.h | 1 |
4 files changed, 103 insertions, 6 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 65913d48f0c8..a0aeb8df9280 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h | |||
@@ -169,6 +169,9 @@ struct hw_bank { | |||
169 | * @b_sess_valid_event: indicates there is a vbus event, and handled | 169 | * @b_sess_valid_event: indicates there is a vbus event, and handled |
170 | * at ci_otg_work | 170 | * at ci_otg_work |
171 | * @imx28_write_fix: Freescale imx28 needs swp instruction for writing | 171 | * @imx28_write_fix: Freescale imx28 needs swp instruction for writing |
172 | * @supports_runtime_pm: if runtime pm is supported | ||
173 | * @in_lpm: if the core in low power mode | ||
174 | * @wakeup_int: if wakeup interrupt occur | ||
172 | */ | 175 | */ |
173 | struct ci_hdrc { | 176 | struct ci_hdrc { |
174 | struct device *dev; | 177 | struct device *dev; |
@@ -211,6 +214,9 @@ struct ci_hdrc { | |||
211 | bool id_event; | 214 | bool id_event; |
212 | bool b_sess_valid_event; | 215 | bool b_sess_valid_event; |
213 | bool imx28_write_fix; | 216 | bool imx28_write_fix; |
217 | bool supports_runtime_pm; | ||
218 | bool in_lpm; | ||
219 | bool wakeup_int; | ||
214 | }; | 220 | }; |
215 | 221 | ||
216 | static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) | 222 | static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) |
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index a57dc8866fc5..63d2b398c9a0 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c | |||
@@ -491,6 +491,13 @@ static irqreturn_t ci_irq(int irq, void *data) | |||
491 | irqreturn_t ret = IRQ_NONE; | 491 | irqreturn_t ret = IRQ_NONE; |
492 | u32 otgsc = 0; | 492 | u32 otgsc = 0; |
493 | 493 | ||
494 | if (ci->in_lpm) { | ||
495 | disable_irq_nosync(irq); | ||
496 | ci->wakeup_int = true; | ||
497 | pm_runtime_get(ci->dev); | ||
498 | return IRQ_HANDLED; | ||
499 | } | ||
500 | |||
494 | if (ci->is_otg) { | 501 | if (ci->is_otg) { |
495 | otgsc = hw_read_otgsc(ci, ~0); | 502 | otgsc = hw_read_otgsc(ci, ~0); |
496 | if (ci_otg_is_fsm_mode(ci)) { | 503 | if (ci_otg_is_fsm_mode(ci)) { |
@@ -673,6 +680,8 @@ static int ci_hdrc_probe(struct platform_device *pdev) | |||
673 | ci->platdata = dev_get_platdata(dev); | 680 | ci->platdata = dev_get_platdata(dev); |
674 | ci->imx28_write_fix = !!(ci->platdata->flags & | 681 | ci->imx28_write_fix = !!(ci->platdata->flags & |
675 | CI_HDRC_IMX28_WRITE_FIX); | 682 | CI_HDRC_IMX28_WRITE_FIX); |
683 | ci->supports_runtime_pm = !!(ci->platdata->flags & | ||
684 | CI_HDRC_SUPPORTS_RUNTIME_PM); | ||
676 | 685 | ||
677 | ret = hw_device_init(ci, base); | 686 | ret = hw_device_init(ci, base); |
678 | if (ret < 0) { | 687 | if (ret < 0) { |
@@ -788,6 +797,14 @@ static int ci_hdrc_probe(struct platform_device *pdev) | |||
788 | if (ret) | 797 | if (ret) |
789 | goto stop; | 798 | goto stop; |
790 | 799 | ||
800 | if (ci->supports_runtime_pm) { | ||
801 | pm_runtime_set_active(&pdev->dev); | ||
802 | pm_runtime_enable(&pdev->dev); | ||
803 | pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); | ||
804 | pm_runtime_mark_last_busy(ci->dev); | ||
805 | pm_runtime_use_autosuspend(&pdev->dev); | ||
806 | } | ||
807 | |||
791 | if (ci_otg_is_fsm_mode(ci)) | 808 | if (ci_otg_is_fsm_mode(ci)) |
792 | ci_hdrc_otg_fsm_start(ci); | 809 | ci_hdrc_otg_fsm_start(ci); |
793 | 810 | ||
@@ -807,6 +824,12 @@ static int ci_hdrc_remove(struct platform_device *pdev) | |||
807 | { | 824 | { |
808 | struct ci_hdrc *ci = platform_get_drvdata(pdev); | 825 | struct ci_hdrc *ci = platform_get_drvdata(pdev); |
809 | 826 | ||
827 | if (ci->supports_runtime_pm) { | ||
828 | pm_runtime_get_sync(&pdev->dev); | ||
829 | pm_runtime_disable(&pdev->dev); | ||
830 | pm_runtime_put_noidle(&pdev->dev); | ||
831 | } | ||
832 | |||
810 | dbg_remove_files(ci); | 833 | dbg_remove_files(ci); |
811 | ci_role_destroy(ci); | 834 | ci_role_destroy(ci); |
812 | ci_hdrc_enter_lpm(ci, true); | 835 | ci_hdrc_enter_lpm(ci, true); |
@@ -815,13 +838,14 @@ static int ci_hdrc_remove(struct platform_device *pdev) | |||
815 | return 0; | 838 | return 0; |
816 | } | 839 | } |
817 | 840 | ||
818 | #ifdef CONFIG_PM_SLEEP | 841 | #ifdef CONFIG_PM |
819 | static void ci_controller_suspend(struct ci_hdrc *ci) | 842 | static void ci_controller_suspend(struct ci_hdrc *ci) |
820 | { | 843 | { |
844 | disable_irq(ci->irq); | ||
821 | ci_hdrc_enter_lpm(ci, true); | 845 | ci_hdrc_enter_lpm(ci, true); |
822 | 846 | usb_phy_set_suspend(ci->usb_phy, 1); | |
823 | if (ci->usb_phy) | 847 | ci->in_lpm = true; |
824 | usb_phy_set_suspend(ci->usb_phy, 1); | 848 | enable_irq(ci->irq); |
825 | } | 849 | } |
826 | 850 | ||
827 | static int ci_controller_resume(struct device *dev) | 851 | static int ci_controller_resume(struct device *dev) |
@@ -830,23 +854,49 @@ static int ci_controller_resume(struct device *dev) | |||
830 | 854 | ||
831 | dev_dbg(dev, "at %s\n", __func__); | 855 | dev_dbg(dev, "at %s\n", __func__); |
832 | 856 | ||
833 | ci_hdrc_enter_lpm(ci, false); | 857 | if (!ci->in_lpm) { |
858 | WARN_ON(1); | ||
859 | return 0; | ||
860 | } | ||
834 | 861 | ||
862 | ci_hdrc_enter_lpm(ci, false); | ||
835 | if (ci->usb_phy) { | 863 | if (ci->usb_phy) { |
836 | usb_phy_set_suspend(ci->usb_phy, 0); | 864 | usb_phy_set_suspend(ci->usb_phy, 0); |
837 | usb_phy_set_wakeup(ci->usb_phy, false); | 865 | usb_phy_set_wakeup(ci->usb_phy, false); |
838 | hw_wait_phy_stable(); | 866 | hw_wait_phy_stable(); |
839 | } | 867 | } |
840 | 868 | ||
869 | ci->in_lpm = false; | ||
870 | if (ci->wakeup_int) { | ||
871 | ci->wakeup_int = false; | ||
872 | pm_runtime_mark_last_busy(ci->dev); | ||
873 | pm_runtime_put_autosuspend(ci->dev); | ||
874 | enable_irq(ci->irq); | ||
875 | } | ||
876 | |||
841 | return 0; | 877 | return 0; |
842 | } | 878 | } |
843 | 879 | ||
880 | #ifdef CONFIG_PM_SLEEP | ||
844 | static int ci_suspend(struct device *dev) | 881 | static int ci_suspend(struct device *dev) |
845 | { | 882 | { |
846 | struct ci_hdrc *ci = dev_get_drvdata(dev); | 883 | struct ci_hdrc *ci = dev_get_drvdata(dev); |
847 | 884 | ||
848 | if (ci->wq) | 885 | if (ci->wq) |
849 | flush_workqueue(ci->wq); | 886 | flush_workqueue(ci->wq); |
887 | /* | ||
888 | * Controller needs to be active during suspend, otherwise the core | ||
889 | * may run resume when the parent is at suspend if other driver's | ||
890 | * suspend fails, it occurs before parent's suspend has not started, | ||
891 | * but the core suspend has finished. | ||
892 | */ | ||
893 | if (ci->in_lpm) | ||
894 | pm_runtime_resume(dev); | ||
895 | |||
896 | if (ci->in_lpm) { | ||
897 | WARN_ON(1); | ||
898 | return 0; | ||
899 | } | ||
850 | 900 | ||
851 | ci_controller_suspend(ci); | 901 | ci_controller_suspend(ci); |
852 | 902 | ||
@@ -855,13 +905,51 @@ static int ci_suspend(struct device *dev) | |||
855 | 905 | ||
856 | static int ci_resume(struct device *dev) | 906 | static int ci_resume(struct device *dev) |
857 | { | 907 | { |
858 | return ci_controller_resume(dev); | 908 | struct ci_hdrc *ci = dev_get_drvdata(dev); |
909 | int ret; | ||
910 | |||
911 | ret = ci_controller_resume(dev); | ||
912 | if (ret) | ||
913 | return ret; | ||
914 | |||
915 | if (ci->supports_runtime_pm) { | ||
916 | pm_runtime_disable(dev); | ||
917 | pm_runtime_set_active(dev); | ||
918 | pm_runtime_enable(dev); | ||
919 | } | ||
920 | |||
921 | return ret; | ||
859 | } | 922 | } |
860 | #endif /* CONFIG_PM_SLEEP */ | 923 | #endif /* CONFIG_PM_SLEEP */ |
861 | 924 | ||
925 | static int ci_runtime_suspend(struct device *dev) | ||
926 | { | ||
927 | struct ci_hdrc *ci = dev_get_drvdata(dev); | ||
928 | |||
929 | dev_dbg(dev, "at %s\n", __func__); | ||
930 | |||
931 | if (ci->in_lpm) { | ||
932 | WARN_ON(1); | ||
933 | return 0; | ||
934 | } | ||
935 | |||
936 | usb_phy_set_wakeup(ci->usb_phy, true); | ||
937 | ci_controller_suspend(ci); | ||
938 | |||
939 | return 0; | ||
940 | } | ||
941 | |||
942 | static int ci_runtime_resume(struct device *dev) | ||
943 | { | ||
944 | return ci_controller_resume(dev); | ||
945 | } | ||
946 | |||
947 | #endif /* CONFIG_PM */ | ||
862 | static const struct dev_pm_ops ci_pm_ops = { | 948 | static const struct dev_pm_ops ci_pm_ops = { |
863 | SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume) | 949 | SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume) |
950 | SET_RUNTIME_PM_OPS(ci_runtime_suspend, ci_runtime_resume, NULL) | ||
864 | }; | 951 | }; |
952 | |||
865 | static struct platform_driver ci_hdrc_driver = { | 953 | static struct platform_driver ci_hdrc_driver = { |
866 | .probe = ci_hdrc_probe, | 954 | .probe = ci_hdrc_probe, |
867 | .remove = ci_hdrc_remove, | 955 | .remove = ci_hdrc_remove, |
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index a048b08b9d4d..ad6c87a4653c 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c | |||
@@ -96,6 +96,7 @@ static void ci_otg_work(struct work_struct *work) | |||
96 | return; | 96 | return; |
97 | } | 97 | } |
98 | 98 | ||
99 | pm_runtime_get_sync(ci->dev); | ||
99 | if (ci->id_event) { | 100 | if (ci->id_event) { |
100 | ci->id_event = false; | 101 | ci->id_event = false; |
101 | ci_handle_id_switch(ci); | 102 | ci_handle_id_switch(ci); |
@@ -104,6 +105,7 @@ static void ci_otg_work(struct work_struct *work) | |||
104 | ci_handle_vbus_change(ci); | 105 | ci_handle_vbus_change(ci); |
105 | } else | 106 | } else |
106 | dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); | 107 | dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); |
108 | pm_runtime_put_sync(ci->dev); | ||
107 | 109 | ||
108 | enable_irq(ci->irq); | 110 | enable_irq(ci->irq); |
109 | } | 111 | } |
diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index 535997a6681b..39ba00f0d1d5 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h | |||
@@ -19,6 +19,7 @@ struct ci_hdrc_platform_data { | |||
19 | enum usb_phy_interface phy_mode; | 19 | enum usb_phy_interface phy_mode; |
20 | unsigned long flags; | 20 | unsigned long flags; |
21 | #define CI_HDRC_REGS_SHARED BIT(0) | 21 | #define CI_HDRC_REGS_SHARED BIT(0) |
22 | #define CI_HDRC_SUPPORTS_RUNTIME_PM BIT(2) | ||
22 | #define CI_HDRC_DISABLE_STREAMING BIT(3) | 23 | #define CI_HDRC_DISABLE_STREAMING BIT(3) |
23 | /* | 24 | /* |
24 | * Only set it when DCCPARAMS.DC==1 and DCCPARAMS.HC==1, | 25 | * Only set it when DCCPARAMS.DC==1 and DCCPARAMS.HC==1, |