USB: RTS/CTS handshaking support, DTR fixes for MCT U232 serial adapter
authorDave Platt <dplatt@radagast.org>
Tue, 8 May 2007 18:00:12 +0000 (11:00 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 12 Jul 2007 23:29:48 +0000 (16:29 -0700)
Improvements and fixes to the MCT U232 USB/serial interface driver.
Implement RTS/CTS hardware flow control.  Implement HUPCL.  Bring
handling of DTR and RTS into conformance with other Linux serial
port drivers - assert both signals when opening device, even if
"crtscts" is not currently selected.

Signed-off-by: Dave Platt <dplatt@radagast.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/serial/mct_u232.c
drivers/usb/serial/mct_u232.h

index 3db1adc..204f0f9 100644 (file)
@@ -81,7 +81,7 @@
 /*
  * Version Information
  */
-#define DRIVER_VERSION "z2.0"          /* Linux in-kernel version */
+#define DRIVER_VERSION "z2.1"          /* Linux in-kernel version */
 #define DRIVER_AUTHOR "Wolfgang Grandegger <wolfgang@ces.ch>"
 #define DRIVER_DESC "Magic Control Technology USB-RS232 converter driver"
 
@@ -110,6 +110,10 @@ static int  mct_u232_tiocmget               (struct usb_serial_port *port,
 static int  mct_u232_tiocmset           (struct usb_serial_port *port,
                                          struct file *file, unsigned int set,
                                          unsigned int clear);
+static void mct_u232_throttle           (struct usb_serial_port *port);
+static void mct_u232_unthrottle                 (struct usb_serial_port *port);
+
+
 /*
  * All of the device info needed for the MCT USB-RS232 converter.
  */
@@ -145,6 +149,8 @@ static struct usb_serial_driver mct_u232_device = {
        .num_ports =         1,
        .open =              mct_u232_open,
        .close =             mct_u232_close,
+       .throttle =          mct_u232_throttle,
+       .unthrottle =        mct_u232_unthrottle,
        .read_int_callback = mct_u232_read_int_callback,
        .ioctl =             mct_u232_ioctl,
        .set_termios =       mct_u232_set_termios,
@@ -162,8 +168,11 @@ struct mct_u232_private {
        unsigned char        last_lcr;      /* Line Control Register */
        unsigned char        last_lsr;      /* Line Status Register */
        unsigned char        last_msr;      /* Modem Status Register */
+       unsigned int         rx_flags;      /* Throttling flags */
 };
 
+#define THROTTLED              0x01
+
 /*
  * Handle vendor specific USB requests
  */
@@ -216,11 +225,13 @@ static int mct_u232_calculate_baud_rate(struct usb_serial *serial, int value)
        }
 }
 
-static int mct_u232_set_baud_rate(struct usb_serial *serial, int value)
+static int mct_u232_set_baud_rate(struct usb_serial *serial, struct usb_serial_port *port,
+                                 int value)
 {
        __le32 divisor;
         int rc;
         unsigned char zero_byte = 0;
+        unsigned char cts_enable_byte = 0;
 
        divisor = cpu_to_le32(mct_u232_calculate_baud_rate(serial, value));
 
@@ -238,10 +249,17 @@ static int mct_u232_set_baud_rate(struct usb_serial *serial, int value)
           'baud rate change' message.  The actual functionality of the
           request codes in these messages is not fully understood but these
           particular codes are never seen in any operation besides a baud
-          rate change.  Both of these messages send a single byte of data
-          whose value is always zero.  The second of these two extra messages
-          is required in order for data to be properly written to an RS-232
-          device which does not assert the 'CTS' signal. */
+          rate change.  Both of these messages send a single byte of data.
+          In the first message, the value of this byte is always zero.
+
+          The second message has been determined experimentally to control
+          whether data will be transmitted to a device which is not asserting
+          the 'CTS' signal.  If the second message's data byte is zero, data
+          will be transmitted even if 'CTS' is not asserted (i.e. no hardware
+          flow control).  if the second message's data byte is nonzero (a value
+          of 1 is used by this driver), data will not be transmitted to a device
+          which is not asserting 'CTS'.
+       */
 
        rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
                             MCT_U232_SET_UNKNOWN1_REQUEST, 
@@ -252,14 +270,19 @@ static int mct_u232_set_baud_rate(struct usb_serial *serial, int value)
                err("Sending USB device request code %d failed (error = %d)", 
                    MCT_U232_SET_UNKNOWN1_REQUEST, rc);
 
+       if (port && C_CRTSCTS(port->tty)) {
+          cts_enable_byte = 1;
+       }
+
+        dbg("set_baud_rate: send second control message, data = %02X", cts_enable_byte);
        rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
-                            MCT_U232_SET_UNKNOWN2_REQUEST, 
+                            MCT_U232_SET_CTS_REQUEST,
                             MCT_U232_SET_REQUEST_TYPE,
-                            0, 0, &zero_byte, MCT_U232_SET_UNKNOWN2_SIZE, 
+                            0, 0, &cts_enable_byte, MCT_U232_SET_CTS_SIZE,
                             WDR_TIMEOUT);
        if (rc < 0)
