Skip to content
ftdi_sio.c 81.1 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
	if (usb_control_msg(port->serial->dev, usb_sndctrlpipe(port->serial->dev, 0),
			    FTDI_SIO_SET_DATA_REQUEST,
Linus Torvalds's avatar
Linus Torvalds committed
			    FTDI_SIO_SET_DATA_REQUEST_TYPE,
			    urb_value , priv->interface,
			    buf, 0, WDR_TIMEOUT) < 0) {
		err("%s FAILED to enable/disable break state (state was %d)", __func__,break_state);
Linus Torvalds's avatar
Linus Torvalds committed

	dbg("%s break state is %d - urb is %d", __func__,break_state, urb_value);
Linus Torvalds's avatar
Linus Torvalds committed
}


/* old_termios contains the original termios settings and tty->termios contains
 * the new setting to be used
 * WARNING: set_termios calls this with old_termios in kernel space
 */

static void ftdi_set_termios (struct usb_serial_port *port, struct ktermios *old_termios)
Linus Torvalds's avatar
Linus Torvalds committed
{ /* ftdi_termios */
	struct usb_device *dev = port->serial->dev;
	struct ftdi_private *priv = usb_get_serial_port_data(port);
	struct ktermios *termios = port->tty->termios;
	unsigned int cflag = termios->c_cflag;
Linus Torvalds's avatar
Linus Torvalds committed
	__u16 urb_value; /* will hold the new flags */
	char buf[1]; /* Perhaps I should dynamically alloc this? */
Linus Torvalds's avatar
Linus Torvalds committed
	// Added for xon/xoff support
	unsigned int iflag = termios->c_iflag;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned char vstop;
	unsigned char vstart;
	dbg("%s", __func__);
Linus Torvalds's avatar
Linus Torvalds committed

	/* Force baud rate if this device requires it, unless it is set to B0. */
	if (priv->force_baud && ((termios->c_cflag & CBAUD) != B0)) {
		dbg("%s: forcing baud rate for this device", __func__);
		tty_encode_baud_rate(port->tty, priv->force_baud,
					priv->force_baud);
Linus Torvalds's avatar
Linus Torvalds committed
	}

	/* Force RTS-CTS if this device requires it. */
	if (priv->force_rtscts) {
		dbg("%s: forcing rtscts for this device", __func__);
		termios->c_cflag |= CRTSCTS;
	cflag = termios->c_cflag;
Linus Torvalds's avatar
Linus Torvalds committed

	/* FIXME -For this cut I don't care if the line is really changing or
	   not  - so just do the change regardless  - should be able to
Linus Torvalds's avatar
Linus Torvalds committed
	   compare old_termios and tty->termios */
	/* NOTE These routines can get interrupted by
	   ftdi_sio_read_bulk_callback  - need to examine what this
Linus Torvalds's avatar
Linus Torvalds committed
           means - don't see any problems yet */
Linus Torvalds's avatar
Linus Torvalds committed
	/* Set number of data bits, parity, stop bits */
	termios->c_cflag &= ~CMSPAR;

Linus Torvalds's avatar
Linus Torvalds committed
	urb_value = 0;
	urb_value |= (cflag & CSTOPB ? FTDI_SIO_SET_DATA_STOP_BITS_2 :
		      FTDI_SIO_SET_DATA_STOP_BITS_1);
	urb_value |= (cflag & PARENB ?
		      (cflag & PARODD ? FTDI_SIO_SET_DATA_PARITY_ODD :
Linus Torvalds's avatar
Linus Torvalds committed
		       FTDI_SIO_SET_DATA_PARITY_EVEN) :
		      FTDI_SIO_SET_DATA_PARITY_NONE);
	if (cflag & CSIZE) {
		switch (cflag & CSIZE) {
		case CS5: urb_value |= 5; dbg("Setting CS5"); break;
		case CS6: urb_value |= 6; dbg("Setting CS6"); break;
		case CS7: urb_value |= 7; dbg("Setting CS7"); break;
		case CS8: urb_value |= 8; dbg("Setting CS8"); break;
		default:
			err("CSIZE was set but not CS5-CS8");
		}
	}

	/* This is needed by the break command since it uses the same command - but is
	 *  or'ed with this value  */
	priv->last_set_data_urb_value = urb_value;
Linus Torvalds's avatar
Linus Torvalds committed
	if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			    FTDI_SIO_SET_DATA_REQUEST,
Linus Torvalds's avatar
Linus Torvalds committed
			    FTDI_SIO_SET_DATA_REQUEST_TYPE,
			    urb_value , priv->interface,
			    buf, 0, WDR_SHORT_TIMEOUT) < 0) {
		err("%s FAILED to set databits/stopbits/parity", __func__);
Linus Torvalds's avatar
Linus Torvalds committed

	/* Now do the baudrate */
	if ((cflag & CBAUD) == B0 ) {
		/* Disable flow control */
		if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
				    FTDI_SIO_SET_FLOW_CTRL_REQUEST,
Linus Torvalds's avatar
Linus Torvalds committed
				    FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
				    0, priv->interface,
Linus Torvalds's avatar
Linus Torvalds committed
				    buf, 0, WDR_TIMEOUT) < 0) {
			err("%s error from disable flowcontrol urb", __func__);
Linus Torvalds's avatar
Linus Torvalds committed
		/* Drop RTS and DTR */
		clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
Linus Torvalds's avatar
Linus Torvalds committed
	} else {
		/* set the baudrate determined before */
		if (change_speed(port)) {
			err("%s urb failed to set baudrate", __func__);
		}
		/* Ensure RTS and DTR are raised when baudrate changed from 0 */
		if (!old_termios || (old_termios->c_cflag & CBAUD) == B0) {
Linus Torvalds's avatar
Linus Torvalds committed
		}
	}

	/* Set flow control */
	/* Note device also supports DTR/CD (ugh) and Xon/Xoff in hardware */
	if (cflag & CRTSCTS) {
		dbg("%s Setting to CRTSCTS flow control", __func__);
		if (usb_control_msg(dev,
Linus Torvalds's avatar
Linus Torvalds committed
				    usb_sndctrlpipe(dev, 0),
				    FTDI_SIO_SET_FLOW_CTRL_REQUEST,
Linus Torvalds's avatar
Linus Torvalds committed
				    FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
				    0 , (FTDI_SIO_RTS_CTS_HS | priv->interface),
				    buf, 0, WDR_TIMEOUT) < 0) {
			err("urb failed to set to rts/cts flow control");
Linus Torvalds's avatar
Linus Torvalds committed
		/*
		 * Xon/Xoff code
		 *
		 * Check the IXOFF status in the iflag component of the termios structure
		 * if IXOFF is not set, the pre-xon/xoff code is executed.
		*/
		if (iflag & IXOFF) {
			dbg("%s  request to enable xonxoff iflag=%04x",__func__,iflag);
Linus Torvalds's avatar
Linus Torvalds committed
			// Try to enable the XON/XOFF on the ftdi_sio
			// Set the vstart and vstop -- could have been done up above where
			// a lot of other dereferencing is done but that would be very
			// inefficient as vstart and vstop are not always needed
			vstart = termios->c_cc[VSTART];
			vstop = termios->c_cc[VSTOP];
Linus Torvalds's avatar
Linus Torvalds committed
			urb_value=(vstop << 8) | (vstart);

			if (usb_control_msg(dev,
					    usb_sndctrlpipe(dev, 0),
					    FTDI_SIO_SET_FLOW_CTRL_REQUEST,
					    FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
					    urb_value , (FTDI_SIO_XON_XOFF_HS
							 | priv->interface),
					    buf, 0, WDR_TIMEOUT) < 0) {
				err("urb failed to set to xon/xoff flow control");
			}
		} else {
			/* else clause to only run if cfag ! CRTSCTS and iflag ! XOFF */
			/* CHECKME Assuming XON/XOFF handled by tty stack - not by device */
			dbg("%s Turning off hardware flow control", __func__);
			if (usb_control_msg(dev,
Linus Torvalds's avatar
Linus Torvalds committed
					    usb_sndctrlpipe(dev, 0),
					    FTDI_SIO_SET_FLOW_CTRL_REQUEST,
Linus Torvalds's avatar
Linus Torvalds committed
					    FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
					    0, priv->interface,
Linus Torvalds's avatar
Linus Torvalds committed
					    buf, 0, WDR_TIMEOUT) < 0) {
				err("urb failed to clear flow control");
Linus Torvalds's avatar
Linus Torvalds committed
		}
Linus Torvalds's avatar
Linus Torvalds committed
	}
	return;
} /* ftdi_termios */


static int ftdi_tiocmget (struct usb_serial_port *port, struct file *file)
{
	struct ftdi_private *priv = usb_get_serial_port_data(port);
	unsigned char buf[2];
	int ret;

	dbg("%s TIOCMGET", __func__);
Linus Torvalds's avatar
Linus Torvalds committed
	switch (priv->chip_type) {
	case SIO:
		/* Request the status from the device */
		if ((ret = usb_control_msg(port->serial->dev,
Linus Torvalds's avatar
Linus Torvalds committed
					   usb_rcvctrlpipe(port->serial->dev, 0),
					   FTDI_SIO_GET_MODEM_STATUS_REQUEST,
Linus Torvalds's avatar
Linus Torvalds committed
					   FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE,
Linus Torvalds's avatar
Linus Torvalds committed
					   buf, 1, WDR_TIMEOUT)) < 0 ) {
			err("%s Could not get modem status of device - err: %d", __func__,
Linus Torvalds's avatar
Linus Torvalds committed
			    ret);
			return(ret);
		}
		break;
	case FT8U232AM:
	case FT232BM:
	case FT2232C:
Linus Torvalds's avatar
Linus Torvalds committed
		/* the 8U232AM returns a two byte value (the sio is a 1 byte value) - in the same
		   format as the data returned from the in point */
		if ((ret = usb_control_msg(port->serial->dev,
Linus Torvalds's avatar
Linus Torvalds committed
					   usb_rcvctrlpipe(port->serial->dev, 0),
					   FTDI_SIO_GET_MODEM_STATUS_REQUEST,
Linus Torvalds's avatar
Linus Torvalds committed
					   FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE,
					   0, priv->interface,
Linus Torvalds's avatar
Linus Torvalds committed
					   buf, 2, WDR_TIMEOUT)) < 0 ) {
			err("%s Could not get modem status of device - err: %d", __func__,
Linus Torvalds's avatar
Linus Torvalds committed
			    ret);
			return(ret);
		}
		break;
	default:
		return -EFAULT;
		break;
	}
Linus Torvalds's avatar
Linus Torvalds committed
	return  (buf[0] & FTDI_SIO_DSR_MASK ? TIOCM_DSR : 0) |
		(buf[0] & FTDI_SIO_CTS_MASK ? TIOCM_CTS : 0) |
		(buf[0]  & FTDI_SIO_RI_MASK  ? TIOCM_RI  : 0) |
		(buf[0]  & FTDI_SIO_RLSD_MASK ? TIOCM_CD  : 0) |
		priv->last_dtr_rts;
Linus Torvalds's avatar
Linus Torvalds committed
}

static int ftdi_tiocmset(struct usb_serial_port *port, struct file * file, unsigned int set, unsigned int clear)
{
	dbg("%s TIOCMSET", __func__);
	return update_mctrl(port, set, clear);
Linus Torvalds's avatar
Linus Torvalds committed
}


static int ftdi_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg)
{
	struct ftdi_private *priv = usb_get_serial_port_data(port);

	dbg("%s cmd 0x%04x", __func__, cmd);
Linus Torvalds's avatar
Linus Torvalds committed

	/* Based on code from acm.c and others */
	switch (cmd) {

	case TIOCGSERIAL: /* gets serial port data */
		return get_serial_info(port, (struct serial_struct __user *) arg);

	case TIOCSSERIAL: /* sets serial port data */
		return set_serial_info(port, (struct serial_struct __user *) arg);

	/*
	 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
	 * - mask passed in arg for lines of interest
	 *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
	 * Caller should use TIOCGICOUNT to see which one it was.
	 *
	 * This code is borrowed from linux/drivers/char/serial.c
	 */
	case TIOCMIWAIT:
		while (priv != NULL) {
			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.
				 */
			}
		}
		return(0);
		break;
	default:
		break;
	/* This is not necessarily an error - turns out the higher layers will do
Linus Torvalds's avatar
Linus Torvalds committed
	 *  some ioctls itself (see comment above)
	 */
	dbg("%s arg not supported - it was 0x%04x - check /usr/include/asm/ioctls.h", __func__, cmd);
Linus Torvalds's avatar
Linus Torvalds committed

	return(-ENOIOCTLCMD);
} /* ftdi_ioctl */


