The sunxi musb has a bug where sometimes it will generate a babble
error on device disconnect instead of a disconnect irq. When this
happens the musb-controller switches from host mode to device mode
gets stuck in this state.

Clearing this requires reporting Vbus low for 200 or more ms, but
on some devices Vbus is simply always high (host-only mode, no Vbus

This commit modifies sun4i_usb_phy_set_mode so that it will force
end the current session when called with the current mode, before this
commit calling set_mode with the current mode was a nop since id_det
would stay the same resulting in the detect_work not doing anything.

This allows the sunxi-musb glue to use sun4i_usb_phy_set_mode to force
end the current session without changing the mode, to fixup the stuck
state after a babble error.

Signed-off-by: Hans de Goede <>
Changes in v2:
-New patch in v2 of this series replacing the
 "phy-sun4i-usb: Add sun4i_usb_phy_force_session_end() function"
 from v1
 drivers/phy/phy-sun4i-usb.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c
index 43c0d98..d76538c 100644
--- a/drivers/phy/phy-sun4i-usb.c
+++ b/drivers/phy/phy-sun4i-usb.c
@@ -437,25 +437,32 @@ static int sun4i_usb_phy_set_mode(struct phy *_phy, enum 
phy_mode mode)
        struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
        struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
+       enum usb_dr_mode new_mode;
        if (phy->index != 0)
                return -EINVAL;
        switch (mode) {
        case PHY_MODE_USB_HOST:
-               data->dr_mode = USB_DR_MODE_HOST;
+               new_mode = USB_DR_MODE_HOST;
        case PHY_MODE_USB_DEVICE:
-               data->dr_mode = USB_DR_MODE_PERIPHERAL;
+               new_mode = USB_DR_MODE_PERIPHERAL;
        case PHY_MODE_USB_OTG:
-               data->dr_mode = USB_DR_MODE_OTG;
+               new_mode = USB_DR_MODE_OTG;
                return -EINVAL;
-       dev_info(&_phy->dev, "Changing dr_mode to %d\n", (int)data->dr_mode);
+       if (new_mode != data->dr_mode) {
+               dev_info(&_phy->dev, "Changing dr_mode to %d\n",
+                        (int)data->dr_mode);
+               data->dr_mode = new_mode;
+       }
+       data->id_det = -1; /* Force reprocessing of id */
        data->force_session_end = true;
        queue_delayed_work(system_wq, &data->detect, 0);

To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to
More majordomo info at

Reply via email to