USB: ftdi_sio.c: Use ftdi async_icount structure for TIOCMIWAIT, as in other drivers
[linux-flexiantxendom0.git] / drivers / usb / serial / ftdi_sio.c
index d3e58b5..8fe034d 100644 (file)
@@ -17,7 +17,7 @@
  * See Documentation/usb/usb-serial.txt for more information on using this
  * driver
  *
- * See http://ftdi-usb-sio.sourceforge.net for upto date testing info
+ * See http://ftdi-usb-sio.sourceforge.net for up to date testing info
  *     and extra documentation
  *
  * Change entries from 2004 and earlier can be found in versions of this
@@ -73,8 +73,10 @@ struct ftdi_private {
                                 */
        int flags;              /* some ASYNC_xxxx flags are supported */
        unsigned long last_dtr_rts;     /* saved modem control outputs */
+       struct async_icount     icount;
        wait_queue_head_t delta_msr_wait; /* Used for TIOCMIWAIT */
        char prev_status, diff_status;        /* Used for TIOCMIWAIT */
+       char transmit_empty;    /* If transmitter is empty or not */
        struct usb_serial_port *port;
        __u16 interface;        /* FT2232C, FT2232H or FT4232H port interface
                                   (0 for FT232/245) */
@@ -99,6 +101,8 @@ struct ftdi_sio_quirk {
 static int   ftdi_jtag_probe(struct usb_serial *serial);
 static int   ftdi_mtxorb_hack_setup(struct usb_serial *serial);
 static int   ftdi_NDI_device_setup(struct usb_serial *serial);
+static int   ftdi_stmclite_probe(struct usb_serial *serial);
+static int   ftdi_8u2232c_probe(struct usb_serial *serial);
 static void  ftdi_USB_UIRT_setup(struct ftdi_private *priv);
 static void  ftdi_HE_TIRA1_setup(struct ftdi_private *priv);
 
@@ -122,6 +126,14 @@ static struct ftdi_sio_quirk ftdi_HE_TIRA1_quirk = {
        .port_probe = ftdi_HE_TIRA1_setup,
 };
 
+static struct ftdi_sio_quirk ftdi_stmclite_quirk = {
+       .probe  = ftdi_stmclite_probe,
+};
+
+static struct ftdi_sio_quirk ftdi_8u2232c_quirk = {
+       .probe  = ftdi_8u2232c_probe,
+};
+
 /*
  * The 8U232AM has the same API as the sio except for:
  * - it can support MUCH higher baudrates; up to:
@@ -145,6 +157,9 @@ static struct ftdi_sio_quirk ftdi_HE_TIRA1_quirk = {
  * /sys/bus/usb/ftdi_sio/new_id, then send patch/report!
  */
 static struct usb_device_id id_table_combined [] = {
+       { USB_DEVICE(FTDI_VID, FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_CTI_MINI_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_CTI_NANO_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_AMC232_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_CANUSB_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_CANDAPTER_PID) },
@@ -169,8 +184,10 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_8U232AM_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_8U232AM_ALT_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_232RL_PID) },
-       { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) ,
+               .driver_info = (kernel_ulong_t)&ftdi_8u2232c_quirk },
        { USB_DEVICE(FTDI_VID, FTDI_4232H_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_232H_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_PID) },
@@ -191,6 +208,8 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_XF_640_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_XF_642_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_DSS20_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_URBAN_0_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_URBAN_1_PID) },
        { USB_DEVICE(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_VNHCPCUSB_D_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_MTXORB_0_PID) },
@@ -201,6 +220,7 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_MTXORB_5_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_MTXORB_6_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_R2000KU_TRUE_RNG) },
+       { USB_DEVICE(FTDI_VID, FTDI_VARDAAN_PID) },
        { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0100_PID) },
        { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0101_PID) },
        { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0102_PID) },
@@ -518,6 +538,7 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_8_PID) },
        { USB_DEVICE(IDTECH_VID, IDTECH_IDT1221U_PID) },
        { USB_DEVICE(OCT_VID, OCT_US101_PID) },
+       { USB_DEVICE(OCT_VID, OCT_DK201_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_HE_TIRA1_PID),
                .driver_info = (kernel_ulong_t)&ftdi_HE_TIRA1_quirk },
        { USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID),
@@ -556,6 +577,7 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_IBS_APP70_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_IBS_PEDO_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_IBS_PROD_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_TAVIR_STK500_PID) },
        /*
         * ELV devices:
         */
