usb: mtu3: support suspend/resume for device mode
authorChunfeng Yun <chunfeng.yun@mediatek.com>
Thu, 15 Jul 2021 09:07:57 +0000 (17:07 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 27 Jul 2021 13:58:37 +0000 (15:58 +0200)
Support suspend/resume for device mode if the device is not
connected with a host, otherwise reject to suspend.

Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
Link: https://lore.kernel.org/r/1626340078-29111-13-git-send-email-chunfeng.yun@mediatek.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/mtu3/mtu3.h
drivers/usb/mtu3/mtu3_core.c
drivers/usb/mtu3/mtu3_dr.h
drivers/usb/mtu3/mtu3_gadget.c
drivers/usb/mtu3/mtu3_plat.c

index 171e5b3..022bbdc 100644 (file)
@@ -356,6 +356,7 @@ struct mtu3 {
        unsigned is_u3_ip:1;
        unsigned delayed_status:1;
        unsigned gen2cp:1;
+       unsigned connected:1;
 
        u8 address;
        u8 test_mode_nr;
index 648e970..a800920 100644 (file)
@@ -9,6 +9,7 @@
  */
 
 #include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of_address.h>
@@ -380,6 +381,24 @@ void mtu3_stop(struct mtu3 *mtu)
        mtu3_dev_power_down(mtu);
 }
 
+static void mtu3_dev_suspend(struct mtu3 *mtu)
+{
+       if (!mtu->is_active)
+               return;
+
+       mtu3_intr_disable(mtu);
+       mtu3_dev_power_down(mtu);
+}
+
+static void mtu3_dev_resume(struct mtu3 *mtu)
+{
+       if (!mtu->is_active)
+               return;
+
+       mtu3_dev_power_on(mtu);
+       mtu3_intr_enable(mtu);
+}
+
 /* for non-ep0 */
 int mtu3_config_ep(struct mtu3 *mtu, struct mtu3_ep *mep,
                        int interval, int burst, int mult)
@@ -700,11 +719,15 @@ static irqreturn_t mtu3_link_isr(struct mtu3 *mtu)
        mtu->g.speed = udev_speed;
        mtu->g.ep0->maxpacket = maxpkt;
        mtu->ep0_state = MU3D_EP0_STATE_SETUP;
+       mtu->connected = !!(udev_speed != USB_SPEED_UNKNOWN);
 
-       if (udev_speed == USB_SPEED_UNKNOWN)
+       if (udev_speed == USB_SPEED_UNKNOWN) {
                mtu3_gadget_disconnect(mtu);
-       else
+               pm_runtime_put(mtu->dev);
+       } else {
+               pm_runtime_get(mtu->dev);
                mtu3_ep0_setup(mtu);
+       }
 
        return IRQ_HANDLED;
 }
@@ -984,3 +1007,44 @@ void ssusb_gadget_exit(struct ssusb_mtk *ssusb)
        device_init_wakeup(ssusb->dev, false);
        mtu3_hw_exit(mtu);
 }
+
+int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       struct mtu3 *mtu = ssusb->u3d;
+       void __iomem *ibase = mtu->ippc_base;
+       u32 value;
+       int ret = 0;
+
+       if (!mtu->gadget_driver)
+               return 0;
+
+       if (mtu->connected)
+               return -EBUSY;
+
+       mtu3_dev_suspend(mtu);
+       synchronize_irq(mtu->irq);
+
+       /* wait for ip to sleep */
+       if (mtu->is_active && mtu->softconnect) {
+               ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1,
+                               value, (value & SSUSB_IP_SLEEP_STS), 100, 100000);
+               if (ret) {
+                       dev_err(mtu->dev, "ip sleep failed!!!\n");
+                       ret = -EBUSY;
+               }
+       }
+
+       return ret;
+}
+
+int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       struct mtu3 *mtu = ssusb->u3d;
+
+       if (!mtu->gadget_driver)
+               return 0;
+
+       mtu3_dev_resume(mtu);
+
+       return 0;
+}
index 70dbf47..5286f9f 100644 (file)
@@ -57,6 +57,8 @@ static inline void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable)
 #if IS_ENABLED(CONFIG_USB_MTU3_GADGET) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE)
 int ssusb_gadget_init(struct ssusb_mtk *ssusb);
 void ssusb_gadget_exit(struct ssusb_mtk *ssusb);
