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>
This commit is contained in:
Dave Platt 2007-05-08 11:00:12 -07:00 committed by Greg Kroah-Hartman
parent 01cd081920
commit 45b844df5a
2 changed files with 106 additions and 41 deletions

View 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)
{

View file

@ -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 @@ 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