@@ -614,6 +636,7 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_OCEANIC_PID) },
        { USB_DEVICE(TTI_VID, TTI_QL355P_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_RM_CANVIEW_PID) },
+       { USB_DEVICE(ACTON_VID, ACTON_SPECTRAPRO_PID) },
        { USB_DEVICE(CONTEC_VID, CONTEC_COM1USBH_PID) },
        { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) },
        { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) },
@@ -635,6 +658,7 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, EVER_ECO_PRO_CDS) },
        { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_3_PID) },
        { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_0_PID) },
        { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_1_PID) },
        { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_2_PID) },
@@ -674,7 +698,17 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_PCDJ_DAC2_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_RRCIRKITS_LOCOBUFFER_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_ASK_RDR400_PID) },
-       { USB_DEVICE(ICOM_ID1_VID, ICOM_ID1_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_1_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_OPC_U_UC_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C1_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C2_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP2D_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VT_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VR_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVT_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVR_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVT_PID) },
+       { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVR_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_ACG_HFDUAL_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_YEI_SERVOCENTER31_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_THORLABS_PID) },
@@ -696,6 +730,7 @@ static struct usb_device_id id_table_combined [] = {
                .driver_info = (kernel_ulong_t)&ftdi_NDI_device_quirk },
        { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) },
        { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_SERIAL_VX7_PID) },
+       { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_CT29B_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_MAXSTREAM_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_PHI_FISCO_PID) },
        { USB_DEVICE(TML_VID, TML_USB_SERIAL_PID) },
@@ -703,6 +738,8 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_PROPOX_JTAGCABLEII_PID) },
        { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_PID),
                .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
+       { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_H_PID),
+               .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { USB_DEVICE(FIC_VID, FIC_NEO1973_DEBUG_PID),
                .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { USB_DEVICE(FTDI_VID, FTDI_OOCDLINK_PID),
@@ -711,6 +748,8 @@ static struct usb_device_id id_table_combined [] = {
                .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { USB_DEVICE(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID),
                .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
+       { USB_DEVICE(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID),
+               .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { USB_DEVICE(FTDI_VID, FTDI_TURTELIZER_PID),
                .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) },
@@ -766,6 +805,8 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, MARVELL_OPENRD_PID),
                .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { USB_DEVICE(FTDI_VID, HAMEG_HO820_PID) },
+       { USB_DEVICE(FTDI_VID, HAMEG_HO720_PID) },
+       { USB_DEVICE(FTDI_VID, HAMEG_HO730_PID) },
        { USB_DEVICE(FTDI_VID, HAMEG_HO870_PID) },
        { USB_DEVICE(FTDI_VID, MJSG_GENERIC_PID) },
        { USB_DEVICE(FTDI_VID, MJSG_SR_RADIO_PID) },
@@ -791,6 +832,14 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MAXI_WING_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MEDIA_WING_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_WING_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LOGBOOKML_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LS_LOGBOOK_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_HS_LOGBOOK_PID) },
+       { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) },
+       { USB_DEVICE(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID),
+               .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
+       { USB_DEVICE(ST_VID, ST_STMCLT1030_PID),
+               .driver_info = (kernel_ulong_t)&ftdi_stmclite_quirk },
        { },                                    /* Optional parameter entry */
        { }                                     /* Terminating entry */
 };
@@ -812,7 +861,8 @@ static const char *ftdi_chip_name[] = {
        [FT2232C] = "FT2232C",
        [FT232RL] = "FT232RL",
        [FT2232H] = "FT2232H",
-       [FT4232H] = "FT4232H"
+       [FT4232H] = "FT4232H",
+       [FT232H]  = "FT232H"
 };
 
 
@@ -837,10 +887,12 @@ static int ftdi_prepare_write_buffer(struct usb_serial_port *port,
                                                void *dest, size_t size);
 static void ftdi_set_termios(struct tty_struct *tty,
                        struct usb_serial_port *port, struct ktermios *old);
