diff options
author | Li Jun <b47624@freescale.com> | 2015-02-11 07:45:03 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-03-18 18:19:12 +0300 |
commit | 961ea496facda611eeb153d8133a4d40055e56ca (patch) | |
tree | da1267b53586d89f2510dcdd27b636315ac7eb6e /drivers/usb/chipidea | |
parent | 6594591741883e004aaba105951337878698b054 (diff) | |
download | linux-961ea496facda611eeb153d8133a4d40055e56ca.tar.xz |
usb: chipidea: support runtime power management for otg fsm mode
This patch adds runtime power management support for otg fsm mode, since
A-device in a_idle state cannot detect data pulse irq after suspended, here
enable wakeup by connection before suspend to make it can be resumed by DP;
and handle wakeup from that state like SRP.
Signed-off-by: Li Jun <jun.li@freescale.com>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r-- | drivers/usb/chipidea/bits.h | 1 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 43 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg_fsm.c | 22 |
3 files changed, 59 insertions, 7 deletions
diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index e69424d6e423..3cb9bda51ddf 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -63,6 +63,7 @@ #define PORTSC_HSP BIT(9) #define PORTSC_PP BIT(12) #define PORTSC_PTC (0x0FUL << 16) +#define PORTSC_WKCN BIT(20) #define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23)) /* PTS and PTW for non lpm version only */ #define PORTSC_PFSC BIT(24) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 4b22d7cb6557..74fea4fa41b1 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -798,11 +798,11 @@ static int ci_hdrc_probe(struct platform_device *pdev) : CI_ROLE_GADGET; } - /* only update vbus status for peripheral */ - if (ci->role == CI_ROLE_GADGET) - ci_handle_vbus_change(ci); - if (!ci_otg_is_fsm_mode(ci)) { + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", @@ -861,6 +861,33 @@ static int ci_hdrc_remove(struct platform_device *pdev) } #ifdef CONFIG_PM +/* Prepare wakeup by SRP before suspend */ +static void ci_otg_fsm_suspend_for_srp(struct ci_hdrc *ci) +{ + if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && + !hw_read_otgsc(ci, OTGSC_ID)) { + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, + PORTSC_PP); + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_WKCN, + PORTSC_WKCN); + } +} + +/* Handle SRP when wakeup by data pulse */ +static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci) +{ + if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && + (ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) { + if (!hw_read_otgsc(ci, OTGSC_ID)) { + ci->fsm.a_srp_det = 1; + ci->fsm.a_bus_drop = 0; + } else { + ci->fsm.id = 1; + } + ci_otg_queue_work(ci); + } +} + static void ci_controller_suspend(struct ci_hdrc *ci) { disable_irq(ci->irq); @@ -894,6 +921,8 @@ static int ci_controller_resume(struct device *dev) pm_runtime_mark_last_busy(ci->dev); pm_runtime_put_autosuspend(ci->dev); enable_irq(ci->irq); + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_wakeup_by_srp(ci); } return 0; @@ -921,6 +950,9 @@ static int ci_suspend(struct device *dev) } if (device_may_wakeup(dev)) { + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_suspend_for_srp(ci); + usb_phy_set_wakeup(ci->usb_phy, true); enable_irq_wake(ci->irq); } @@ -963,6 +995,9 @@ static int ci_runtime_suspend(struct device *dev) return 0; } + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_suspend_for_srp(ci); + usb_phy_set_wakeup(ci->usb_phy, true); ci_controller_suspend(ci); diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 562e581f6765..e3cf5be66d3d 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -225,6 +225,9 @@ static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) return; } + if (list_empty(active_timers)) + pm_runtime_get(ci->dev); + timer->count = timer->expires; list_add_tail(&timer->list, active_timers); @@ -241,17 +244,22 @@ static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) struct ci_otg_fsm_timer *tmp_timer, *del_tmp; struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; struct list_head *active_timers = &ci->fsm_timer->active_timers; + int flag = 0; if (t >= NUM_CI_OTG_FSM_TIMERS) return; list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) - if (tmp_timer == timer) + if (tmp_timer == timer) { list_del(&timer->list); + flag = 1; + } /* Disable 1ms irq if there is no any active timer */ - if (list_empty(active_timers)) + if (list_empty(active_timers) && (flag == 1)) { hw_write_otgsc(ci, OTGSC_1MSIE, 0); + pm_runtime_put(ci->dev); + } } /* @@ -275,8 +283,10 @@ static inline int ci_otg_tick_timer(struct ci_hdrc *ci) } /* disable 1ms irq if there is no any timer active */ - if ((expired == 1) && list_empty(active_timers)) + if ((expired == 1) && list_empty(active_timers)) { hw_write_otgsc(ci, OTGSC_1MSIE, 0); + pm_runtime_put(ci->dev); + } return expired; } @@ -585,6 +595,7 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) ci->fsm.otg->state < OTG_STATE_A_IDLE) return 0; + pm_runtime_get_sync(ci->dev); if (otg_statemachine(&ci->fsm)) { if (ci->fsm.otg->state == OTG_STATE_A_IDLE) { /* @@ -609,8 +620,13 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) */ ci_otg_queue_work(ci); } + } else if (ci->fsm.otg->state == OTG_STATE_A_HOST) { + pm_runtime_mark_last_busy(ci->dev); + pm_runtime_put_autosuspend(ci->dev); + return 0; } } + pm_runtime_put_sync(ci->dev); return 0; } |