Skip to content
ftdi_sio.c 81.5 KiB
Newer Older
			priv->rx_flags |= ACTUALLY_THROTTLED;
			spin_unlock_irqrestore(&priv->rx_lock, flags);
			dbg("%s - deferring remainder until unthrottled",
			return;
		}
		spin_unlock_irqrestore(&priv->rx_lock, flags);
		/* if the port is closed stop trying to read */
Alan Cox's avatar
Alan Cox committed
		if (port->port.count > 0)
			/* delay processing of remainder */
			schedule_delayed_work(&priv->rx_work, 1);
Alan Cox's avatar
Alan Cox committed
		else
			dbg("%s - port is closed", __func__);
		return;
	}

	/* urb is completely processed */
	priv->rx_processed = 0;

Linus Torvalds's avatar
Linus Torvalds committed
	/* if the port is closed stop trying to read */
Alan Cox's avatar
Alan Cox committed
	if (port->port.count > 0) {
Linus Torvalds's avatar
Linus Torvalds committed
		/* Continue trying to always read  */
		usb_fill_bulk_urb(port->read_urb, port->serial->dev,
Alan Cox's avatar
Alan Cox committed
			usb_rcvbulkpipe(port->serial->dev,
					port->bulk_in_endpointAddress),
			port->read_urb->transfer_buffer,
			port->read_urb->transfer_buffer_length,
			ftdi_read_bulk_callback, port);
Linus Torvalds's avatar
Linus Torvalds committed

		result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
		if (result)
Alan Cox's avatar
Alan Cox committed
			err("%s - failed resubmitting read urb, error %d",
							__func__, result);
Linus Torvalds's avatar
Linus Torvalds committed
	}
} /* ftdi_process_read */


