USB: RTS/CTS handshaking support, DTR fixes for MCT U232 serial adapter

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>

diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c
index 3db1adc..204f0f9 100644
--- a/drivers/usb/serial/mct_u232.c
+++ b/drivers/usb/serial/mct_u232.c
@@ -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_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 @@
 	.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 @@
 	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_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 @@
 	   '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 @@
 		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 @@
 
 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 @@
 {
 	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 @@
 	 * 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 @@
 
 	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 @@
 	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)
 {
diff --git a/drivers/usb/serial/mct_u232.h b/drivers/usb/serial/mct_u232.h
index 73dd0d9..a61bac8 100644
--- a/drivers/usb/serial/mct_u232.h
+++ b/drivers/usb/serial/mct_u232.h
@@ -63,14 +63,15 @@
 #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 @@
  * 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