* loopback_add - add a loopback testing configuration to a device
  * @cdev: the device to support the loopback configuration
  */
-int __init loopback_add(struct usb_composite_dev *cdev)
+int __init loopback_add(struct usb_composite_dev *cdev, bool autoresume)
 {
        int id;
 
        loopback_intf.iInterface = id;
        loopback_driver.iConfiguration = id;
 
+       /* support autoresume for remote wakeup testing */
+       if (autoresume)
+               sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+
        /* support OTG systems */
        if (gadget_is_otg(cdev->gadget)) {
                loopback_driver.descriptors = otg_desc;
 
 
        struct usb_ep           *in_ep;
        struct usb_ep           *out_ep;
-       struct timer_list       resume;
 };
 
 static inline struct f_sourcesink *func_to_ss(struct usb_function *f)
        return container_of(f, struct f_sourcesink, function);
 }
 
-static unsigned autoresume;
-module_param(autoresume, uint, 0);
-MODULE_PARM_DESC(autoresume, "zero, or seconds before remote wakeup");
-
 static unsigned pattern;
 module_param(pattern, uint, 0);
 MODULE_PARM_DESC(pattern, "0 = all zeroes, 1 = mod63 ");
 
 /*-------------------------------------------------------------------------*/
 
-static void sourcesink_autoresume(unsigned long _c)
-{
-       struct usb_composite_dev *cdev = (void *)_c;
-       struct usb_gadget       *g = cdev->gadget;
-
-       /* Normally the host would be woken up for something
-        * more significant than just a timer firing; likely
-        * because of some direct user request.
-        */
-       if (g->speed != USB_SPEED_UNKNOWN) {
-               int status = usb_gadget_wakeup(g);
-               DBG(cdev, "%s --> %d\n", __func__, status);
-       }
-}
-
 static int __init
 sourcesink_bind(struct usb_configuration *c, struct usb_function *f)
 {
                goto autoconf_fail;
        ss->out_ep->driver_data = cdev; /* claim */
 
-       setup_timer(&ss->resume, sourcesink_autoresume,
-                       (unsigned long) c->cdev);
-
        /* support high speed hardware */
        if (gadget_is_dualspeed(c->cdev->gadget)) {
                hs_source_desc.bEndpointAddress =
 
        cdev = ss->function.config->cdev;
        disable_endpoints(cdev, ss->in_ep, ss->out_ep);
-       del_timer(&ss->resume);
        VDBG(cdev, "%s disabled\n", ss->function.name);
 }
 
        disable_source_sink(ss);
 }
 
-static void sourcesink_suspend(struct usb_function *f)
-{
-       struct f_sourcesink     *ss = func_to_ss(f);
-       struct usb_composite_dev *cdev = f->config->cdev;
-
-       if (cdev->gadget->speed == USB_SPEED_UNKNOWN)
-               return;
-
-       if (autoresume) {
-               mod_timer(&ss->resume, jiffies + (HZ * autoresume));
-               DBG(cdev, "suspend, wakeup in %d seconds\n", autoresume);
-       } else
-               DBG(cdev, "%s\n", __func__);
-}
-
-static void sourcesink_resume(struct usb_function *f)
-{
-       struct f_sourcesink     *ss = func_to_ss(f);
-       struct usb_composite_dev *cdev = f->config->cdev;
-
-       DBG(cdev, "%s\n", __func__);
-       del_timer(&ss->resume);
-}
-
 /*-------------------------------------------------------------------------*/
 
 static int __init sourcesink_bind_config(struct usb_configuration *c)
        ss->function.unbind = sourcesink_unbind;
        ss->function.set_alt = sourcesink_set_alt;
        ss->function.disable = sourcesink_disable;
-       ss->function.suspend = sourcesink_suspend;
-       ss->function.resume = sourcesink_resume;
 
        status = usb_add_function(c, &ss->function);
        if (status)
  * sourcesink_add - add a source/sink testing configuration to a device
  * @cdev: the device to support the configuration
  */
-int __init sourcesink_add(struct usb_composite_dev *cdev)
+int __init sourcesink_add(struct usb_composite_dev *cdev, bool autoresume)
 {
        int id;
 
 
                struct usb_ep *in, struct usb_ep *out);
 
 /* configuration-specific linkup */
