]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/usb/class/cdc-wdm.c
Merge git://git.infradead.org/embedded-2.6
[linux-2.6-omap-h63xx.git] / drivers / usb / class / cdc-wdm.c
index 731db051070a82d63a5eeebe048c6ab314b92810..7e8e1235e4e52d93cf3aa0ab930a7cce618bc7ea 100644 (file)
@@ -28,8 +28,9 @@
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v0.02"
+#define DRIVER_VERSION "v0.03"
 #define DRIVER_AUTHOR "Oliver Neukum"
+#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
 
 static struct usb_device_id wdm_ids[] = {
        {
@@ -87,6 +88,7 @@ struct wdm_device {
        dma_addr_t              ihandle;
        struct mutex            wlock;
        struct mutex            rlock;
+       struct mutex            plock;
        wait_queue_head_t       wait;
        struct work_struct      rxwork;
        int                     werr;
@@ -205,7 +207,7 @@ static void wdm_int_callback(struct urb *urb)
        req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
        req->wValue = 0;
        req->wIndex = desc->inum;
-       req->wLength = cpu_to_le16(desc->bMaxPacketSize0);
+       req->wLength = cpu_to_le16(desc->wMaxCommand);
 
        usb_fill_control_urb(
                desc->response,
@@ -214,7 +216,7 @@ static void wdm_int_callback(struct urb *urb)
                usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
                (unsigned char *)req,
                desc->inbuf,
-               desc->bMaxPacketSize0,
+               desc->wMaxCommand,
                wdm_in_callback,
                desc
        );
@@ -247,6 +249,7 @@ exit:
 
 static void kill_urbs(struct wdm_device *desc)
 {
+       /* the order here is essential */
        usb_kill_urb(desc->command);
        usb_kill_urb(desc->validity);
        usb_kill_urb(desc->response);
@@ -266,7 +269,7 @@ static void cleanup(struct wdm_device *desc)
                        desc->sbuf,
                        desc->validity->transfer_dma);
        usb_buffer_free(interface_to_usbdev(desc->intf),
-                       desc->wMaxPacketSize,
+                       desc->wMaxCommand,
                        desc->inbuf,
                        desc->response->transfer_dma);
        kfree(desc->orq);
@@ -299,6 +302,9 @@ static ssize_t wdm_write
        if (r)
                goto outnl;
 
+       r = usb_autopm_get_interface(desc->intf);
+       if (r < 0)
+               goto outnp;
        r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
                                                           &desc->flags));
        if (r < 0)
@@ -347,11 +353,14 @@ static ssize_t wdm_write
        if (rv < 0) {
                kfree(buf);
                clear_bit(WDM_IN_USE, &desc->flags);
+               err("Tx URB error: %d", rv);
        } else {
                dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",
                        req->wIndex);
        }
 out:
+       usb_autopm_put_interface(desc->intf);
+outnp:
        mutex_unlock(&desc->wlock);
 outnl:
        return rv < 0 ? rv : count;
@@ -376,6 +385,11 @@ retry:
                rv = wait_event_interruptible(desc->wait,
                                              test_bit(WDM_READ, &desc->flags));
 
