Skip to content
ftdi_sio.c 72.7 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
					    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", __FUNCTION__);
			if (usb_control_msg(dev, 
					    usb_sndctrlpipe(dev, 0),
					    FTDI_SIO_SET_FLOW_CTRL_REQUEST, 
					    FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
					    0, priv->interface, 
					    buf, 0, WDR_TIMEOUT) < 0) {
				err("urb failed to clear flow control");
			}				
		}
		
	}
	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", __FUNCTION__);
	switch (priv->chip_type) {
	case SIO:
		/* Request the status from the device */
		if ((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)) < 0 ) {
			err("%s Could not get modem status of device - err: %d", __FUNCTION__,
			    ret);
			return(ret);
		}
		break;
	case FT8U232AM:
	case FT232BM:
	case FT2232C:
		/* 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, 
					   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)) < 0 ) {
			err("%s Could not get modem status of device - err: %d", __FUNCTION__,
			    ret);
			return(ret);
		}
		break;
	default:
		return -EFAULT;
		break;
	}
	
	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;			
}

static int ftdi_tiocmset(struct usb_serial_port *port, struct file * file, unsigned int set, unsigned int clear)
{
	dbg("%s TIOCMSET", __FUNCTION__);
	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", __FUNCTION__, cmd);

	/* 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 
	 *  some ioctls itself (see comment above)
	 */
	dbg("%s arg not supported - it was 0x%04x - check /usr/include/asm/ioctls.h", __FUNCTION__, cmd);

	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", __FUNCTION__, port->number);

	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", __FUNCTION__, port->number);

	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_work(&priv->rx_work);
Linus Torvalds's avatar
Linus Torvalds committed
}

static int __init ftdi_init (void)
{
	int retval;

	dbg("%s", __FUNCTION__);
	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) 
		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", __FUNCTION__);

	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