-static int  ftdi_tiocmget(struct tty_struct *tty, struct file *file);
-static int  ftdi_tiocmset(struct tty_struct *tty, struct file *file,
+static int  ftdi_tiocmget(struct tty_struct *tty);
+static int  ftdi_tiocmset(struct tty_struct *tty,
                        unsigned int set, unsigned int clear);
-static int  ftdi_ioctl(struct tty_struct *tty, struct file *file,
+static int ftdi_get_icount(struct tty_struct *tty,
+                          struct serial_icounter_struct *icount);
+static int  ftdi_ioctl(struct tty_struct *tty,
                        unsigned int cmd, unsigned long arg);
 static void ftdi_break_ctl(struct tty_struct *tty, int break_state);
 
@@ -874,6 +926,7 @@ static struct usb_serial_driver ftdi_sio_device = {
        .prepare_write_buffer = ftdi_prepare_write_buffer,
        .tiocmget =             ftdi_tiocmget,
        .tiocmset =             ftdi_tiocmset,
+       .get_icount =           ftdi_get_icount,
        .ioctl =                ftdi_ioctl,
        .set_termios =          ftdi_set_termios,
        .break_ctl =            ftdi_break_ctl,
@@ -946,7 +999,7 @@ static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base)
        int divisor3;
 
        /* hi-speed baud rate is 10-bit sampling instead of 16-bit */
-       divisor3 = (base / 10 / baud) * 8;
+       divisor3 = base * 8 / (baud * 10);
 
        divisor = divisor3 >> 3;
        divisor |= (__u32)divfrac[divisor3 & 0x7] << 14;
@@ -1132,7 +1185,8 @@ static __u32 get_ftdi_divisor(struct tty_struct *tty,
                break;
        case FT2232H: /* FT2232H chip */
        case FT4232H: /* FT4232H chip */
-               if ((baud <= 12000000) & (baud >= 1200)) {
+       case FT232H:  /* FT232H chip */
+               if ((baud <= 12000000) && (baud >= 1200)) {
                        div_value = ftdi_2232h_baud_to_divisor(baud);
                } else if (baud < 1200) {
                        div_value = ftdi_232bm_baud_to_divisor(baud);
@@ -1166,7 +1220,10 @@ static int change_speed(struct tty_struct *tty, struct usb_serial_port *port)
        urb_index_value = get_ftdi_divisor(tty, port);
        urb_value = (__u16)urb_index_value;
        urb_index = (__u16)(urb_index_value >> 16);
-       if (priv->interface) {  /* FT2232C */
+       if ((priv->chip_type == FT2232C) || (priv->chip_type == FT2232H) ||
+               (priv->chip_type == FT4232H) || (priv->chip_type == FT232H)) {
+               /* Probably the BM type needs the MSB of the encoded fractional
+                * divider also moved like for the chips above. Any infos? */
                urb_index = (__u16)((urb_index << 8) | priv->interface);
        }
 
@@ -1315,6 +1372,23 @@ check_and_exit:
        return 0;
 }
 
+static int get_lsr_info(struct usb_serial_port *port,
+                       struct serial_struct __user *retinfo)
+{
+       struct ftdi_private *priv = usb_get_serial_port_data(port);
+       unsigned int result = 0;
+
+       if (!retinfo)
+               return -EFAULT;
+
+       if (priv->transmit_empty)
+               result = TIOCSER_TEMT;
+
+       if (copy_to_user(retinfo, &result, sizeof(unsigned int)))
+               return -EFAULT;
+       return 0;
+}
+
 
 /* Determine type of FTDI chip based on USB config and descriptor. */
 static void ftdi_determine_type(struct usb_serial_port *port)
@@ -1376,9 +1450,12 @@ static void ftdi_determine_type(struct usb_serial_port *port)
        } else if (version < 0x600) {
                /* Assume it's an FT232BM (or FT245BM) */
                priv->chip_type = FT232BM;
-       } else {
-               /* Assume it's an FT232R */
+       } else if (version < 0x900) {
+               /* Assume it's an FT232RL */
                priv->chip_type = FT232RL;
+       } else {
+               /* Assume it's an FT232H */
+               priv->chip_type = FT232H;
        }
        dev_info(&udev->dev, "Detected %s\n", ftdi_chip_name[priv->chip_type]);
 }
@@ -1417,7 +1494,7 @@ static void ftdi_set_max_packet_size(struct usb_serial_port *port)
        }
 
        /* set max packet size based on descriptor */
-       priv->max_packet_size = le16_to_cpu(ep_desc->wMaxPacketSize);
+       priv->max_packet_size = usb_endpoint_maxp(ep_desc);
 
        dev_info(&udev->dev, "Setting MaxPacketSize %d\n", priv->max_packet_size);
 }
@@ -1506,7 +1583,8 @@ static int create_sysfs_attrs(struct usb_serial_port *port)
                     priv->chip_type == FT2232C ||
                     priv->chip_type == FT232RL ||
                     priv->chip_type == FT2232H ||
-                    priv->chip_type == FT4232H)) {
+                    priv->chip_type == FT4232H ||
+                    priv->chip_type == FT232H)) {
                        retval = device_create_file(&port->dev,
                                                    &dev_attr_latency_timer);
                }