-int sourcesink_add(struct usb_composite_dev *cdev);
-int loopback_add(struct usb_composite_dev *cdev);
+int sourcesink_add(struct usb_composite_dev *cdev, bool autoresume);
+int loopback_add(struct usb_composite_dev *cdev, bool autoresume);
 
 #endif /* __G_ZERO_H */
 
 #ifndef        CONFIG_USB_ZERO_HNPTEST
 #define DRIVER_VENDOR_NUM      0x0525          /* NetChip */
 #define DRIVER_PRODUCT_NUM     0xa4a0          /* Linux-USB "Gadget Zero" */
+#define DEFAULT_AUTORESUME     0
 #else
 #define DRIVER_VENDOR_NUM      0x1a0a          /* OTG test device IDs */
 #define DRIVER_PRODUCT_NUM     0xbadd
+#define DEFAULT_AUTORESUME     5
 #endif
 
+/* If the optional "autoresume" mode is enabled, it provides good
+ * functional coverage for the "USBCV" test harness from USB-IF.
+ * It's always set if OTG mode is enabled.
+ */
+unsigned autoresume = DEFAULT_AUTORESUME;
+module_param(autoresume, uint, S_IRUGO);
+MODULE_PARM_DESC(autoresume, "zero, or seconds before remote wakeup");
+
 /*-------------------------------------------------------------------------*/
 
 static struct usb_device_descriptor device_desc = {
 
 /*-------------------------------------------------------------------------*/
 
+static struct timer_list       autoresume_timer;
+
+static void zero_autoresume(unsigned long _c)
+{
+       struct usb_composite_dev        *cdev = (void *)_c;
+       struct usb_gadget               *g = cdev->gadget;
+
+       /* unconfigured devices can't issue wakeups */
+       if (!cdev->config)
+               return;
+
+       /* Normally the host would be woken up for something
+        * more significant than just a timer firing; likely
+        * because of some direct user request.
+        */
+       if (g->speed != USB_SPEED_UNKNOWN) {
+               int status = usb_gadget_wakeup(g);
+               INFO(cdev, "%s --> %d\n", __func__, status);
+       }
+}
+
+static void zero_suspend(struct usb_composite_dev *cdev)
+{
+       if (cdev->gadget->speed == USB_SPEED_UNKNOWN)
+               return;
+
+       if (autoresume) {
+               mod_timer(&autoresume_timer, jiffies + (HZ * autoresume));
+               DBG(cdev, "suspend, wakeup in %d seconds\n", autoresume);
+       } else
+               DBG(cdev, "%s\n", __func__);
+}
+
+static void zero_resume(struct usb_composite_dev *cdev)
+{
+       DBG(cdev, "%s\n", __func__);
+       del_timer(&autoresume_timer);
+}
+
+/*-------------------------------------------------------------------------*/
+
 static int __init zero_bind(struct usb_composite_dev *cdev)
 {
        int                     gcnum;
        strings_dev[STRING_SERIAL_IDX].id = id;
        device_desc.iSerialNumber = id;
 
+       setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev);
+
        /* Register primary, then secondary configuration.  Note that
         * SH3 only allows one config...
         */
        if (loopdefault) {
-               loopback_add(cdev);
+               loopback_add(cdev, autoresume != 0);
                if (!gadget_is_sh(gadget))
-                       sourcesink_add(cdev);
+                       sourcesink_add(cdev, autoresume != 0);
        } else {
-               sourcesink_add(cdev);
+               sourcesink_add(cdev, autoresume != 0);
                if (!gadget_is_sh(gadget))
-                       loopback_add(cdev);
+                       loopback_add(cdev, autoresume != 0);
        }
 
        gcnum = usb_gadget_controller_number(gadget);
        return 0;
 }
 
+static int zero_unbind(struct usb_composite_dev *cdev)
+{
+       del_timer_sync(&autoresume_timer);
+       return 0;
+}
+
 static struct usb_composite_driver zero_driver = {
        .name           = "zero",
        .dev            = &device_desc,
        .strings        = dev_strings,
        .bind           = zero_bind,
+       .unbind         = zero_unbind,
+       .suspend        = zero_suspend,
+       .resume         = zero_resume,
 };
 
 MODULE_AUTHOR("David Brownell");