+int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg);
+int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg);
 #else
 static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb)
 {
@@ -65,6 +67,18 @@ static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb)
 
 static inline void ssusb_gadget_exit(struct ssusb_mtk *ssusb)
 {}
+
+static inline int
+ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       return 0;
+}
+
+static inline int
+ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       return 0;
+}
 #endif
 
 
index 5e21ba0..7b54631 100644 (file)
@@ -469,6 +469,8 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
        dev_dbg(mtu->dev, "%s (%s) for %sactive device\n", __func__,
                is_on ? "on" : "off", mtu->is_active ? "" : "in");
 
+       pm_runtime_get_sync(mtu->dev);
+
        /* we'd rather not pullup unless the device is active. */
        spin_lock_irqsave(&mtu->lock, flags);
 
@@ -482,6 +484,7 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
        }
 
        spin_unlock_irqrestore(&mtu->lock, flags);
+       pm_runtime_put(mtu->dev);
 
        return 0;
 }
@@ -499,6 +502,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget,
        }
 
        dev_dbg(mtu->dev, "bind driver %s\n", driver->function);
+       pm_runtime_get_sync(mtu->dev);
 
        spin_lock_irqsave(&mtu->lock, flags);
 
@@ -509,6 +513,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget,
                mtu3_start(mtu);
 
        spin_unlock_irqrestore(&mtu->lock, flags);
+       pm_runtime_put(mtu->dev);
 
        return 0;
 }
index 2be890f..e174ada 100644 (file)
@@ -421,21 +421,32 @@ static int mtu3_remove(struct platform_device *pdev)
        return 0;
 }
 
-/*
- * when support dual-role mode, we reject suspend when
- * it works as device mode;
- */
 static int mtu3_suspend_common(struct device *dev, pm_message_t msg)
 {
        struct ssusb_mtk *ssusb = dev_get_drvdata(dev);
+       int ret = 0;
 
        dev_dbg(dev, "%s\n", __func__);
 
-       /* REVISIT: disconnect it for only device mode? */
-       if (!ssusb->is_host)
-               return 0;
+       switch (ssusb->dr_mode) {
+       case USB_DR_MODE_PERIPHERAL:
+               ret = ssusb_gadget_suspend(ssusb, msg);
+               if (ret)
+                       return ret;
 
-       ssusb_host_suspend(ssusb);
+               break;
+       case USB_DR_MODE_HOST:
+               ssusb_host_suspend(ssusb);
+               break;
+       case USB_DR_MODE_OTG:
+               if (!ssusb->is_host)
+                       return 0;
+
+               ssusb_host_suspend(ssusb);
+               break;
+       default:
+               return -EINVAL;
+       }
        ssusb_phy_power_off(ssusb);
        clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks);
        ssusb_wakeup_set(ssusb, true);
@@ -450,9 +461,6 @@ static int mtu3_resume_common(struct device *dev, pm_message_t msg)
 
        dev_dbg(dev, "%s\n", __func__);
 
-       if (!ssusb->is_host)
-               return 0;
-
        ssusb_wakeup_set(ssusb, false);
        ret = clk_bulk_prepare_enable(BULK_CLKS_CNT, ssusb->clks);
        if (ret)
@@ -462,7 +470,22 @@ static int mtu3_resume_common(struct device *dev, pm_message_t msg)
        if (ret)
                goto phy_err;
 
-       ssusb_host_resume(ssusb, false);
+       switch (ssusb->dr_mode) {
+       case USB_DR_MODE_PERIPHERAL:
+               ssusb_gadget_resume(ssusb, msg);
+               break;
+       case USB_DR_MODE_HOST:
+               ssusb_host_resume(ssusb, false);
+               break;
+       case USB_DR_MODE_OTG:
+               if (!ssusb->is_host)
+                       return 0;
+
+               ssusb_host_resume(ssusb, true);
+               break;
+       default:
+               return -EINVAL;
+       }
 
        return 0;