usb: chipidea: core: handle usb role switch in a common way
authorXu Yang <xu.yang_2@nxp.com>
Sun, 9 Oct 2022 15:53:36 +0000 (23:53 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 23 Oct 2022 12:34:52 +0000 (14:34 +0200)
Currently, ci_usb_role_switch_set() may be called before system resume
stage when suspended. Worse yet, ci_hdrc device may stay at RPM_ACTIVE
state which will cause pm_runtime_get_sync() fail to resume the device.
In this case, role-switch may unable to complete transition process due
to not exit from lpm state or due to lack some means after system resume.

Same as ci_cable_notifier(), usb_role_switch could handle its events based
on ci_hdrc_cable mechanism.

Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
Link: https://lore.kernel.org/r/20221009155336.766960-1-xu.yang_2@nxp.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/chipidea/core.c

index 6330fa9117926a59a2299f28579a163c4aab2e13..ae90fee75a32de6e657c320501b50fa3fd0fbf14 100644 (file)
@@ -608,49 +608,32 @@ static int ci_usb_role_switch_set(struct usb_role_switch *sw,
                                  enum usb_role role)
 {
        struct ci_hdrc *ci = usb_role_switch_get_drvdata(sw);
-       struct ci_hdrc_cable *cable = NULL;
-       enum usb_role current_role = ci_role_to_usb_role(ci);
-       enum ci_role ci_role = usb_role_to_ci_role(role);
-       unsigned long flags;
-
-       if ((ci_role != CI_ROLE_END && !ci->roles[ci_role]) ||
-           (current_role == role))
-               return 0;
+       struct ci_hdrc_cable *cable;
 
-       pm_runtime_get_sync(ci->dev);
-       /* Stop current role */
-       spin_lock_irqsave(&ci->lock, flags);
-       if (current_role == USB_ROLE_DEVICE)
+       if (role == USB_ROLE_HOST) {
+               cable = &ci->platdata->id_extcon;
+               cable->changed = true;
+               cable->connected = true;
                cable = &ci->platdata->vbus_extcon;
-       else if (current_role == USB_ROLE_HOST)
+               cable->changed = true;
+               cable->connected = false;
+       } else if (role == USB_ROLE_DEVICE) {
                cable = &ci->platdata->id_extcon;
-
-       if (cable) {
                cable->changed = true;
                cable->connected = false;
-               ci_irq(ci);
-               spin_unlock_irqrestore(&ci->lock, flags);
-               if (ci->wq && role != USB_ROLE_NONE)
-                       flush_workqueue(ci->wq);
-               spin_lock_irqsave(&ci->lock, flags);
-       }
-
-       cable = NULL;
-
-       /* Start target role */
-       if (role == USB_ROLE_DEVICE)
                cable = &ci->platdata->vbus_extcon;
-       else if (role == USB_ROLE_HOST)
-               cable = &ci->platdata->id_extcon;
-
-       if (cable) {
                cable->changed = true;
                cable->connected = true;
-               ci_irq(ci);
+       } else {
+               cable = &ci->platdata->id_extcon;
+               cable->changed = true;
+               cable->connected = false;
+               cable = &ci->platdata->vbus_extcon;
+               cable->changed = true;
+               cable->connected = false;
        }
-       spin_unlock_irqrestore(&ci->lock, flags);
-       pm_runtime_put_sync(ci->dev);
 
+       ci_irq(ci);
        return 0;
 }
 
@@ -1305,11 +1288,13 @@ static void ci_extcon_wakeup_int(struct ci_hdrc *ci)
        cable_id = &ci->platdata->id_extcon;
        cable_vbus = &ci->platdata->vbus_extcon;
 
-       if (!IS_ERR(cable_id->edev) && ci->is_otg &&
+       if ((!IS_ERR(cable_id->edev) || !IS_ERR(ci->role_switch))
+               && ci->is_otg &&
                (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS))
                ci_irq(ci);
 
-       if (!IS_ERR(cable_vbus->edev) && ci->is_otg &&
+       if ((!IS_ERR(cable_vbus->edev) || !IS_ERR(ci->role_switch))
+               && ci->is_otg &&
                (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS))
                ci_irq(ci);
 }