@@ -1527,7 +1605,8 @@ static void remove_sysfs_attrs(struct usb_serial_port *port)
                    priv->chip_type == FT2232C ||
                    priv->chip_type == FT232RL ||
                    priv->chip_type == FT2232H ||
-                   priv->chip_type == FT4232H) {
+                   priv->chip_type == FT4232H ||
+                    priv->chip_type == FT232H) {
                        device_remove_file(&port->dev, &dev_attr_latency_timer);
                }
        }
@@ -1575,6 +1654,7 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port)
 
        kref_init(&priv->kref);
        mutex_init(&priv->cfg_lock);
+       memset(&priv->icount, 0x00, sizeof(priv->icount));
        init_waitqueue_head(&priv->delta_msr_wait);
 
        priv->flags = ASYNC_LOW_LATENCY;
@@ -1672,6 +1752,37 @@ static int ftdi_jtag_probe(struct usb_serial *serial)
        return 0;
 }
 
+static int ftdi_8u2232c_probe(struct usb_serial *serial)
+{
+       struct usb_device *udev = serial->dev;
+
+       dbg("%s", __func__);
+
+       if (strcmp(udev->manufacturer, "CALAO Systems") == 0)
+               return ftdi_jtag_probe(serial);
+
+       return 0;
+}
+
+/*
+ * First and second port on STMCLiteadaptors is reserved for JTAG interface
+ * and the forth port for pio
+ */
+static int ftdi_stmclite_probe(struct usb_serial *serial)
+{
+       struct usb_device *udev = serial->dev;
+       struct usb_interface *interface = serial->interface;
+
+       dbg("%s", __func__);
+
+       if (interface == udev->actconfig->interface[2])
+               return 0;
+
+       dev_info(&udev->dev, "Ignoring serial port reserved for JTAG\n");
+
+       return -ENODEV;
+}
+
 /*
  * The Matrix Orbital VK204-25-USB has an invalid IN endpoint.
  * We have to correct it if we want to read from it.
@@ -1807,6 +1918,7 @@ static int ftdi_prepare_write_buffer(struct usb_serial_port *port,
                        c = kfifo_out(&port->write_fifo, &buffer[i + 1], len);
                        if (!c)
                                break;
+                       priv->icount.tx += c;
                        buffer[i] = (c << 2) + 1;
                        count += c + 1;
                }
@@ -1814,6 +1926,7 @@ static int ftdi_prepare_write_buffer(struct usb_serial_port *port,
        } else {
                count = kfifo_out_locked(&port->write_fifo, dest, size,
                                                                &port->lock);
+               priv->icount.tx += count;
        }
 
        return count;
@@ -1841,6 +1954,14 @@ static int ftdi_process_packet(struct tty_struct *tty,
           N.B. packet may be processed more than once, but differences
           are only processed once.  */
        status = packet[0] & FTDI_STATUS_B0_MASK;
+       if (status & FTDI_RS0_CTS)
+               priv->icount.cts++;
+       if (status & FTDI_RS0_DSR)
+               priv->icount.dsr++;
+       if (status & FTDI_RS0_RI)
+               priv->icount.rng++;
+       if (status & FTDI_RS0_RLSD)
+               priv->icount.dcd++;
        if (status != priv->prev_status) {
                priv->diff_status |= status ^ priv->prev_status;
                wake_up_interruptible(&priv->delta_msr_wait);
@@ -1853,20 +1974,32 @@ static int ftdi_process_packet(struct tty_struct *tty,
                 * over framing errors */
                if (packet[1] & FTDI_RS_BI) {
                        flag = TTY_BREAK;
+                       priv->icount.brk++;
                        usb_serial_handle_break(port);
                } else if (packet[1] & FTDI_RS_PE) {
                        flag = TTY_PARITY;
+                       priv->icount.parity++;
                } else if (packet[1] & FTDI_RS_FE) {
                        flag = TTY_FRAME;
+                       priv->icount.frame++;
                }
                /* Overrun is special, not associated with a char */
-               if (packet[1] & FTDI_RS_OE)
+               if (packet[1] & FTDI_RS_OE) {
+                       priv->icount.overrun++;
                        tty_insert_flip_char(tty, 0, TTY_OVERRUN);
+               }
        }
 
+       /* save if the transmitter is empty or not */
+       if (packet[1] & FTDI_RS_TEMT)
+               priv->transmit_empty = 1;
+       else
+               priv->transmit_empty = 0;
+
        len -= 2;
        if (!len)
                return 0;       /* status only */
+       priv->icount.rx += len;
        ch = packet + 2;
 
        if (port->port.console && port->sysrq) {
@@ -2105,10 +2238,9 @@ static void ftdi_set_termios(struct tty_struct *tty,
                }
 
        }
-       return;
 }
 
