vserver 2.0 rc7
[linux-2.6.git] / drivers / usb / serial / ftdi_sio.c
index 8e75ea1..3bfcc7b 100644 (file)
@@ -76,7 +76,7 @@
  *      Defererence pointers after any paranoid checks, not before.
  *
  * (21/Jun/2003) Erik Nygren
- *      Added support for Home Electronics Tira-1 IR tranceiver using FT232BM chip.
+ *      Added support for Home Electronics Tira-1 IR transceiver using FT232BM chip.
  *      See <http://www.home-electro.com/tira1.htm>.  Only operates properly 
  *      at 100000 and RTS-CTS, so set custom divisor mode on startup.
  *      Also force the Tira-1 and USB-UIRT to only use their custom baud rates.
@@ -91,7 +91,7 @@
  *      Minor whitespace and comment changes.
  *
  * (12/Jun/2003) David Norwood
- *      Added support for USB-UIRT IR tranceiver using 8U232AM chip.
+ *      Added support for USB-UIRT IR transceiver using 8U232AM chip.
  *      See <http://home.earthlink.net/~jrhees/USBUIRT/index.htm>.  Only
  *      operates properly at 312500, so set custom divisor mode on startup.
  *
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v1.4.1"
+#define DRIVER_VERSION "v1.4.2"
 #define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Bill Ryder <bryder@sgi.com>, Kuba Ober <kuba@mareimbrium.org>"
 #define DRIVER_DESC "USB FTDI Serial Converters Driver"
 
@@ -272,6 +272,7 @@ static int debug;
 
 static struct usb_device_id id_table_sio [] = {
        { USB_DEVICE(FTDI_VID, FTDI_SIO_PID) },
+       { USB_DEVICE(MOBILITY_VID, MOBILITY_USB_SERIAL_PID) },
        { }                                             /* Terminating entry */
 };
 
@@ -363,16 +364,20 @@ static struct usb_device_id id_table_8U232AM [] = {
        { USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_3, 0, 0x3ff) },
        { USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_4, 0, 0x3ff) },
        { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_UO100_PID, 0, 0x3ff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_UM100_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(FTDI_VID, INSIDE_ACCESSO, 0, 0x3ff) },
        { USB_DEVICE_VER(INTREPID_VID, INTREPID_VALUECAN_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(INTREPID_VID, INTREPID_NEOVI_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(FALCOM_VID, FALCOM_TWIST_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(FTDI_VID, FTDI_SUUNTO_SPORTS_PID, 0, 0x3ff) },
-       { USB_DEVICE_VER(FTDI_RM_VID, FTDI_RMCANVIEW_PID, 0, 0x3ff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_RM_CANVIEW_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(BANDB_VID, BANDB_USOTL4_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(BANDB_VID, BANDB_USTL4_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(BANDB_VID, BANDB_USO9ML2_PID, 0, 0x3ff) },
        { USB_DEVICE_VER(FTDI_VID, EVER_ECO_PRO_CDS, 0, 0x3ff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_4N_GALAXY_DE_0_PID, 0, 0x3ff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID, 0, 0x3ff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID, 0, 0x3ff) },
        { }                                             /* Terminating entry */
 };
 
@@ -471,6 +476,7 @@ static struct usb_device_id id_table_FT232BM [] = {
        { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88E_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88F_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_UO100_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_UM100_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, LINX_SDMUSBQSS_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, LINX_MASTERDEVEL2_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, LINX_FUTURE_0_PID, 0x400, 0xffff) },
@@ -483,11 +489,15 @@ static struct usb_device_id id_table_FT232BM [] = {
        { USB_DEVICE_VER(INTREPID_VID, INTREPID_NEOVI_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FALCOM_VID, FALCOM_TWIST_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, FTDI_SUUNTO_SPORTS_PID, 0x400, 0xffff) },
-       { USB_DEVICE_VER(FTDI_RM_VID, FTDI_RMCANVIEW_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_RM_CANVIEW_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(BANDB_VID, BANDB_USOTL4_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(BANDB_VID, BANDB_USTL4_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(BANDB_VID, BANDB_USO9ML2_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, EVER_ECO_PRO_CDS, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_4N_GALAXY_DE_0_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_ACTIVE_ROBOTS_PID, 0x400, 0xffff) },
        { }                                             /* Terminating entry */
 };
 
@@ -593,7 +603,24 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, PROTEGO_R2X0) },
        { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) },
        { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E808_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E809_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E80A_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E80B_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E80C_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E80D_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E80E_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E80F_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E888_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E889_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88A_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88B_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88C_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88D_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88E_PID, 0x400, 0xffff) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_GUDEADS_E88F_PID, 0x400, 0xffff) },
        { USB_DEVICE(FTDI_VID, FTDI_ELV_UO100_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_ELV_UM100_PID) },
        { USB_DEVICE_VER(FTDI_VID, LINX_SDMUSBQSS_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, LINX_MASTERDEVEL2_PID, 0x400, 0xffff) },
        { USB_DEVICE_VER(FTDI_VID, LINX_FUTURE_0_PID, 0x400, 0xffff) },
@@ -606,11 +633,16 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(INTREPID_VID, INTREPID_NEOVI_PID) },
        { USB_DEVICE(FALCOM_VID, FALCOM_TWIST_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_SUUNTO_SPORTS_PID) },
-       { USB_DEVICE(FTDI_RM_VID, FTDI_RMCANVIEW_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_RM_CANVIEW_PID) },
        { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) },
        { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) },
        { USB_DEVICE(BANDB_VID, BANDB_USO9ML2_PID) },
        { USB_DEVICE(FTDI_VID, EVER_ECO_PRO_CDS) },