Alan Cox's avatar
Alan Cox committed
static void ftdi_break_ctl(struct tty_struct *tty, int break_state)
Linus Torvalds's avatar
Linus Torvalds committed
{
Alan Cox's avatar
Alan Cox committed
	struct usb_serial_port *port = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
	struct ftdi_private *priv = usb_get_serial_port_data(port);
	__u16 urb_value = 0;
Linus Torvalds's avatar
Linus Torvalds committed
	char buf[1];
Linus Torvalds's avatar
Linus Torvalds committed
	/* break_state = -1 to turn on break, and 0 to turn off break */
	/* see drivers/char/tty_io.c to see it used */
	/* last_set_data_urb_value NEVER has the break bit set in it */

Alan Cox's avatar
Alan Cox committed
	if (break_state)
Linus Torvalds's avatar
Linus Torvalds committed
		urb_value = priv->last_set_data_urb_value | FTDI_SIO_SET_BREAK;
Alan Cox's avatar
Alan Cox committed
	else
		urb_value = priv->last_set_data_urb_value;

Alan Cox's avatar
Alan Cox committed
	if (usb_control_msg(port->serial->dev,
			usb_sndctrlpipe(port->serial->dev, 0),
			FTDI_SIO_SET_DATA_REQUEST,
			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

Alan Cox's avatar
Alan Cox 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
 */

Alan Cox's avatar
Alan Cox committed
static void ftdi_set_termios(struct tty_struct *tty,
		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);
Alan Cox's avatar
Alan Cox committed
	struct ktermios *termios = 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? */
Alan Cox's avatar
Alan Cox 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

Alan Cox's avatar
Alan Cox 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__);
Alan Cox's avatar
Alan Cox committed
		tty_encode_baud_rate(tty, 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
Alan Cox's avatar
Alan Cox committed
	   ftdi_sio_read_bulk_callback  - need to examine what this 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");
		}
	}

Alan Cox's avatar
Alan Cox committed
	/* This is needed by the break command since it uses the same command
	   - but is or'ed with this value  */
Linus Torvalds's avatar
Linus Torvalds committed
	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 */
Alan Cox's avatar
Alan Cox committed
	if ((cflag & CBAUD) == B0) {
Linus Torvalds's avatar
Linus Torvalds committed
		/* 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 */
Alan Cox's avatar
Alan Cox committed
		if (change_speed(tty, port))
			err("%s urb failed to set baudrate", __func__);
		/* Ensure RTS and DTR are raised when baudrate changed from 0 */
Alan Cox's avatar
Alan Cox committed
		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
		 *
Alan Cox's avatar
Alan Cox committed
		 * Check the IXOFF status in the iflag component of the
		 * termios structure. If IXOFF is not set, the pre-xon/xoff
		 * code is executed.
		 */
Linus Torvalds's avatar
Linus Torvalds committed
		if (iflag & IXOFF) {
Alan Cox's avatar
Alan Cox committed
			dbg("%s  request to enable xonxoff iflag=%04x",
							__func__, iflag);
			/* 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];
Alan Cox's avatar
Alan Cox committed
			urb_value = (vstop << 8) | (vstart);
Linus Torvalds's avatar
Linus Torvalds committed

			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 {
Alan Cox's avatar
Alan Cox committed
			/* else clause to only run if cflag ! 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;
Alan Cox's avatar
Alan Cox committed
}
Linus Torvalds's avatar
Linus Torvalds committed

Alan Cox's avatar
Alan Cox committed
static int ftdi_tiocmget(struct tty_struct *tty, struct file *file)
Linus Torvalds's avatar
Linus Torvalds committed
{
Alan Cox's avatar
Alan Cox committed
	struct usb_serial_port *port = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
	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 */
Alan Cox's avatar
Alan Cox committed
		ret = usb_control_msg(port->serial->dev,
			   usb_rcvctrlpipe(port->serial->dev, 0),
			   FTDI_SIO_GET_MODEM_STATUS_REQUEST,
			   FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE,
			   0, 0,
			   buf, 1, WDR_TIMEOUT);
		if (ret < 0) {
			err("%s Could not get modem status of device - err: %d", __func__,
Linus Torvalds's avatar
Linus Torvalds committed
			    ret);
Alan Cox's avatar
Alan Cox committed
			return ret;
Linus Torvalds's avatar
Linus Torvalds committed
		}
		break;
	case FT8U232AM:
	case FT232BM:
	case FT2232C:
Alan Cox's avatar
Alan Cox 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 */
		ret = usb_control_msg(port->serial->dev,
				   usb_rcvctrlpipe(port->serial->dev, 0),
				   FTDI_SIO_GET_MODEM_STATUS_REQUEST,
				   FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE,
				   0, priv->interface,
				   buf, 2, WDR_TIMEOUT);
		if (ret < 0) {
			err("%s Could not get modem status of device - err: %d", __func__,
Linus Torvalds's avatar
Linus Torvalds committed
			    ret);
Alan Cox's avatar
Alan Cox committed
			return ret;
Linus Torvalds's avatar
Linus Torvalds committed
		}
		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;
Alan Cox's avatar
Alan Cox committed
static int ftdi_tiocmset(struct tty_struct *tty, struct file *file,
Alan Cox's avatar
Alan Cox committed
			unsigned int set, unsigned int clear)
Linus Torvalds's avatar
Linus Torvalds committed
{
Alan Cox's avatar
Alan Cox committed
	struct usb_serial_port *port = tty->driver_data;
	dbg("%s TIOCMSET", __func__);
	return update_mctrl(port, set, clear);
Alan Cox's avatar
Alan Cox committed
static int ftdi_ioctl(struct tty_struct *tty, struct file *file,
					unsigned int cmd, unsigned long arg)
Linus Torvalds's avatar
Linus Torvalds committed
{
Alan Cox's avatar
Alan Cox committed
	struct usb_serial_port *port = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
	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 */
Alan Cox's avatar
Alan Cox committed
		return get_serial_info(port,
					(struct serial_struct __user *) arg);
Linus Torvalds's avatar
Linus Torvalds committed

	case TIOCSSERIAL: /* sets serial port data */
Alan Cox's avatar
Alan Cox committed
		return set_serial_info(tty, port,
					(struct serial_struct __user *) arg);
Linus Torvalds's avatar
Linus Torvalds committed

	/*
	 * 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;

Alan Cox's avatar
Alan Cox committed
				if (diff == 0)
Linus Torvalds's avatar
Linus Torvalds committed
					return -EIO; /* no change => error */

				/* Consume all events */
				priv->diff_status = 0;

Alan Cox's avatar
Alan Cox committed
				/* 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))) {
Linus Torvalds's avatar
Linus Torvalds committed
					return 0;
				}
				/*
Alan Cox's avatar
Alan Cox committed
				 * Otherwise caller can't care less about what
				 * happened,and so we continue to wait for more
				 * events.
Alan Cox's avatar
Alan Cox committed
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed
	default:
		break;
	}
Alan Cox's avatar
Alan Cox committed
	/* This is not necessarily an error - turns out the higher layers
	 * will do some ioctls themselves (see comment above)
Linus Torvalds's avatar
Linus Torvalds committed
	 */
	dbg("%s arg not supported - it was 0x%04x - check /usr/include/asm/ioctls.h", __func__, cmd);
Alan Cox's avatar
Alan Cox committed
	return -ENOIOCTLCMD;
}
Linus Torvalds's avatar
Linus Torvalds committed

Alan Cox's avatar
Alan Cox committed
static void ftdi_throttle(struct tty_struct *tty)
Linus Torvalds's avatar
Linus Torvalds committed
{
Alan Cox's avatar
Alan Cox committed
	struct usb_serial_port *port = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
	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);
}


Alan Cox's avatar
Alan Cox committed
static void ftdi_unthrottle(struct tty_struct *tty)
Linus Torvalds's avatar
Linus Torvalds committed
{
Alan Cox's avatar
Alan Cox committed
	struct usb_serial_port *port = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
	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);
Alan Cox's avatar
Alan Cox committed
static int __init ftdi_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	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;
}


Alan Cox's avatar
Alan Cox committed
static void __exit ftdi_exit(void)
	dbg("%s", __func__);
Linus Torvalds's avatar
Linus Torvalds committed

Alan Cox's avatar
Alan Cox 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);

Alan Cox's avatar
Alan Cox committed
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
Linus Torvalds's avatar
Linus Torvalds committed
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