-               err("Sending USB device request code %d failed (error = %d)", 
-                   MCT_U232_SET_UNKNOWN2_REQUEST, rc);
+         err("Sending USB device request code %d failed (error = %d)",
+             MCT_U232_SET_CTS_REQUEST, rc);
 
         return rc;
 } /* mct_u232_set_baud_rate */
@@ -458,8 +481,25 @@ error:
 
 static void mct_u232_close (struct usb_serial_port *port, struct file *filp)
 {
+       unsigned int c_cflag;
+       unsigned long flags;
+       unsigned int control_state;
+       struct mct_u232_private *priv = usb_get_serial_port_data(port);
        dbg("%s port %d", __FUNCTION__, port->number);
 
+       if (port->tty) {
+               c_cflag = port->tty->termios->c_cflag;
+               if (c_cflag & HUPCL) {
+                  /* drop DTR and RTS */
+                  spin_lock_irqsave(&priv->lock, flags);
+                  priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS);
+                  control_state = priv->control_state;
+                  spin_unlock_irqrestore(&priv->lock, flags);
+                  mct_u232_set_modem_ctrl(port->serial, control_state);
+               }
+       }
+
+
        if (port->serial->dev) {
                /* shutdown our urbs */
                usb_kill_urb(port->write_urb);
@@ -565,11 +605,10 @@ static void mct_u232_set_termios (struct usb_serial_port *port,
 {
        struct usb_serial *serial = port->serial;
        struct mct_u232_private *priv = usb_get_serial_port_data(port);
-       unsigned int iflag = port->tty->termios->c_iflag;
        unsigned int cflag = port->tty->termios->c_cflag;
        unsigned int old_cflag = old_termios->c_cflag;
        unsigned long flags;
-       unsigned int control_state, new_state;
+       unsigned int control_state;
        unsigned char last_lcr;
 
        /* get a local copy of the current port settings */
@@ -585,18 +624,14 @@ static void mct_u232_set_termios (struct usb_serial_port *port,
         * Premature optimization is the root of all evil.
         */
 
-        /* reassert DTR and (maybe) RTS on transition from B0 */
+        /* reassert DTR and RTS on transition from B0 */
        if ((old_cflag & CBAUD) == B0) {
                dbg("%s: baud was B0", __FUNCTION__);
-               control_state |= TIOCM_DTR;
-               /* don't set RTS if using hardware flow control */
-               if (!(old_cflag & CRTSCTS)) {
-                       control_state |= TIOCM_RTS;
-               }
+               control_state |= TIOCM_DTR | TIOCM_RTS;
                mct_u232_set_modem_ctrl(serial, control_state);
        }
 
-       mct_u232_set_baud_rate(serial, cflag & CBAUD);
+       mct_u232_set_baud_rate(serial, port, cflag & CBAUD);
 
        if ((cflag & CBAUD) == B0 ) {
                dbg("%s: baud is B0", __FUNCTION__);
@@ -638,21 +673,6 @@ static void mct_u232_set_termios (struct usb_serial_port *port,
 
        mct_u232_set_line_ctrl(serial, last_lcr);
 
-       /*
-        * Set flow control: well, I do not really now how to handle DTR/RTS.
-        * Just do what we have seen with SniffUSB on Win98.
-        */
-       /* Drop DTR/RTS if no flow control otherwise assert */
-       new_state = control_state;
-       if ((iflag & IXOFF) || (iflag & IXON) || (cflag & CRTSCTS))
-               new_state |= TIOCM_DTR | TIOCM_RTS;
-       else
-               new_state &= ~(TIOCM_DTR | TIOCM_RTS);
-       if (new_state != control_state) {
-               mct_u232_set_modem_ctrl(serial, new_state);
-               control_state = new_state;
-       }
-
        /* save off the modified port settings */
        spin_lock_irqsave(&priv->lock, flags);
        priv->control_state = control_state;
@@ -747,6 +767,50 @@ static int mct_u232_ioctl (struct usb_serial_port *port, struct file * file,
        return 0;
 } /* mct_u232_ioctl */
 
+static void mct_u232_throttle (struct usb_serial_port *port)
+{
+       struct mct_u232_private *priv = usb_get_serial_port_data(port);
+       unsigned long flags;
+       unsigned int control_state;
+       struct tty_struct *tty;
+
+       tty = port->tty;
+       dbg("%s - port %d", __FUNCTION__, port->number);
+
+       spin_lock_irqsave(&priv->lock, flags);
+       priv->rx_flags |= THROTTLED;
+       if (C_CRTSCTS(tty)) {
+         priv->control_state &= ~TIOCM_RTS;
+         control_state = priv->control_state;
+         spin_unlock_irqrestore(&priv->lock, flags);
+         (void) mct_u232_set_modem_ctrl(port->serial, control_state);
+       } else {
+         spin_unlock_irqrestore(&priv->lock, flags);
+       }
+}
+
+
+static void mct_u232_unthrottle (struct usb_serial_port *port)
+{
+       struct mct_u232_private *priv = usb_get_serial_port_data(port);
+       unsigned long flags;
+       unsigned int control_state;
+       struct tty_struct *tty;
+
+       dbg("%s - port %d", __FUNCTION__, port->number);
+
+       tty = port->tty;
+       spin_lock_irqsave(&priv->lock, flags);
+       if ((priv->rx_flags & THROTTLED) && C_CRTSCTS(tty)) {
+         priv->rx_flags &= ~THROTTLED;
+         priv->control_state |= TIOCM_RTS;
+         control_state = priv->control_state;
+         spin_unlock_irqrestore(&priv->lock, flags);
+         (void) mct_u232_set_modem_ctrl(port->serial, control_state);
+       } else {
+         spin_unlock_irqrestore(&priv->lock, flags);
+       }
+}
 
 static int __init mct_u232_init (void)
 {
index 73dd0d9..a61bac8 100644 (file)
 #define MCT_U232_SET_UNKNOWN1_REQUEST   11  /* Unknown functionality */
 #define MCT_U232_SET_UNKNOWN1_SIZE       1
 
-/* This USB device request code is not well understood.  It is transmitted by
-   the MCT-supplied Windows driver whenever the baud rate changes. 
+/* This USB device request code appears to control whether CTS is required
+   during transmission.
    
-   Without this USB device request, the USB/RS-232 adapter will not write to
-   RS-232 devices which do not assert the 'CTS' signal.
+   Sending a zero byte allows data transmission to a device which is not
+   asserting CTS.  Sending a '1' byte will cause transmission to be deferred
+   until the device asserts CTS.
 */
-#define MCT_U232_SET_UNKNOWN2_REQUEST   12  /* Unknown functionality */
-#define MCT_U232_SET_UNKNOWN2_SIZE       1
+#define MCT_U232_SET_CTS_REQUEST   12
+#define MCT_U232_SET_CTS_SIZE       1
 
 /*
  * Baud rate (divisor)
@@ -439,7 +440,7 @@ static int mct_u232_calculate_baud_rate(struct usb_serial *serial, int value);
  * which says "U232-P9" ;-)
  * 
  * The circuit board inside the adaptor contains a Philips PDIUSBD12
- * USB endpoint chip and a Phillips P87C52UBAA microcontroller with
+ * USB endpoint chip and a Philips P87C52UBAA microcontroller with
  * embedded UART.  Exhaustive documentation for these is available at:
  *
  *   http://www.semiconductors.philips.com/pip/p87c52ubaa