+       { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_0_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID) },
+       { USB_DEVICE(MOBILITY_VID, MOBILITY_USB_SERIAL_PID) },
+       { USB_DEVICE_VER(FTDI_VID, FTDI_ACTIVE_ROBOTS_PID, 0x400, 0xffff) },
        { }                                             /* Terminating entry */
 };
 
@@ -623,6 +655,13 @@ static struct usb_driver ftdi_driver = {
        .id_table =     id_table_combined,
 };
 
+static char *ftdi_chip_name[] = {
+       [SIO] = "SIO",  /* the serial part of FT8U100AX */
+       [FT8U232AM] = "FT8U232AM",
+       [FT232BM] = "FT232BM",
+       [FT2232C] = "FT2232C",
+};
+
 
 /* Constants for read urb and write urb */
 #define BUFSZ 512
@@ -648,6 +687,8 @@ struct ftdi_private {
        char prev_status, diff_status;        /* Used for TIOCMIWAIT */
        __u8 rx_flags;          /* receive state flags (throttling) */
        spinlock_t rx_lock;     /* spinlock for receive state */
+       struct work_struct rx_work;
+       int rx_processed;
 
        __u16 interface;        /* FT2232C port interface (0 for FT232/245) */
 
@@ -678,7 +719,7 @@ static int  ftdi_write_room         (struct usb_serial_port *port);
 static int  ftdi_chars_in_buffer       (struct usb_serial_port *port);
 static void ftdi_write_bulk_callback   (struct urb *urb, struct pt_regs *regs);
 static void ftdi_read_bulk_callback    (struct urb *urb, struct pt_regs *regs);
-static void ftdi_process_read          (struct usb_serial_port *port);
+static void ftdi_process_read          (void *param);
 static void ftdi_set_termios           (struct usb_serial_port *port, struct termios * old);
 static int  ftdi_tiocmget               (struct usb_serial_port *port, struct file *file);
 static int  ftdi_tiocmset              (struct usb_serial_port *port, struct file * file, unsigned int set, unsigned int clear);
@@ -852,7 +893,7 @@ static struct usb_serial_device_type ftdi_HE_TIRA1_device = {
 
 
 
-#define WDR_TIMEOUT (HZ * 5 ) /* default urb timeout */
+#define WDR_TIMEOUT 5000 /* default urb timeout */
 
 /* High and low are for DTR, RTS etc etc */
 #define HIGH 1
@@ -1001,7 +1042,6 @@ static __u32 get_ftdi_divisor(struct usb_serial_port * port)
        struct ftdi_private *priv = usb_get_serial_port_data(port);
        __u32 div_value = 0;
        int div_okay = 1;
-       char *chip_name = "";
        int baud;
 
        /*
@@ -1046,7 +1086,6 @@ static __u32 get_ftdi_divisor(struct usb_serial_port * port)
        if (!baud) baud = 9600; 
        switch(priv->chip_type) {
        case SIO: /* SIO chip */
-               chip_name = "SIO";
                switch(baud) {
                case 300: div_value = ftdi_sio_b300; break;
                case 600: div_value = ftdi_sio_b600; break;
@@ -1066,7 +1105,6 @@ static __u32 get_ftdi_divisor(struct usb_serial_port * port)
                }
                break;
        case FT8U232AM: /* 8U232AM chip */
-               chip_name = "FT8U232AM";
                if (baud <= 3000000) {
                        div_value = ftdi_232am_baud_to_divisor(baud);
                } else {
@@ -1077,11 +1115,6 @@ static __u32 get_ftdi_divisor(struct usb_serial_port * port)
                break;
        case FT232BM: /* FT232BM chip */
        case FT2232C: /* FT2232C chip */
-               if (priv->chip_type == FT2232C) {
-                       chip_name = "FT2232C";
-               } else {
-                       chip_name = "FT232BM";
-               }
                if (baud <= 3000000) {
                        div_value = ftdi_232bm_baud_to_divisor(baud);
                } else {
@@ -1094,7 +1127,8 @@ static __u32 get_ftdi_divisor(struct usb_serial_port * port)
 
        if (div_okay) {
                dbg("%s - Baud rate set to %d (divisor 0x%lX) on chip %s",
-                       __FUNCTION__, baud, (unsigned long)div_value, chip_name);
+                       __FUNCTION__, baud, (unsigned long)div_value,
+                       ftdi_chip_name[priv->chip_type]);
        }
 
        return(div_value);
@@ -1184,7 +1218,7 @@ check_and_exit:
  * ***************************************************************************
  */
 
-ssize_t show_latency_timer(struct device *dev, char *buf)
+static ssize_t show_latency_timer(struct device *dev, char *buf)
 {
        struct usb_serial_port *port = to_usb_serial_port(dev);
        struct ftdi_private *priv = usb_get_serial_port_data(port);
@@ -1211,7 +1245,8 @@ ssize_t show_latency_timer(struct device *dev, char *buf)
 }
 
 /* Write a new value of the latency timer, in units of milliseconds. */
-ssize_t store_latency_timer(struct device *dev, const char *valbuf, size_t count)
+static ssize_t store_latency_timer(struct device *dev, const char *valbuf,
+                                  size_t count)
 {
        struct usb_serial_port *port = to_usb_serial_port(dev);
        struct ftdi_private *priv = usb_get_serial_port_data(port);
@@ -1241,7 +1276,8 @@ ssize_t store_latency_timer(struct device *dev, const char *valbuf, size_t count
 
 /* Write an event character directly to the FTDI register.  The ASCII
    value is in the low 8 bits, with the enable bit in the 9th bit. */
-ssize_t store_event_char(struct device *dev, const char *valbuf, size_t count)
+static ssize_t store_event_char(struct device *dev, const char *valbuf,
+                               size_t count)
 {
        struct usb_serial_port *port = to_usb_serial_port(dev);
        struct ftdi_private *priv = usb_get_serial_port_data(port);
@@ -1269,10 +1305,10 @@ ssize_t store_event_char(struct device *dev, const char *valbuf, size_t count)
        return count;
 }
 
-static DEVICE_ATTR(latency_timer, S_IWUGO | S_IRUGO, show_latency_timer, store_latency_timer);
-static DEVICE_ATTR(event_char, S_IWUGO, NULL, store_event_char);
+static DEVICE_ATTR(latency_timer, S_IWUSR | S_IRUGO, show_latency_timer, store_latency_timer);
+static DEVICE_ATTR(event_char, S_IWUSR, NULL, store_event_char);
 
-void create_sysfs_attrs(struct usb_serial *serial)
+static void create_sysfs_attrs(struct usb_serial *serial)
 {      
        struct ftdi_private *priv;
        struct usb_device *udev;
@@ -1282,14 +1318,18 @@ void create_sysfs_attrs(struct usb_serial *serial)
        priv = usb_get_serial_port_data(serial->port[0]);
        udev = serial->dev;
        
-       if (priv->chip_type == FT232BM) {
-               dbg("sysfs attributes for FT232BM");
+       /* XXX I've no idea if the original SIO supports the event_char
+        * sysfs parameter, so I'm playing it safe.  */
+       if (priv->chip_type != SIO) {
+               dbg("sysfs attributes for %s", ftdi_chip_name[priv->chip_type]);
                device_create_file(&udev->dev, &dev_attr_event_char);
-               device_create_file(&udev->dev, &dev_attr_latency_timer);
+               if (priv->chip_type == FT232BM || priv->chip_type == FT2232C) {
+                       device_create_file(&udev->dev, &dev_attr_latency_timer);
+               }
        }
 }
 
-void remove_sysfs_attrs(struct usb_serial *serial)
+static void remove_sysfs_attrs(struct usb_serial *serial)
 {
        struct ftdi_private *priv;
        struct usb_device *udev;
@@ -1299,9 +1339,12 @@ void remove_sysfs_attrs(struct usb_serial *serial)
        priv = usb_get_serial_port_data(serial->port[0]);
        udev = serial->dev;
        
-       if (priv->chip_type == FT232BM) {
+       /* XXX see create_sysfs_attrs */
+       if (priv->chip_type != SIO) {
                device_remove_file(&udev->dev, &dev_attr_event_char);
-               device_remove_file(&udev->dev, &dev_attr_latency_timer);
+               if (priv->chip_type == FT232BM || priv->chip_type == FT2232C) {
+                       device_remove_file(&udev->dev, &dev_attr_latency_timer);
+               }
        }
        
 }
@@ -1335,9 +1378,7 @@ static int ftdi_common_startup (struct usb_serial *serial)
        priv->flags = ASYNC_LOW_LATENCY;
 
        /* Increase the size of read buffers */
-       if (port->bulk_in_buffer) {
-               kfree (port->bulk_in_buffer);
-       }
+       kfree(port->bulk_in_buffer);
        port->bulk_in_buffer = kmalloc (BUFSZ, GFP_KERNEL);
        if (!port->bulk_in_buffer) {
                kfree (priv);
@@ -1348,15 +1389,15 @@ static int ftdi_common_startup (struct usb_serial *serial)
                port->read_urb->transfer_buffer_length = BUFSZ;
        }
 
+       INIT_WORK(&priv->rx_work, ftdi_process_read, port);
+
        /* Free port's existing write urb and transfer buffer. */
        if (port->write_urb) {
                usb_free_urb (port->write_urb);
                port->write_urb = NULL;
        }
-       if (port->bulk_out_buffer) {
-               kfree (port->bulk_out_buffer);
-               port->bulk_out_buffer = NULL;
-       }
+       kfree(port->bulk_out_buffer);
+       port->bulk_out_buffer = NULL;
 
        usb_set_serial_port_data(serial->port[0], priv);
        
@@ -1403,6 +1444,8 @@ static int ftdi_8U232AM_startup (struct usb_serial *serial)
        priv->chip_type = FT8U232AM;
        priv->baud_base = 48000000 / 2; /* Would be / 16, but FTDI supports 0.125, 0.25 and 0.5 divisor fractions! */
        
+       create_sysfs_attrs(serial);
+       
        return (0);
 } /* ftdi_8U232AM_startup */
 
@@ -1447,13 +1490,15 @@ static int ftdi_FT2232C_startup (struct usb_serial *serial)
        inter = serial->interface->altsetting->desc.bInterfaceNumber;
 
        if (inter) {
-               priv->interface = INTERFACE_B;
+               priv->interface = PIT_SIOB;
        }
        else  {
-               priv->interface = INTERFACE_A;
+               priv->interface = PIT_SIOA;
        }
        priv->baud_base = 48000000 / 2; /* Would be / 16, but FT2232C supports multiple of 0.125 divisor fractions! */
        
+       create_sysfs_attrs(serial);
+
        return (0);
 } /* ftdi_FT2232C_startup */
 
@@ -1576,6 +1621,7 @@ static int  ftdi_open (struct usb_serial_port *port, struct file *filp)
        spin_unlock_irqrestore(&priv->rx_lock, flags);
 
        /* Start reading from the device */
+       priv->rx_processed = 0;
        usb_fill_bulk_urb(port->read_urb, dev,
                      usb_rcvbulkpipe(dev, port->bulk_in_endpointAddress),
                      port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length,
@@ -1626,6 +1672,10 @@ static void ftdi_close (struct usb_serial_port *port, struct file *filp)
                        err("Error from RTS LOW urb");
                }
        } /* Note change no line if hupcl is off */
+
+       /* cancel any scheduled reading */
+       cancel_delayed_work(&priv->rx_work);
+       flush_scheduled_work();
        
        /* shutdown our bulk read */
        if (port->read_urb)
@@ -1821,23 +1871,14 @@ static void ftdi_read_bulk_callback (struct urb *urb, struct pt_regs *regs)
                return;
        }
 
-       /* If throttled, delay receive processing until unthrottled. */
-       spin_lock(&priv->rx_lock);
-       if (priv->rx_flags & THROTTLED) {
-               dbg("Deferring read urb processing until unthrottled");
-               priv->rx_flags |= ACTUALLY_THROTTLED;
-               spin_unlock(&priv->rx_lock);
-               return;
-       }
-       spin_unlock(&priv->rx_lock);
-
        ftdi_process_read(port);
 
 } /* ftdi_read_bulk_callback */
 
 
-static void ftdi_process_read (struct usb_serial_port *port)
+static void ftdi_process_read (void *param)
 { /* ftdi_process_read */
+       struct usb_serial_port *port = (struct usb_serial_port*)param;
        struct urb *urb;
        struct tty_struct *tty;
        struct ftdi_private *priv;
@@ -1848,6 +1889,7 @@ static void ftdi_process_read (struct usb_serial_port *port)
        int result;
        int need_flip;
        int packet_offset;
+       unsigned long flags;
 
        dbg("%s - port %d", __FUNCTION__, port->number);
 
@@ -1874,12 +1916,18 @@ static void ftdi_process_read (struct usb_serial_port *port)
 
        data = urb->transfer_buffer;
 
-        /* The first two bytes of every read packet are status */
-       if (urb->actual_length > 2) {
-               usb_serial_debug_data(debug, &port->dev, __FUNCTION__, urb->actual_length, data);
+       if (priv->rx_processed) {
+               dbg("%s - already processed: %d bytes, %d remain", __FUNCTION__,
+                               priv->rx_processed,
+                               urb->actual_length - priv->rx_processed);
        } else {
-                dbg("Status only: %03oo %03oo",data[0],data[1]);
-        }
+               /* The first two bytes of every read packet are status */
+               if (urb->actual_length > 2) {
+                       usb_serial_debug_data(debug, &port->dev, __FUNCTION__, urb->actual_length, data);
+               } else {
+                       dbg("Status only: %03oo %03oo",data[0],data[1]);
+               }
+       }
 
 
        /* TO DO -- check for hung up line and handle appropriately: */
@@ -1888,8 +1936,12 @@ static void ftdi_process_read (struct usb_serial_port *port)
        /* if CD is dropped and the line is not CLOCAL then we should hangup */
 
        need_flip = 0;
-       for (packet_offset=0; packet_offset < urb->actual_length; packet_offset += PKTSZ) {
+       for (packet_offset = priv->rx_processed; packet_offset < urb->actual_length; packet_offset += PKTSZ) {
+               int length;
+
                /* Compare new line status to the old one, signal if different */
+               /* N.B. packet may be processed more than once, but differences
+                * are only processed once.  */
                if (priv != NULL) {
                        char new_status = data[packet_offset+0] & FTDI_STATUS_B0_MASK;
                        if (new_status != priv->prev_status) {
@@ -1899,6 +1951,35 @@ static void ftdi_process_read (struct usb_serial_port *port)
                        }
                }
 
+               length = min(PKTSZ, urb->actual_length-packet_offset)-2;
+               if (length < 0) {
+                       err("%s - bad packet length: %d", __FUNCTION__, length+2);
+                       length = 0;
+               }
+
+               /* have to make sure we don't overflow the buffer
+                  with tty_insert_flip_char's */
+               if (tty->flip.count+length > TTY_FLIPBUF_SIZE) {
+                       tty_flip_buffer_push(tty);
+                       need_flip = 0;
+
+                       if (tty->flip.count != 0) {
+                               /* flip didn't work, this happens when ftdi_process_read() is
+                                * called from ftdi_unthrottle, because TTY_DONT_FLIP is set */
+                               dbg("%s - flip buffer push failed", __FUNCTION__);
+                               break;
+                       }
+               }
+               if (priv->rx_flags & THROTTLED) {
+                       dbg("%s - throttled", __FUNCTION__);
+                       break;
+               }
+               if (tty->ldisc.receive_room(tty)-tty->flip.count < length) {
+                       /* break out & wait for throttling/unthrottling to happen */
+                       dbg("%s - receive room low", __FUNCTION__);
+                       break;
+               }
+
                /* Handle errors and break */
                error_flag = TTY_NORMAL;
                /* Although the device uses a bitmask and hence can have multiple */
@@ -1921,13 +2002,8 @@ static void ftdi_process_read (struct usb_serial_port *port)
                        error_flag = TTY_FRAME;
                        dbg("FRAMING error");
                }
-               if (urb->actual_length > packet_offset + 2) {
-                       for (i = 2; (i < PKTSZ) && ((i+packet_offset) < urb->actual_length); ++i) {
-                               /* have to make sure we don't overflow the buffer
-                                 with tty_insert_flip_char's */
-                               if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
-                                       tty_flip_buffer_push(tty);
-                               }
+               if (length > 0) {
+                       for (i = 2; i < length+2; i++) {
                                /* Note that the error flag is duplicated for 
                                   every character received since we don't know
                                   which character it applied to */
@@ -1964,6 +2040,35 @@ static void ftdi_process_read (struct usb_serial_port *port)
                tty_flip_buffer_push(tty);
        }
 
+       if (packet_offset < urb->actual_length) {
+               /* not completely processed - record progress */
+               priv->rx_processed = packet_offset;
+               dbg("%s - incomplete, %d bytes processed, %d remain",
+                               __FUNCTION__, packet_offset,
+                               urb->actual_length - packet_offset);
+               /* check if we were throttled while processing */
+               spin_lock_irqsave(&priv->rx_lock, flags);
+               if (priv->rx_flags & THROTTLED) {
+                       priv->rx_flags |= ACTUALLY_THROTTLED;
+                       spin_unlock_irqrestore(&priv->rx_lock, flags);
+                       dbg("%s - deferring remainder until unthrottled",
+                                       __FUNCTION__);
+                       return;
+               }
+               spin_unlock_irqrestore(&priv->rx_lock, flags);
+               /* if the port is closed stop trying to read */
+               if (port->open_count > 0){
+                       /* delay processing of remainder */
+                       schedule_delayed_work(&priv->rx_work, 1);
+               } else {
+                       dbg("%s - port is closed", __FUNCTION__);
+               }
+               return;
+       }
+
+       /* urb is completely processed */
+       priv->rx_processed = 0;
+
        /* if the port is closed stop trying to read */
        if (port->open_count > 0){
                /* Continue trying to always read  */
@@ -2403,7 +2508,7 @@ static void ftdi_unthrottle (struct usb_serial_port *port)
        spin_unlock_irqrestore(&priv->rx_lock, flags);
 
        if (actually_throttled)
-               ftdi_process_read(port);
+               schedule_work(&priv->rx_work);
 }
 
 static int __init ftdi_init (void)