+               if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
+                       rv = -ENODEV;
+                       goto err;
+               }
+               usb_mark_last_busy(interface_to_usbdev(desc->intf));
                if (rv < 0) {
                        rv = -ERESTARTSYS;
                        goto err;
@@ -418,6 +432,9 @@ retry:
                desc->ubuf[i] = desc->ubuf[i + cntr];
 
        desc->length -= cntr;
+       /* in case we had outstanding data */
+       if (!desc->length)
+               clear_bit(WDM_READ, &desc->flags);
        rv = cntr;
 
 err:
@@ -480,18 +497,28 @@ static int wdm_open(struct inode *inode, struct file *file)
        if (test_bit(WDM_DISCONNECTING, &desc->flags))
                goto out;
 
-       desc->count++;
+       ;
        file->private_data = desc;
 
-       rv = usb_submit_urb(desc->validity, GFP_KERNEL);
-
+       rv = usb_autopm_get_interface(desc->intf);
        if (rv < 0) {
-               desc->count--;
-               err("Error submitting int urb - %d", rv);
+               err("Error autopm - %d", rv);
                goto out;
        }
-       rv = 0;
+       intf->needs_remote_wakeup = 1;
 
+       mutex_lock(&desc->plock);
+       if (!desc->count++) {
+               rv = usb_submit_urb(desc->validity, GFP_KERNEL);
+               if (rv < 0) {
+                       desc->count--;
+                       err("Error submitting int urb - %d", rv);
+               }
+       } else {
+               rv = 0;
+       }
+       mutex_unlock(&desc->plock);
+       usb_autopm_put_interface(desc->intf);
 out:
        mutex_unlock(&wdm_mutex);
        return rv;
@@ -502,10 +529,15 @@ static int wdm_release(struct inode *inode, struct file *file)
        struct wdm_device *desc = file->private_data;
 
        mutex_lock(&wdm_mutex);
+       mutex_lock(&desc->plock);
        desc->count--;
+       mutex_unlock(&desc->plock);
+
        if (!desc->count) {
                dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
                kill_urbs(desc);
+               if (!test_bit(WDM_DISCONNECTING, &desc->flags))
+                       desc->intf->needs_remote_wakeup = 0;
        }
        mutex_unlock(&wdm_mutex);
        return 0;
@@ -597,6 +629,7 @@ next_desc:
                goto out;
        mutex_init(&desc->wlock);
        mutex_init(&desc->rlock);
+       mutex_init(&desc->plock);
        spin_lock_init(&desc->iuspin);
        init_waitqueue_head(&desc->wait);
        desc->wMaxCommand = maxcom;
@@ -698,6 +731,7 @@ static void wdm_disconnect(struct usb_interface *intf)
        spin_lock_irqsave(&desc->iuspin, flags);
        set_bit(WDM_DISCONNECTING, &desc->flags);
        set_bit(WDM_READ, &desc->flags);
+       /* to terminate pending flushes */
        clear_bit(WDM_IN_USE, &desc->flags);
        spin_unlock_irqrestore(&desc->iuspin, flags);
        cancel_work_sync(&desc->rxwork);
@@ -708,11 +742,81 @@ static void wdm_disconnect(struct usb_interface *intf)
        mutex_unlock(&wdm_mutex);
 }
 
+static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
+{
+       struct wdm_device *desc = usb_get_intfdata(intf);
+       int rv = 0;
+
+       dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
+
+       mutex_lock(&desc->plock);
+#ifdef CONFIG_PM
+       if (interface_to_usbdev(desc->intf)->auto_pm && test_bit(WDM_IN_USE, &desc->flags)) {
+               rv = -EBUSY;
+       } else {
+#endif
+               cancel_work_sync(&desc->rxwork);
+               kill_urbs(desc);
+#ifdef CONFIG_PM
+       }
+#endif
+       mutex_unlock(&desc->plock);
+
+       return rv;
+}
+
+static int recover_from_urb_loss(struct wdm_device *desc)
+{
+       int rv = 0;
+
+       if (desc->count) {
+               rv = usb_submit_urb(desc->validity, GFP_NOIO);
+               if (rv < 0)
+                       err("Error resume submitting int urb - %d", rv);
+       }
+       return rv;
+}
+static int wdm_resume(struct usb_interface *intf)
+{
+       struct wdm_device *desc = usb_get_intfdata(intf);
+       int rv;
+
+       dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor);
+       mutex_lock(&desc->plock);
+       rv = recover_from_urb_loss(desc);
+       mutex_unlock(&desc->plock);
+       return rv;
+}
+
+static int wdm_pre_reset(struct usb_interface *intf)
+{
+       struct wdm_device *desc = usb_get_intfdata(intf);
+
+       mutex_lock(&desc->plock);
+       return 0;
+}
+
+static int wdm_post_reset(struct usb_interface *intf)
+{
+       struct wdm_device *desc = usb_get_intfdata(intf);
+       int rv;
+
+       rv = recover_from_urb_loss(desc);
+       mutex_unlock(&desc->plock);
+       return 0;
+}
+
 static struct usb_driver wdm_driver = {
        .name =         "cdc_wdm",
        .probe =        wdm_probe,
        .disconnect =   wdm_disconnect,
+       .suspend =      wdm_suspend,
+       .resume =       wdm_resume,
+       .reset_resume = wdm_resume,
+       .pre_reset =    wdm_pre_reset,
+       .post_reset =   wdm_post_reset,
        .id_table =     wdm_ids,
+       .supports_autosuspend = 1,
 };
 
 /* --- low level module stuff --- */
@@ -735,6 +839,5 @@ module_init(wdm_init);
 module_exit(wdm_exit);
 
 MODULE_AUTHOR(DRIVER_AUTHOR);
-MODULE_DESCRIPTION("USB Abstract Control Model driver for "
-                  "USB WCM Device Management");
+MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_LICENSE("GPL");