USB: suspend/resume support for option driver
authorOliver Neukum <oliver@neukum.org>
Tue, 27 Jan 2009 16:21:40 +0000 (17:21 +0100)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 24 Mar 2009 23:20:29 +0000 (16:20 -0700)
This patch implements suspend and resume methods for the
option driver. With my hardware I can even suspend the system
and keep up a connection for a short time.

Signed-off-by: Oliver Neukum <oneukum@suse.de>
Signed-Off-By: Matthias Urlichs <smurf@smurf.noris.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/serial/option.c

index 61ebddc48497092abeba19d9e785258df1e6c7ef..d560c0b54e6e3602b300412873501196ef2c8090 100644 (file)
@@ -62,6 +62,8 @@ static int  option_tiocmget(struct tty_struct *tty, struct file *file);
 static int  option_tiocmset(struct tty_struct *tty, struct file *file,
                                unsigned int set, unsigned int clear);
 static int  option_send_setup(struct tty_struct *tty, struct usb_serial_port *port);
+static int  option_suspend(struct usb_serial *serial, pm_message_t message);
+static int  option_resume(struct usb_serial *serial);
 
 /* Vendor and product IDs */
 #define OPTION_VENDOR_ID                       0x0AF0
@@ -523,6 +525,8 @@ static struct usb_driver option_driver = {
        .name       = "option",
        .probe      = usb_serial_probe,
        .disconnect = usb_serial_disconnect,
+       .suspend    = usb_serial_suspend,
+       .resume     = usb_serial_resume,
        .id_table   = option_ids,
        .no_dynamic_id =        1,
 };
@@ -551,6 +555,8 @@ static struct usb_serial_driver option_1port_device = {
        .attach            = option_startup,
        .shutdown          = option_shutdown,
        .read_int_callback = option_instat_callback,
+       .suspend           = option_suspend,
+       .resume            = option_resume,
 };
 
 static int debug;
@@ -821,10 +827,10 @@ static void option_instat_callback(struct urb *urb)
                                req_pkt->bRequestType, req_pkt->bRequest);
                }
        } else
-               dbg("%s: error %d", __func__, status);
+               err("%s: error %d", __func__, status);
 
        /* Resubmit urb so we continue receiving IRQ data */
-       if (status != -ESHUTDOWN) {
+       if (status != -ESHUTDOWN && status != -ENOENT) {
                urb->dev = serial->dev;
                err = usb_submit_urb(urb, GFP_ATOMIC);
                if (err)
@@ -843,7 +849,6 @@ static int option_write_room(struct tty_struct *tty)
 
        portdata = usb_get_serial_port_data(port);
 
-
        for (i = 0; i < N_OUT_URB; i++) {
                this_urb = portdata->out_urbs[i];
                if (this_urb && !test_bit(i, &portdata->out_busy))
@@ -1105,14 +1110,12 @@ bail_out_error:
        return 1;
 }
 
-static void option_shutdown(struct usb_serial *serial)
+static void stop_read_write_urbs(struct usb_serial *serial)
 {
        int i, j;
        struct usb_serial_port *port;
        struct option_port_private *portdata;
 
-       dbg("%s", __func__);
-
        /* Stop reading/writing urbs */
        for (i = 0; i < serial->num_ports; ++i) {
                port = serial->port[i];
@@ -1122,6 +1125,17 @@ static void option_shutdown(struct usb_serial *serial)
                for (j = 0; j < N_OUT_URB; j++)
                        usb_kill_urb(portdata->out_urbs[j]);
        }
+}
+
+static void option_shutdown(struct usb_serial *serial)
+{
+       int i, j;
+       struct usb_serial_port *port;
+       struct option_port_private *portdata;
+
+       dbg("%s", __func__);
+
+       stop_read_write_urbs(serial);
 
        /* Now free them */
        for (i = 0; i < serial->num_ports; ++i) {
@@ -1152,6 +1166,66 @@ static void option_shutdown(struct usb_serial *serial)
        }
 }
 
+static int option_suspend(struct usb_serial *serial, pm_message_t message)
+{
+       dbg("%s entered", __func__);
+       stop_read_write_urbs(serial);
+
+       return 0;
+}
+
+static int option_resume(struct usb_serial *serial)
+{
+       int err, i, j;
+       struct usb_serial_port *port;
+       struct urb *urb;
+       struct option_port_private *portdata;
+
+       dbg("%s entered", __func__);
+       /* get the interrupt URBs resubmitted unconditionally */
+       for (i = 0; i < serial->num_ports; i++) {
+               port = serial->port[i];
+               if (!port->interrupt_in_urb) {
+                       dbg("%s: No interrupt URB for port %d\n", __func__, i);
+                       continue;
+               }
+               port->interrupt_in_urb->dev = serial->dev;
+               err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+               dbg("Submitted interrupt URB for port %d (result %d)", i, err);
+               if (err < 0) {
+                       err("%s: Error %d for interrupt URB of port%d",
+                                __func__, err, i);
+                       return err;
+               }
+       }
+
+       for (i = 0; i < serial->num_ports; i++) {
+               /* walk all ports */
+               port = serial->port[i];
+               portdata = usb_get_serial_port_data(port);
+               mutex_lock(&port->mutex);
+
+               /* skip closed ports */
+               if (!port->port.count) {
+                       mutex_unlock(&port->mutex);
+                       continue;
+               }
+
+               for (j = 0; j < N_IN_URB; j++) {
+                       urb = portdata->in_urbs[j];
+                       err = usb_submit_urb(urb, GFP_NOIO);
+                       if (err < 0) {
+                               mutex_unlock(&port->mutex);
+                               err("%s: Error %d for bulk URB %d",
+                                        __func__, err, i);
+                               return err;
+                       }
+               }
+               mutex_unlock(&port->mutex);
+       }
+       return 0;
+}
+
 MODULE_AUTHOR(DRIVER_AUTHOR);
 MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_VERSION(DRIVER_VERSION);