static void ftdi_throttle (struct usb_serial_port *port)
{
	struct ftdi_private *priv = usb_get_serial_port_data(port);
	unsigned long flags;

	dbg("%s - port %d", __func__, port->number);
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock_irqsave(&priv->rx_lock, flags);
	priv->rx_flags |= THROTTLED;
	spin_unlock_irqrestore(&priv->rx_lock, flags);
}


static void ftdi_unthrottle (struct usb_serial_port *port)
{
	struct ftdi_private *priv = usb_get_serial_port_data(port);
	int actually_throttled;
	unsigned long flags;

	dbg("%s - port %d", __func__, port->number);
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock_irqsave(&priv->rx_lock, flags);
	actually_throttled = priv->rx_flags & ACTUALLY_THROTTLED;
	priv->rx_flags &= ~(THROTTLED | ACTUALLY_THROTTLED);
	spin_unlock_irqrestore(&priv->rx_lock, flags);

	if (actually_throttled)
		schedule_delayed_work(&priv->rx_work, 0);
Linus Torvalds's avatar
Linus Torvalds committed
}

static int __init ftdi_init (void)
{
	int retval;

	dbg("%s", __func__);
	if (vendor > 0 && product > 0) {
		/* Add user specified VID/PID to reserved element of table. */
		int i;
		for (i = 0; id_table_combined[i].idVendor; i++)
			;
		id_table_combined[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
		id_table_combined[i].idVendor = vendor;
		id_table_combined[i].idProduct = product;
	}
	retval = usb_serial_register(&ftdi_sio_device);
Linus Torvalds's avatar
Linus Torvalds committed
	if (retval)
		goto failed_sio_register;
Linus Torvalds's avatar
Linus Torvalds committed
	retval = usb_register(&ftdi_driver);
	if (retval)
Linus Torvalds's avatar
Linus Torvalds committed
		goto failed_usb_register;

	info(DRIVER_VERSION ":" DRIVER_DESC);
	return 0;
failed_usb_register:
	usb_serial_deregister(&ftdi_sio_device);
failed_sio_register:
Linus Torvalds's avatar
Linus Torvalds committed
	return retval;
}


static void __exit ftdi_exit (void)
{

	dbg("%s", __func__);
Linus Torvalds's avatar
Linus Torvalds committed

	usb_deregister (&ftdi_driver);
	usb_serial_deregister (&ftdi_sio_device);
Linus Torvalds's avatar
Linus Torvalds committed

}


module_init(ftdi_init);
module_exit(ftdi_exit);

MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug enabled or not");
module_param(vendor, ushort, 0);
MODULE_PARM_DESC(vendor, "User specified vendor ID (default="
		__MODULE_STRING(FTDI_VID)")");
module_param(product, ushort, 0);
MODULE_PARM_DESC(vendor, "User specified product ID");
Linus Torvalds's avatar
Linus Torvalds committed