-static int ftdi_tiocmget(struct tty_struct *tty, struct file *file)
+static int ftdi_tiocmget(struct tty_struct *tty)
 {
        struct usb_serial_port *port = tty->driver_data;
        struct ftdi_private *priv = usb_get_serial_port_data(port);
@@ -2135,6 +2267,7 @@ static int ftdi_tiocmget(struct tty_struct *tty, struct file *file)
        case FT232RL:
        case FT2232H:
        case FT4232H:
+       case FT232H:
                len = 2;
                break;
        default:
@@ -2161,7 +2294,7 @@ out:
        return ret;
 }
 
-static int ftdi_tiocmset(struct tty_struct *tty, struct file *file,
+static int ftdi_tiocmset(struct tty_struct *tty,
                        unsigned int set, unsigned int clear)
 {
        struct usb_serial_port *port = tty->driver_data;
@@ -2169,11 +2302,34 @@ static int ftdi_tiocmset(struct tty_struct *tty, struct file *file,
        return update_mctrl(port, set, clear);
 }
 
-static int ftdi_ioctl(struct tty_struct *tty, struct file *file,
+static int ftdi_get_icount(struct tty_struct *tty,
+                               struct serial_icounter_struct *icount)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       struct ftdi_private *priv = usb_get_serial_port_data(port);
+       struct async_icount *ic = &priv->icount;
+
+       icount->cts = ic->cts;
+       icount->dsr = ic->dsr;
+       icount->rng = ic->rng;
+       icount->dcd = ic->dcd;
+       icount->tx = ic->tx;
+       icount->rx = ic->rx;
+       icount->frame = ic->frame;
+       icount->parity = ic->parity;
+       icount->overrun = ic->overrun;
+       icount->brk = ic->brk;
+       icount->buf_overrun = ic->buf_overrun;
+       return 0;
+}
+
+static int ftdi_ioctl(struct tty_struct *tty,
                                        unsigned int cmd, unsigned long arg)
 {
        struct usb_serial_port *port = tty->driver_data;
        struct ftdi_private *priv = usb_get_serial_port_data(port);
+       struct async_icount cnow;
+       struct async_icount cprev;
 
        dbg("%s cmd 0x%04x", __func__, cmd);
 
@@ -2197,36 +2353,29 @@ static int ftdi_ioctl(struct tty_struct *tty, struct file *file,
         * This code is borrowed from linux/drivers/char/serial.c
         */
        case TIOCMIWAIT:
-               while (priv != NULL) {
+               cprev = priv->icount;
+               while (1) {
                        interruptible_sleep_on(&priv->delta_msr_wait);
                        /* see if a signal did it */
                        if (signal_pending(current))
                                return -ERESTARTSYS;
-                       else {
-                               char diff = priv->diff_status;
-
-                               if (diff == 0)
-                                       return -EIO; /* no change => error */
-
-                               /* Consume all events */
-                               priv->diff_status = 0;
-
-                               /* Return 0 if caller wanted to know about
-                                  these bits */
-                               if (((arg & TIOCM_RNG) && (diff & FTDI_RS0_RI)) ||
-                                   ((arg & TIOCM_DSR) && (diff & FTDI_RS0_DSR)) ||
-                                   ((arg & TIOCM_CD)  && (diff & FTDI_RS0_RLSD)) ||
-                                   ((arg & TIOCM_CTS) && (diff & FTDI_RS0_CTS))) {
-                                       return 0;
-                               }
-                               /*
-                                * Otherwise caller can't care less about what
-                                * happened,and so we continue to wait for more
-                                * events.
-                                */
+                       cnow = priv->icount;
+                       if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+                           cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
+                               return -EIO; /* no change => error */
+                       if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+                           ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+                           ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) ||
+                           ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
+                               return 0;
                        }
+                       cprev = cnow;
                }
-               return 0;
+               /* not reached */
+               break;
+       case TIOCSERGETLSR:
+               return get_lsr_info(port, (struct serial_struct __user *)arg);
+               break;
        default:
                break;
        }