Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: chipidea: core: add controller resume support when controller is powered off

For some SoCs, the controler's power will be off during the system
suspend, and it needs some recovery operation to let the system back
to workable. We add this support in this patch.

Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
Acked-by: Peter Chen <peter.chen@kernel.org>
Link: https://lore.kernel.org/r/20221013151442.3262951-2-xu.yang_2@nxp.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Xu Yang and committed by
Greg Kroah-Hartman
74494b33 caa7b744

+63 -20
+61 -19
drivers/usb/chipidea/core.c
··· 637 637 return 0; 638 638 } 639 639 640 + static enum ci_role ci_get_role(struct ci_hdrc *ci) 641 + { 642 + enum ci_role role; 643 + 644 + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { 645 + if (ci->is_otg) { 646 + role = ci_otg_role(ci); 647 + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); 648 + } else { 649 + /* 650 + * If the controller is not OTG capable, but support 651 + * role switch, the defalt role is gadget, and the 652 + * user can switch it through debugfs. 653 + */ 654 + role = CI_ROLE_GADGET; 655 + } 656 + } else { 657 + role = ci->roles[CI_ROLE_HOST] ? CI_ROLE_HOST 658 + : CI_ROLE_GADGET; 659 + } 660 + 661 + return role; 662 + } 663 + 664 + static void ci_handle_power_lost(struct ci_hdrc *ci) 665 + { 666 + enum ci_role role; 667 + 668 + disable_irq_nosync(ci->irq); 669 + if (!ci_otg_is_fsm_mode(ci)) { 670 + role = ci_get_role(ci); 671 + 672 + if (ci->role != role) { 673 + ci_handle_id_switch(ci); 674 + } else if (role == CI_ROLE_GADGET) { 675 + if (ci->is_otg && hw_read_otgsc(ci, OTGSC_BSV)) 676 + usb_gadget_vbus_connect(&ci->gadget); 677 + } 678 + } 679 + 680 + enable_irq(ci->irq); 681 + } 682 + 640 683 static struct usb_role_switch_desc ci_role_switch = { 641 684 .set = ci_usb_role_switch_set, 642 685 .get = ci_usb_role_switch_get, ··· 1177 1134 } 1178 1135 } 1179 1136 1180 - if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { 1181 - if (ci->is_otg) { 1182 - ci->role = ci_otg_role(ci); 1183 - /* Enable ID change irq */ 1184 - hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); 1185 - } else { 1186 - /* 1187 - * If the controller is not OTG capable, but support 1188 - * role switch, the defalt role is gadget, and the 1189 - * user can switch it through debugfs. 1190 - */ 1191 - ci->role = CI_ROLE_GADGET; 1192 - } 1193 - } else { 1194 - ci->role = ci->roles[CI_ROLE_HOST] 1195 - ? CI_ROLE_HOST 1196 - : CI_ROLE_GADGET; 1197 - } 1198 - 1137 + ci->role = ci_get_role(ci); 1199 1138 if (!ci_otg_is_fsm_mode(ci)) { 1200 1139 /* only update vbus status for peripheral */ 1201 1140 if (ci->role == CI_ROLE_GADGET) { ··· 1399 1374 static int ci_resume(struct device *dev) 1400 1375 { 1401 1376 struct ci_hdrc *ci = dev_get_drvdata(dev); 1377 + bool power_lost; 1402 1378 int ret; 1379 + 1380 + /* Since ASYNCLISTADDR (host mode) and ENDPTLISTADDR (device 1381 + * mode) share the same register address. We can check if 1382 + * controller resume from power lost based on this address 1383 + * due to this register will be reset after power lost. 1384 + */ 1385 + power_lost = !hw_read(ci, OP_ENDPTLISTADDR, ~0); 1403 1386 1404 1387 if (device_may_wakeup(dev)) 1405 1388 disable_irq_wake(ci->irq); ··· 1415 1382 ret = ci_controller_resume(dev); 1416 1383 if (ret) 1417 1384 return ret; 1385 + 1386 + if (power_lost) { 1387 + /* shutdown and re-init for phy */ 1388 + ci_usb_phy_exit(ci); 1389 + ci_usb_phy_init(ci); 1390 + } 1391 + 1392 + if (power_lost) 1393 + ci_handle_power_lost(ci); 1418 1394 1419 1395 if (ci->supports_runtime_pm) { 1420 1396 pm_runtime_disable(dev);
+1 -1
drivers/usb/chipidea/otg.c
··· 165 165 return 0; 166 166 } 167 167 168 - static void ci_handle_id_switch(struct ci_hdrc *ci) 168 + void ci_handle_id_switch(struct ci_hdrc *ci) 169 169 { 170 170 enum ci_role role = ci_otg_role(ci); 171 171
+1
drivers/usb/chipidea/otg.h
··· 14 14 void ci_hdrc_otg_destroy(struct ci_hdrc *ci); 15 15 enum ci_role ci_otg_role(struct ci_hdrc *ci); 16 16 void ci_handle_vbus_change(struct ci_hdrc *ci); 17 + void ci_handle_id_switch(struct ci_hdrc *ci); 17 18 static inline void ci_otg_queue_work(struct ci_hdrc *ci) 18 19 { 19 20 disable_irq_nosync(ci->irq);