diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 8cfc837c97a3..02e0ff9e0d85 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -263,6 +263,7 @@ struct ci_hdrc { u32 pm_configured_flag; u32 pm_portsc; u32 pm_usbmode; + struct work_struct power_lost_work; }; 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 0027e0d86272..d13b4b9c91cc 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -780,6 +780,50 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) } } +static enum ci_role ci_get_role(struct ci_hdrc *ci) +{ + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { + if (ci->is_otg) { + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); + return ci_otg_role(ci); + } else { + /* + * If the controller is not OTG capable, but support + * role switch, the defalt role is gadget, and the + * user can switch it through debugfs. + */ + return CI_ROLE_GADGET; + } + } else { + return ci->roles[CI_ROLE_HOST] + ? CI_ROLE_HOST + : CI_ROLE_GADGET; + } +} + +static void ci_start_new_role(struct ci_hdrc *ci) +{ + enum ci_role role = ci_get_role(ci); + + if (ci->role != role) + ci_handle_id_switch(ci); + + if (role == CI_ROLE_GADGET) + ci_handle_vbus_connected(ci); +} + +static void ci_power_lost_work(struct work_struct *work) +{ + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, + power_lost_work); + + pm_runtime_get_sync(ci->dev); + if (!ci_otg_is_fsm_mode(ci)) + ci_start_new_role(ci); + pm_runtime_put_sync(ci->dev); + enable_irq(ci->irq); +} + static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -883,25 +927,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) } } - if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { - if (ci->is_otg) { - ci->role = ci_otg_role(ci); - /* Enable ID change irq */ - hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); - } else { - /* - * If the controller is not OTG capable, but support - * role switch, the defalt role is gadget, and the - * user can switch it through debugfs. - */ - ci->role = CI_ROLE_GADGET; - } - } else { - ci->role = ci->roles[CI_ROLE_HOST] - ? CI_ROLE_HOST - : CI_ROLE_GADGET; - } - + ci->role = ci_get_role(ci); /* only update vbus status for peripheral */ if (ci->role == CI_ROLE_GADGET) ci_handle_vbus_connected(ci); @@ -934,6 +960,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) device_set_wakeup_capable(&pdev->dev, true); + /* Init workqueue for controller power lost handling */ + INIT_WORK(&ci->power_lost_work, ci_power_lost_work); + ret = dbg_create_files(ci); if (!ret) return 0; @@ -1092,10 +1121,21 @@ static int ci_resume(struct device *dev) if (ret) return ret; + if (power_lost) { + /* shutdown and re-init for phy */ + ci_usb_phy_exit(ci); + ci_usb_phy_init(ci); + } + /* Extra routine per role after system resume */ if (ci->role != CI_ROLE_END && ci_role(ci)->resume) ci_role(ci)->resume(ci, power_lost); + if (power_lost) { + disable_irq_nosync(ci->irq); + schedule_work(&ci->power_lost_work); + } + if (ci->supports_runtime_pm) { pm_runtime_disable(dev); pm_runtime_set_active(dev);