www.pudn.com > usbold11.rar > usb-serial.c


/*
 * USB Serial Converter driver
 *
 *	(C) Copyright (C) 1999, 2000
 *	    Greg Kroah-Hartman (greg@kroah.com)
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 * This driver was originally based on the ACM driver by Armin Fuerst (which was 
 * based on a driver by Brad Keryan)
 *
 * See Documentation/usb-serial.txt for more information on using this driver.
 * 
 * (01/13/2000) gkh
 *	Fixed the vendor id for the generic driver to the one I meant it to be.
 *
 * (01/12/2000) gkh
 *	Forget the version numbering...that's pretty useless...
 *	Made the driver able to be compiled so that the user can select which
 *	converter they want to use. This allows people who only want the Visor
 *	support to not pay the memory size price of the WhiteHEAT.
 *	Fixed bug where the generic driver (idVendor=0000 and idProduct=0000)
 *	grabbed the root hub. Not good.
 * 
 * version 0.4.0 (01/10/2000) gkh
 *	Added whiteheat.h containing the firmware for the ConnectTech WhiteHEAT
 *	device. Added startup function to allow firmware to be downloaded to
 *	a device if it needs to be.
 *	Added firmware download logic to the WhiteHEAT device.
 *	Started to add #defines to split up the different drivers for potential
 *	configuration option.
 *	
 * version 0.3.1 (12/30/99) gkh
 *      Fixed problems with urb for bulk out.
 *      Added initial support for multiple sets of endpoints. This enables
 *      the Handspring Visor to be attached successfully. Only the first
 *      bulk in / bulk out endpoint pair is being used right now.
 *
 * version 0.3.0 (12/27/99) gkh
 *	Added initial support for the Handspring Visor based on a patch from
 *	Miles Lott (milos@sneety.insync.net)
 *	Cleaned up the code a bunch and converted over to using urbs only.
 *
 * version 0.2.3 (12/21/99) gkh
 *	Added initial support for the Connect Tech WhiteHEAT converter.
 *	Incremented the number of ports in expectation of getting the
 *	WhiteHEAT to work properly (4 ports per connection).
 *	Added notification on insertion and removal of what port the
 *	device is/was connected to (and what kind of device it was).
 *
 * version 0.2.2 (12/16/99) gkh
 *	Changed major number to the new allocated number. We're legal now!
 *
 * version 0.2.1 (12/14/99) gkh
 *	Fixed bug that happens when device node is opened when there isn't a
 *	device attached to it. Thanks to marek@webdesign.no for noticing this.
 *
 * version 0.2.0 (11/10/99) gkh
 *	Split up internals to make it easier to add different types of serial 
 *	converters to the code.
 *	Added a "generic" driver that gets it's vendor and product id
 *	from when the module is loaded. Thanks to David E. Nelson (dnelson@jump.net)
 *	for the idea and sample code (from the usb scanner driver.)
 *	Cleared up any licensing questions by releasing it under the GNU GPL.
 *
 * version 0.1.2 (10/25/99) gkh
 * 	Fixed bug in detecting device.
 *
 * version 0.1.1 (10/05/99) gkh
 * 	Changed the major number to not conflict with anything else.
 *
 * version 0.1 (09/28/99) gkh
 * 	Can recognize the two different devices and start up a read from
 *	device when asked to. Writes also work. No control signals yet, this
 *	all is vendor specific data (i.e. no spec), also no control for
 *	different baud rates or other bit settings.
 *	Currently we are using the same devid as the acm driver. This needs
 *	to change.
 * 
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef CONFIG_USB_SERIAL_WHITEHEAT
#include "whiteheat.h"		/* firmware for the ConnectTech WhiteHEAT device */
#endif

#define DEBUG

#include "usb.h"

/* Module information */
MODULE_AUTHOR("Greg Kroah-Hartman, greg@kroah.com, http://www.kroah.com/linux-usb/");
MODULE_DESCRIPTION("USB Serial Driver");

#ifdef CONFIG_USB_SERIAL_GENERIC
static __u16	vendor	= 0x05f9;
static __u16	product	= 0xffff;
MODULE_PARM(vendor, "i");
MODULE_PARM_DESC(vendor, "User specified USB idVendor");

MODULE_PARM(product, "i");
MODULE_PARM_DESC(product, "User specified USB idProduct");
#endif

/* USB Serial devices vendor ids and device ids that this driver supports */
#define BELKIN_VENDOR_ID		0x056c
#define BELKIN_SERIAL_CONVERTER		0x8007
#define PERACOM_VENDOR_ID		0x0565
#define PERACOM_SERIAL_CONVERTER	0x0001
#define CONNECT_TECH_VENDOR_ID		0x0710
#define CONNECT_TECH_FAKE_WHITE_HEAT_ID	0x0001
#define CONNECT_TECH_WHITE_HEAT_ID	0x8001
#define HANDSPRING_VENDOR_ID		0x082d
#define HANDSPRING_VISOR_ID		0x0100


#define SERIAL_MAJOR	188	/* Nice legal number now */
#define NUM_PORTS	16	/* Actually we are allowed 255, but this is good for now */


static void * usb_serial_probe(struct usb_device *dev, unsigned int ifnum);
static void usb_serial_disconnect(struct usb_device *dev, void *ptr);


#define MAX_ENDPOINTS	8

struct usb_serial_state {
	struct usb_device *		dev;
	struct usb_serial_device_type *	type;
	void *				irq_handle;
	unsigned int			irqpipe;
	struct tty_struct *		tty;		/* the coresponding tty for this device */
	unsigned char			number;
	char				present;
	char				active;

	char			num_interrupt_in;	/* number of interrupt in endpoints we have */
	char			interrupt_in_inuse;	/* if the interrupt in endpoint is in use */
	__u8			interrupt_in_endpoint[MAX_ENDPOINTS];
	__u8			interrupt_in_interval[MAX_ENDPOINTS];
	__u16			interrupt_in_size[MAX_ENDPOINTS];	/* the size of the interrupt in endpoint */
	unsigned int		interrupt_in_pipe[MAX_ENDPOINTS];
	unsigned char *		interrupt_in_buffer[MAX_ENDPOINTS];
	void *			interrupt_in_transfer[MAX_ENDPOINTS];
	struct urb		control_urb;

	char			num_bulk_in;		/* number of bulk in endpoints we have */
	__u8			bulk_in_endpoint[MAX_ENDPOINTS];
	__u8			bulk_in_interval[MAX_ENDPOINTS];
	__u16			bulk_in_size[MAX_ENDPOINTS];		/* the size of the bulk in endpoint */
	unsigned int		bulk_in_pipe[MAX_ENDPOINTS];
	unsigned char *		bulk_in_buffer[MAX_ENDPOINTS];
	void *			bulk_in_transfer[MAX_ENDPOINTS];
	struct urb		read_urb;

	char			num_bulk_out;		/* number of bulk out endpoints we have */
	__u8			bulk_out_endpoint[MAX_ENDPOINTS];
	__u8			bulk_out_interval[MAX_ENDPOINTS];
	__u16			bulk_out_size[MAX_ENDPOINTS];		/* the size of the bulk out endpoint */
	unsigned int		bulk_out_pipe[MAX_ENDPOINTS];
	unsigned char *		bulk_out_buffer[MAX_ENDPOINTS];
	void *			bulk_out_transfer[MAX_ENDPOINTS];
	struct urb		write_urb;
};


#define MUST_HAVE_NOT	0x01
#define MUST_HAVE	0x02
#define DONT_CARE	0x03

#define	HAS		0x02
#define HAS_NOT		0x01

#define NUM_DONT_CARE	(-1)

/* local function prototypes */
static int serial_open (struct tty_struct *tty, struct file * filp);
static void serial_close (struct tty_struct *tty, struct file * filp);
static int serial_write (struct tty_struct * tty, int from_user, const unsigned char *buf, int count);
static void serial_put_char (struct tty_struct *tty, unsigned char ch);
static int serial_write_room (struct tty_struct *tty);
static int serial_chars_in_buffer (struct tty_struct *tty);
static void serial_throttle (struct tty_struct * tty);
static void serial_unthrottle (struct tty_struct * tty);


/* This structure defines the individual serial converter. */
struct usb_serial_device_type {
	char	*name;
	__u16	*idVendor;
	__u16	*idProduct;
	char	needs_interrupt_in;
	char	needs_bulk_in;
	char	needs_bulk_out;
	char	num_interrupt_in;
	char	num_bulk_in;
	char	num_bulk_out;

	/* function call to make before accepting driver */
	int (*startup) (struct usb_serial_state *serial);	/* return 0 to continue initialization, anything else to abort */
	
	/* serial function calls */
	int  (*open)(struct tty_struct * tty, struct file * filp);
	void (*close)(struct tty_struct * tty, struct file * filp);
	int  (*write)(struct tty_struct * tty, int from_user,const unsigned char *buf, int count);
	void (*put_char)(struct tty_struct *tty, unsigned char ch);
	int  (*write_room)(struct tty_struct *tty);
	int  (*chars_in_buffer)(struct tty_struct *tty);
	void (*throttle)(struct tty_struct * tty);
	void (*unthrottle)(struct tty_struct * tty);
};


/* function prototypes for a "generic" type serial converter (no flow control, not all endpoints needed) */
/* need to always compile these in, as some of the other devices use these functions as their own. */
static int  generic_serial_open		(struct tty_struct *tty, struct file *filp);
static void generic_serial_close	(struct tty_struct *tty, struct file *filp);
static int  generic_serial_write	(struct tty_struct *tty, int from_user, const unsigned char *buf, int count);
static void generic_serial_put_char	(struct tty_struct *tty, unsigned char ch);
static int  generic_write_room		(struct tty_struct *tty);
static int  generic_chars_in_buffer	(struct tty_struct *tty);

#ifdef CONFIG_USB_SERIAL_GENERIC
/* All of the device info needed for the Generic Serial Converter */
static struct usb_serial_device_type generic_device = {
	name:			"Generic",
	idVendor:		&vendor,		/* use the user specified vendor id */
	idProduct:		&product,		/* use the user specified product id */
	needs_interrupt_in:	DONT_CARE,		/* don't have to have an interrupt in endpoint */
	needs_bulk_in:		DONT_CARE,		/* don't have to have a bulk in endpoint */
	needs_bulk_out:		DONT_CARE,		/* don't have to have a bulk out endpoint */
	num_interrupt_in:	NUM_DONT_CARE,
	num_bulk_in:		NUM_DONT_CARE,
	num_bulk_out:		NUM_DONT_CARE,
	open:			generic_serial_open,
	close:			generic_serial_close,
	write:			generic_serial_write,
	put_char:		generic_serial_put_char,
	write_room:		generic_write_room,
	chars_in_buffer:	generic_chars_in_buffer,
};
#endif

#if defined(CONFIG_USB_SERIAL_BELKIN) || defined(CONFIG_USB_SERIAL_PERACOM)
/* function prototypes for the eTek type converters (this includes Belkin and Peracom) */
static int  etek_serial_open		(struct tty_struct *tty, struct file *filp);
static void etek_serial_close		(struct tty_struct *tty, struct file *filp);
#endif

#ifdef CONFIG_USB_SERIAL_BELKIN
/* All of the device info needed for the Belkin Serial Converter */
static __u16	belkin_vendor_id	= BELKIN_VENDOR_ID;
static __u16	belkin_product_id	= BELKIN_SERIAL_CONVERTER;
static struct usb_serial_device_type belkin_device = {
	name:			"Belkin",
	idVendor:		&belkin_vendor_id,	/* the Belkin vendor id */
	idProduct:		&belkin_product_id,	/* the Belkin serial converter product id */
	needs_interrupt_in:	MUST_HAVE,		/* this device must have an interrupt in endpoint */
	needs_bulk_in:		MUST_HAVE,		/* this device must have a bulk in endpoint */
	needs_bulk_out:		MUST_HAVE,		/* this device must have a bulk out endpoint */
	num_interrupt_in:	1,
	num_bulk_in:		1,
	num_bulk_out:		1,
	open:			etek_serial_open,
	close:			etek_serial_close,
	write:			generic_serial_write,
	put_char:		generic_serial_put_char,
	write_room:		generic_write_room,
	chars_in_buffer:	generic_chars_in_buffer,
};
#endif


#ifdef CONFIG_USB_SERIAL_PERACOM
/* All of the device info needed for the Peracom Serial Converter */
static __u16	peracom_vendor_id	= PERACOM_VENDOR_ID;
static __u16	peracom_product_id	= PERACOM_SERIAL_CONVERTER;
static struct usb_serial_device_type peracom_device = {
	name:			"Peracom",
	idVendor:		&peracom_vendor_id,	/* the Peracom vendor id */
	idProduct:		&peracom_product_id,	/* the Peracom serial converter product id */
	needs_interrupt_in:	MUST_HAVE,		/* this device must have an interrupt in endpoint */
	needs_bulk_in:		MUST_HAVE,		/* this device must have a bulk in endpoint */
	needs_bulk_out:		MUST_HAVE,		/* this device must have a bulk out endpoint */
	num_interrupt_in:	1,
	num_bulk_in:		1,
	num_bulk_out:		1,
	open:			etek_serial_open,
	close:			etek_serial_close,
	write:			generic_serial_write,
	put_char:		generic_serial_put_char,
	write_room:		generic_write_room,
	chars_in_buffer:	generic_chars_in_buffer,
};
#endif


#ifdef CONFIG_USB_SERIAL_WHITEHEAT
/* function prototypes for the Connect Tech WhiteHEAT serial converter */
static int  whiteheat_serial_open	(struct tty_struct *tty, struct file *filp);
static void whiteheat_serial_close	(struct tty_struct *tty, struct file *filp);
static void whiteheat_throttle		(struct tty_struct *tty);
static void whiteheat_unthrottle	(struct tty_struct *tty);
static int  whiteheat_startup		(struct usb_serial_state *serial);

/* All of the device info needed for the Connect Tech WhiteHEAT */
static __u16	connecttech_vendor_id			= CONNECT_TECH_VENDOR_ID;
static __u16	connecttech_whiteheat_fake_product_id	= CONNECT_TECH_FAKE_WHITE_HEAT_ID;
static __u16	connecttech_whiteheat_product_id	= CONNECT_TECH_WHITE_HEAT_ID;
static struct usb_serial_device_type whiteheat_fake_device = {
	name:			"Connect Tech - WhiteHEAT - (prerenumeration)",
	idVendor:		&connecttech_vendor_id,			/* the Connect Tech vendor id */
	idProduct:		&connecttech_whiteheat_fake_product_id,	/* the White Heat initial product id */
	needs_interrupt_in:	DONT_CARE,				/* don't have to have an interrupt in endpoint */
	needs_bulk_in:		DONT_CARE,				/* don't have to have a bulk in endpoint */
	needs_bulk_out:		DONT_CARE,				/* don't have to have a bulk out endpoint */
	num_interrupt_in:	NUM_DONT_CARE,
	num_bulk_in:		NUM_DONT_CARE,
	num_bulk_out:		NUM_DONT_CARE,
	startup:		whiteheat_startup	
};
static struct usb_serial_device_type whiteheat_device = {
	name:			"Connect Tech - WhiteHEAT",
	idVendor:		&connecttech_vendor_id,			/* the Connect Tech vendor id */
	idProduct:		&connecttech_whiteheat_product_id,	/* the White Heat real product id */
	needs_interrupt_in:	DONT_CARE,				/* don't have to have an interrupt in endpoint */
	needs_bulk_in:		DONT_CARE,				/* don't have to have a bulk in endpoint */
	needs_bulk_out:		DONT_CARE,				/* don't have to have a bulk out endpoint */
	num_interrupt_in:	NUM_DONT_CARE,
	num_bulk_in:		NUM_DONT_CARE,
	num_bulk_out:		NUM_DONT_CARE,
	open:			whiteheat_serial_open,
	close:			whiteheat_serial_close,
	write:			generic_serial_write,
	put_char:		generic_serial_put_char,
	write_room:		generic_write_room,
	chars_in_buffer:	generic_chars_in_buffer,
	throttle:		whiteheat_throttle,
	unthrottle:		whiteheat_unthrottle
};
#endif


#ifdef CONFIG_USB_SERIAL_VISOR
/* function prototypes for a handspring visor */
static int  visor_serial_open		(struct tty_struct *tty, struct file *filp);
static void visor_serial_close		(struct tty_struct *tty, struct file *filp);
static void visor_throttle		(struct tty_struct *tty);
static void visor_unthrottle		(struct tty_struct *tty);

/* All of the device info needed for the Handspring Visor */
static __u16	handspring_vendor_id	= HANDSPRING_VENDOR_ID;
static __u16	handspring_product_id	= HANDSPRING_VISOR_ID;
static struct usb_serial_device_type handspring_device = {
	name:			"Handspring Visor",
	idVendor:		&handspring_vendor_id,	/* the Handspring vendor ID */
	idProduct:		&handspring_product_id,	/* the Handspring Visor product id */
	needs_interrupt_in:	MUST_HAVE_NOT,		/* this device must not have an interrupt in endpoint */
	needs_bulk_in:		MUST_HAVE,		/* this device must have a bulk in endpoint */
	needs_bulk_out:		MUST_HAVE,		/* this device must have a bulk out endpoint */
	num_interrupt_in:	0,
	num_bulk_in:		2,
	num_bulk_out:		2,
	open:			visor_serial_open,
	close:			visor_serial_close,
	write:			generic_serial_write,
	put_char:		generic_serial_put_char,
	write_room:		generic_write_room,
	chars_in_buffer:	generic_chars_in_buffer,
	throttle:		visor_throttle,
	unthrottle:		visor_unthrottle
};
#endif

/* To add support for another serial converter, create a usb_serial_device_type
   structure for that device, and add it to this list, making sure that the last
   entry is NULL. */
static struct usb_serial_device_type *usb_serial_devices[] = {
#ifdef CONFIG_USB_SERIAL_GENERIC
	&generic_device,
#endif
#ifdef CONFIG_USB_SERIAL_WHITEHEAT
	&whiteheat_fake_device,
	&whiteheat_device,
#endif
#ifdef CONFIG_USB_SERIAL_BELKIN
	&belkin_device,
#endif
#ifdef CONFIG_USB_SERIAL_PERACOM
	&peracom_device,
#endif
#ifdef CONFIG_USB_SERIAL_VISOR
	&handspring_device,
#endif
	NULL
};


static struct usb_driver usb_serial_driver = {
	"serial",
	usb_serial_probe,
	usb_serial_disconnect,
	{ NULL, NULL }
};

static int			serial_refcount;
static struct tty_struct *	serial_tty[NUM_PORTS];
static struct termios *		serial_termios[NUM_PORTS];
static struct termios *		serial_termios_locked[NUM_PORTS];
static struct usb_serial_state	serial_state_table[NUM_PORTS];



static void serial_read_bulk (struct urb *urb)
{
	struct usb_serial_state *serial = (struct usb_serial_state *)urb->context;
       	struct tty_struct *tty = serial->tty; 
       	unsigned char *data = urb->transfer_buffer;
	int i;

	dbg("serial_read_irq");

	if (urb->status) {
		dbg("nonzero read bulk status received: %d", urb->status);
		return;
	}

	if (urb->actual_length)
		dbg("%d %s", urb->actual_length, data);

	if (urb->actual_length) {
		for (i = 0; i < urb->actual_length ; ++i) {
			 tty_insert_flip_char(tty, data[i], 0);
	  	}
	  	tty_flip_buffer_push(tty);
	}

	/* Continue trying to always read  */
	if (usb_submit_urb(urb))
		dbg("failed resubmitting read urb");

	return;
}


static void serial_write_bulk (struct urb *urb)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) urb->context; 
       	struct tty_struct *tty = serial->tty; 

	dbg("serial_write_irq");

	if (urb->status) {
		dbg("nonzero write bulk status received: %d", urb->status);
		return;
	}

	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
		(tty->ldisc.write_wakeup)(tty);

	wake_up_interruptible(&tty->write_wait);
	
	return;
}



/*****************************************************************************
 * Driver tty interface functions
 *****************************************************************************/
static int serial_open (struct tty_struct *tty, struct file * filp)
{
	struct usb_serial_state *serial;
	
	dbg("serial_open");

	/* assign a serial object to the tty pointer */
	serial = &serial_state_table [MINOR(tty->device)-tty->driver.minor_start];

	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return (-ENODEV);
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return (-ENODEV);
	}

	/* make the tty driver remember our serial object, and us it */
	tty->driver_data = serial;
	serial->tty = tty;
	 
	/* pass on to the driver specific version of this function */
	if (serial->type->open) {
		return (serial->type->open(tty, filp));
	}
		
	return (0);
}


static void serial_close(struct tty_struct *tty, struct file * filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 
	dbg("serial_close");
	
	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return;
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return;
	}
	if (!serial->present) {
		dbg("no device registered");
		return;
	}
	if (!serial->active) {
		dbg ("device already open");
		return;
	}

	/* pass on to the driver specific version of this function */
	if (serial->type->close) {
		serial->type->close(tty, filp);
	}
}	


static int serial_write (struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 
	
	dbg("serial_write");

	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return (-ENODEV);
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return (-ENODEV);
	}
	if (!serial->present) {
		dbg("device not registered");
		return (-EINVAL);
	}
	if (!serial->active) {
		dbg ("device not opened");
		return (-EINVAL);
	}
	
	/* pass on to the driver specific version of this function */
	if (serial->type->write) {
		return (serial->type->write(tty, from_user, buf, count));
	}

	/* no specific driver, so return that we didn't write anything */
	return (0);
}


static void serial_put_char (struct tty_struct *tty, unsigned char ch)
{
	struct usb_serial_state *serial = (struct usb_serial_state *)tty->driver_data; 
	
	dbg("serial_put_char");
	
	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return;
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return;
	}
	if (!serial->present) {
		dbg("no device registered");
		return;
	}
	if (!serial->active) {
		dbg ("device not open");
		return;
	}

	/* pass on to the driver specific version of this function */
	if (serial->type->put_char) {
		serial->type->put_char(tty, ch);
	}

	return;
}	


static int serial_write_room (struct tty_struct *tty) 
{
	struct usb_serial_state *serial = (struct usb_serial_state *)tty->driver_data; 

	dbg("serial_write_room");
	
	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return (-ENODEV);
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return (-ENODEV);
	}
	if (!serial->present) {
		dbg("no device registered");
		return (-EINVAL);
	}
	if (!serial->active) {
		dbg ("device not open");
		return (-EINVAL);
	}
	
	/* pass on to the driver specific version of this function */
	if (serial->type->write_room) {
		return (serial->type->write_room(tty));
	}

	return (0);
}


static int serial_chars_in_buffer (struct tty_struct *tty) 
{
	struct usb_serial_state *serial = (struct usb_serial_state *)tty->driver_data; 

	dbg("serial_chars_in_buffer");
	
	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return (-ENODEV);
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return (-ENODEV);
	}
	if (!serial->present) {
		dbg("no device registered");
		return (-EINVAL);
	}
	if (!serial->active) {
		dbg ("device not open");
		return (-EINVAL);
	}
	
	/* pass on to the driver specific version of this function */
	if (serial->type->chars_in_buffer) {
		return (serial->type->chars_in_buffer(tty));
	}

	return (0);
}


static void serial_throttle (struct tty_struct * tty)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 

	dbg("serial_throttle");
	
	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return;
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return;
	}
	if (!serial->present) {
		dbg("no device registered");
		return;
	}
	if (!serial->active) {
		dbg ("device not open");
		return;
	}

	/* pass on to the driver specific version of this function */
	if (serial->type->throttle) {
		serial->type->throttle(tty);
	}

	return;
}


static void serial_unthrottle (struct tty_struct * tty)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 

	dbg("serial_unthrottle");
	
	/* do some sanity checking that we really have a device present */
	if (!serial) {
		dbg("serial == NULL!");
		return;
	}
	if (!serial->type) {
		dbg("serial->type == NULL!");
		return;
	}
	if (!serial->present) {
		dbg("no device registered");
		return;
	}
	if (!serial->active) {
		dbg ("device not open");
		return;
	}


	/* pass on to the driver specific version of this function */
	if (serial->type->unthrottle) {
		serial->type->unthrottle(tty);
	}

	return;
}


#if defined(CONFIG_USB_SERIAL_BELKIN) || defined(CONFIG_USB_SERIAL_PERACOM)
/*****************************************************************************
 * eTek specific driver functions
 *****************************************************************************/
static int etek_serial_open (struct tty_struct *tty, struct file *filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 

	dbg("etek_serial_open");

	if (!serial->present) {
		dbg("no device registered");
		return -EINVAL;
	}

	if (serial->active) {
		dbg ("device already open");
		return -EINVAL;
	}
	serial->active = 1;
 
	/*Start reading from the device*/
	if (usb_submit_urb(&serial->read_urb))
		dbg("usb_submit_urb(read bulk) failed");

	/* Need to do device specific setup here (control lines, baud rate, etc.) */
	/* FIXME!!! */

	return (0);
}


static void etek_serial_close(struct tty_struct *tty, struct file * filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 
	dbg("etek_serial_close");
	
	/* Need to change the control lines here */
	/* FIXME */
	
	/* shutdown our bulk reads and writes */
	usb_unlink_urb (&serial->write_urb);
	usb_unlink_urb (&serial->read_urb);
	serial->active = 0;
}
#endif	/* defined(CONFIG_USB_SERIAL_BELKIN) || defined(CONFIG_USB_SERIAL_PERACOM) */



#ifdef CONFIG_USB_SERIAL_WHITEHEAT
/*****************************************************************************
 * Connect Tech's White Heat specific driver functions
 *****************************************************************************/
static int whiteheat_serial_open (struct tty_struct *tty, struct file *filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 

	dbg("whiteheat_serial_open");

	if (!serial->present) {
		dbg("no device registered");
		return -EINVAL;
	}

	if (serial->active) {
		dbg ("device already open");
		return -EINVAL;
	}
	serial->active = 1;
 
	/*Start reading from the device*/
	if (usb_submit_urb(&serial->read_urb))
		dbg("usb_submit_urb(read bulk) failed");

	/* Need to do device specific setup here (control lines, baud rate, etc.) */
	/* FIXME!!! */

	return (0);
}


static void whiteheat_serial_close(struct tty_struct *tty, struct file * filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 
	dbg("whiteheat_serial_close");
	
	/* Need to change the control lines here */
	/* FIXME */
	
	/* shutdown our bulk reads and writes */
	usb_unlink_urb (&serial->write_urb);
	usb_unlink_urb (&serial->read_urb);
	serial->active = 0;
}


static void whiteheat_throttle (struct tty_struct * tty)
{
	dbg("whiteheat_throttle");

	/* Change the control signals */
	/* FIXME!!! */

	return;
}


static void whiteheat_unthrottle (struct tty_struct * tty)
{
	dbg("whiteheat_unthrottle");

	/* Change the control signals */
	/* FIXME!!! */

	return;
}


static int whiteheat_writememory (struct usb_serial_state *serial, int address, unsigned char *data, int length, __u8 bRequest)
{
	int result;
	unsigned char *transfer_buffer =  kmalloc (length, GFP_KERNEL);
	if (!transfer_buffer) {
		err("whiteheat_writememory: kmalloc(%d) failed.\n", length);
		return -ENOMEM;
	}
	memcpy (transfer_buffer, data, length);
	result = usb_control_msg (serial->dev, usb_sndctrlpipe(serial->dev, 0), bRequest, 0x40, address, 0, transfer_buffer, length, 300);
	kfree (transfer_buffer);
	return result;
}

/* EZ-USB Control and Status Register.  Bit 0 controls 8051 reset */
#define CPUCS_REG    0x7F92

static int whiteheat_set_reset (struct usb_serial_state *serial, unsigned char reset_bit)
{
	dbg("whiteheat_set_reset: %d", reset_bit);
	return (whiteheat_writememory (serial, CPUCS_REG, &reset_bit, 1, 0xa0));
}


/* steps to download the firmware to the WhiteHEAT device:
 - hold the reset (by writing to the reset bit of the CPUCS register)
 - download the VEND_AX.HEX file to the chip using VENDOR_REQUEST-ANCHOR_LOAD
 - release the reset (by writing to the CPUCS register)
 - download the WH.HEX file for all addresses greater than 0x1b3f using
   VENDOR_REQUEST-ANCHOR_EXTERNAL_RAM_LOAD
 - hold the reset
 - download the WH.HEX file for all addresses less than 0x1b40 using
   VENDOR_REQUEST_ANCHOR_LOAD
 - release the reset
 - device renumerated itself and comes up as new device id with all
   firmware download completed.
*/
static int  whiteheat_startup (struct usb_serial_state *serial)
{
	int response;
	const struct whiteheat_hex_record *record;
	
	dbg("whiteheat_startup\n");
	
	response = whiteheat_set_reset (serial, 1);

	record = &whiteheat_loader[0];
	while (record->address != 0xffff) {
		response = whiteheat_writememory (serial, record->address, 
				(unsigned char *)record->data, record->data_size, 0xa0);
		if (response < 0) {
			err("whiteheat_writememory failed for loader (%d %04X %p %d)", 
				response, record->address, record->data, record->data_size);
			break;
		}
		++record;
	}

	response = whiteheat_set_reset (serial, 0);

	record = &whiteheat_firmware[0];
	while (record->address < 0x8000) {
		++record;
	}
	while (record->address != 0xffff) {
		response = whiteheat_writememory (serial, record->address, 
				(unsigned char *)record->data, record->data_size, 0xa3);
		if (response < 0) {
			err("whiteheat_writememory failed for first firmware step (%d %04X %p %d)", 
				response, record->address, record->data, record->data_size);
			break;
		}
		++record;
	}
	
	response = whiteheat_set_reset (serial, 1);

	record = &whiteheat_firmware[0];
	while (record->address < 0x8000) {
		response = whiteheat_writememory (serial, record->address, 
				(unsigned char *)record->data, record->data_size, 0xa0);
		if (response < 0) {
			err("whiteheat_writememory failed for first firmware step (%d %04X %p %d)\n", 
				response, record->address, record->data, record->data_size);
			break;
		}
		++record;
	}

	response = whiteheat_set_reset (serial, 0);

	/* we want this device to fail to have a driver assigned to it. */
	return (1);
}
#endif	/* CONFIG_USB_SERIAL_WHITEHEAT */


#ifdef CONFIG_USB_SERIAL_VISOR
/******************************************************************************
 * Handspring Visor specific driver functions
 ******************************************************************************/
static int visor_serial_open (struct tty_struct *tty, struct file *filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data;
	dbg("visor_serial_open");

	if (!serial->present) {
		dbg("no device registered");
		return -EINVAL;
	}

	if (serial->active) {
		dbg ("device already open");
		return -EINVAL;
	}

	serial->active = 1;

	/*Start reading from the device*/
	if (usb_submit_urb(&serial->read_urb))
		dbg("usb_submit_urb(read bulk) failed");

	return (0);
}

static void visor_serial_close(struct tty_struct *tty, struct file * filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data;
	
	dbg("USB: visor_serial_close");
			 
	/* shutdown our bulk reads and writes */
	usb_unlink_urb (&serial->write_urb);
	usb_unlink_urb (&serial->read_urb);
	serial->active = 0;
}


static void visor_throttle (struct tty_struct * tty)
{
/*	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; */

	dbg("visor_throttle");

	/* Change the control signals */
	/* FIXME!!! */

	return;
}

static void visor_unthrottle (struct tty_struct * tty)
{
/*	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; */

	dbg("visor_unthrottle");

	/* Change the control signals */
	/* FIXME!!! */

	return;
}
#endif	/* CONFIG_USB_SERIAL_VISOR*/


/*****************************************************************************
 * generic devices specific driver functions
 *****************************************************************************/
static int generic_serial_open (struct tty_struct *tty, struct file *filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 

	dbg("generic_serial_open");

	if (!serial->present) {
		dbg("no device registered");
		return -EINVAL;
	}

	if (serial->active) {
		dbg ("device already open");
		return -EINVAL;
	}
	serial->active = 1;
 
	/* if we have a bulk interrupt, start reading from it */
	if (serial->num_bulk_in) {
		/*Start reading from the device*/
		if (usb_submit_urb(&serial->read_urb))
			dbg("usb_submit_urb(read bulk) failed");
	}

	return (0);
}


static void generic_serial_close(struct tty_struct *tty, struct file * filp)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 
	dbg("generic_serial_close");
	
	/* shutdown any bulk reads that might be going on */
	if (serial->num_bulk_out) {
		usb_unlink_urb (&serial->write_urb);
	}
	if (serial->num_bulk_in) {
		usb_unlink_urb (&serial->read_urb);
	}

	serial->active = 0;
}


static int generic_serial_write (struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) tty->driver_data; 
	
	dbg("generic_serial_write");

	if (count == 0) {
		dbg("write request of 0 bytes");
		return (0);
	}

	/* only do something if we have a bulk out endpoint */
	if (serial->num_bulk_out) {
		if (serial->write_urb.status == -EINPROGRESS) {
			dbg ("already writing");
			return (0);
		}

		count = (count > serial->bulk_out_size[0]) ? serial->bulk_out_size[0] : count;

		if (from_user) {
			copy_from_user(serial->write_urb.transfer_buffer, buf, count);
		}
		else {
			memcpy (serial->write_urb.transfer_buffer, buf, count);
		}  

		/* send the data out the bulk port */
		serial->write_urb.transfer_buffer_length = count;

		if (usb_submit_urb(&serial->write_urb))
			dbg("usb_submit_urb(write bulk) failed");

		return (count);
	}
	
	/* no bulk out, so return 0 bytes written */
	return (0);
} 


static void generic_serial_put_char (struct tty_struct *tty, unsigned char ch)
{
	struct usb_serial_state *serial = (struct usb_serial_state *)tty->driver_data; 
	
	dbg("generic_serial_put_char");
	
	/* if we have a bulk out endpoint, then shove a character out it */
	if (serial->num_bulk_out) {
		/* send the single character out the bulk port */
		memcpy (serial->write_urb.transfer_buffer, &ch, 1);
		serial->write_urb.transfer_buffer_length = 1;

		if (usb_submit_urb(&serial->write_urb))
			dbg("usb_submit_urb(write bulk) failed");

	}

	return;
}


static int generic_write_room (struct tty_struct *tty) 
{
	struct usb_serial_state *serial = (struct usb_serial_state *)tty->driver_data; 
	int room;

	dbg("generic_write_room");
	
	if (serial->num_bulk_out) {
		if (serial->write_urb.status == -EINPROGRESS)
			room = 0;
		else
			room = serial->bulk_out_size[0];
		dbg("generic_write_room returns %d", room);
		return (room);
	}
	
	return (0);
}


static int generic_chars_in_buffer (struct tty_struct *tty) 
{
	struct usb_serial_state *serial = (struct usb_serial_state *)tty->driver_data; 

	dbg("generic_chars_in_buffer");
	
	if (serial->num_bulk_out) {
		if (serial->write_urb.status == -EINPROGRESS) {
			return (serial->bulk_out_size[0]);
		}
	}

	return (0);
}


static int Get_Free_Serial (void)
{
	int i;
 
	for (i=0; i < NUM_PORTS; ++i) {
		if (!serial_state_table[i].present)
			return (i);
	}
	return (-1);
}


static void * usb_serial_probe(struct usb_device *dev, unsigned int ifnum)
{
	struct usb_serial_state *serial = NULL;
	struct usb_interface_descriptor *interface;
	struct usb_endpoint_descriptor *endpoint;
	struct usb_endpoint_descriptor *interrupt_in_endpoint[MAX_ENDPOINTS];
	struct usb_endpoint_descriptor *bulk_in_endpoint[MAX_ENDPOINTS];
	struct usb_endpoint_descriptor *bulk_out_endpoint[MAX_ENDPOINTS];
	struct usb_serial_device_type *type;
	int device_num;
	int serial_num;
	int i;
	char interrupt_pipe;
	char bulk_in_pipe;
	char bulk_out_pipe;
	int num_interrupt_in = 0;
	int num_bulk_in = 0;
	int num_bulk_out = 0;
	
	/* loop through our list of known serial converters, and see if this device matches */
	device_num = 0;
	while (usb_serial_devices[device_num] != NULL) {
		type = usb_serial_devices[device_num];
		dbg ("Looking at %s Vendor id=%.4x Product id=%.4x", type->name, *(type->idVendor), *(type->idProduct));

		/* look at the device descriptor */
		if ((dev->descriptor.idVendor == *(type->idVendor)) &&
		    (dev->descriptor.idProduct == *(type->idProduct))) {

			dbg("descriptor matches...looking at the endpoints");

			/* descriptor matches, let's try to find the endpoints needed */
			interrupt_pipe = bulk_in_pipe = bulk_out_pipe = HAS_NOT;
			
			/* check out the endpoints */
			interface = &dev->actconfig->interface[ifnum].altsetting[0];
			for (i = 0; i < interface->bNumEndpoints; ++i) {
				endpoint = &interface->endpoint[i];
		
				if ((endpoint->bEndpointAddress & 0x80) &&
				    ((endpoint->bmAttributes & 3) == 0x02)) {
					/* we found a bulk in endpoint */
					dbg("found bulk in");
					bulk_in_pipe = HAS;
					bulk_in_endpoint[num_bulk_in] = endpoint;
					++num_bulk_in;
				}

				if (((endpoint->bEndpointAddress & 0x80) == 0x00) &&
				    ((endpoint->bmAttributes & 3) == 0x02)) {
					/* we found a bulk out endpoint */
					dbg("found bulk out");
					bulk_out_pipe = HAS;
					bulk_out_endpoint[num_bulk_out] = endpoint;
					++num_bulk_out;
				}
		
				if ((endpoint->bEndpointAddress & 0x80) &&
				    ((endpoint->bmAttributes & 3) == 0x03)) {
					/* we found a interrupt in endpoint */
					dbg("found interrupt in");
					interrupt_pipe = HAS;
					interrupt_in_endpoint[num_interrupt_in] = endpoint;
					++num_interrupt_in;
				}

			}
	
			/* verify that we found all of the endpoints that we need */
			if ((interrupt_pipe & type->needs_interrupt_in) &&
			    (bulk_in_pipe & type->needs_bulk_in) &&
			    (bulk_out_pipe & type->needs_bulk_out)) {
				/* found all that we need */
				info("%s converter detected", type->name);

				if (0>(serial_num = Get_Free_Serial())) {
					dbg("Too many devices connected");
					return NULL;
				}
	
				serial = &serial_state_table[serial_num];

			       	memset(serial, 0, sizeof(struct usb_serial_state));
			       	serial->dev = dev;
				serial->type = type;
				serial->number = serial_num;
				serial->num_bulk_in = num_bulk_in;
				serial->num_bulk_out = num_bulk_out;
				serial->num_interrupt_in = num_interrupt_in;

				/* if this device type has a startup function, call it */
				if (type->startup) {
					if (type->startup (serial))
						return NULL;
				}

				/* set up the endpoint information */
				for (i = 0; i < num_bulk_in; ++i) {
					serial->bulk_in_endpoint[i] = bulk_in_endpoint[i]->bEndpointAddress;
					serial->bulk_in_size[i] = bulk_in_endpoint[i]->wMaxPacketSize;
					serial->bulk_in_interval[i] = bulk_in_endpoint[i]->bInterval;
					serial->bulk_in_pipe[i] = usb_rcvbulkpipe (dev, serial->bulk_in_endpoint[i]);
					serial->bulk_in_buffer[i] = kmalloc (serial->bulk_in_size[i], GFP_KERNEL);
					if (!serial->bulk_in_buffer[i]) {
						err("Couldn't allocate bulk_in_buffer");
						goto probe_error;
					}
				}
				if (num_bulk_in)
					FILL_BULK_URB(&serial->read_urb, dev, usb_rcvbulkpipe (dev, serial->bulk_in_endpoint[0]),
							serial->bulk_in_buffer[0], serial->bulk_in_size[0], serial_read_bulk, serial);

				for (i = 0; i < num_bulk_out; ++i) {
					serial->bulk_out_endpoint[i] = bulk_out_endpoint[i]->bEndpointAddress;
					serial->bulk_out_size[i] = bulk_out_endpoint[i]->wMaxPacketSize;
					serial->bulk_out_interval[i] = bulk_out_endpoint[i]->bInterval;
					serial->bulk_out_pipe[i] = usb_rcvbulkpipe (dev, serial->bulk_out_endpoint[i]);
					serial->bulk_out_buffer[i] = kmalloc (serial->bulk_out_size[i], GFP_KERNEL);
					if (!serial->bulk_out_buffer[i]) {
						err("Couldn't allocate bulk_out_buffer");
						goto probe_error;
					}
				}
				if (num_bulk_out)
					FILL_BULK_URB(&serial->write_urb, dev, usb_sndbulkpipe (dev, serial->bulk_in_endpoint[0]),
							serial->bulk_in_buffer[0], serial->bulk_in_size[0], serial_write_bulk, serial);

				for (i = 0; i < num_interrupt_in; ++i) {
					serial->interrupt_in_inuse = 0;
					serial->interrupt_in_endpoint[i] = interrupt_in_endpoint[i]->bEndpointAddress;
					serial->interrupt_in_size[i] = interrupt_in_endpoint[i]->wMaxPacketSize;
					serial->interrupt_in_interval[i] = interrupt_in_endpoint[i]->bInterval;
					/* serial->interrupt_in_pipe = usb_rcvbulkpipe (dev, serial->bulk_in_endpoint); */
					serial->interrupt_in_buffer[i] = kmalloc (serial->bulk_in_size[i], GFP_KERNEL);
					if (!serial->interrupt_in_buffer[i]) {
						err("Couldn't allocate interrupt_in_buffer");
						goto probe_error;
					}
				}

				#if 0
				/* set up an interrupt for out bulk in pipe */
				/* ask for a bulk read */
				serial->bulk_in_inuse = 1;
				serial->bulk_in_transfer = usb_request_bulk (serial->dev, serial->bulk_in_pipe, serial_read_irq, serial->bulk_in_buffer, serial->bulk_in_size, serial);

				/* set up our interrupt to be the time for the bulk in read */
				ret = usb_request_irq (dev, serial->bulk_in_pipe, usb_serial_irq, serial->bulk_in_interval, serial, &serial->irq_handle);
				if (ret) {
					info("failed usb_request_irq (0x%x)", ret);
					goto probe_error;
				}
				#endif

				serial->present = 1;
				MOD_INC_USE_COUNT;

				info("%s converter now attached to ttyUSB%d", type->name, serial_num);
				return serial;
			} else {
				info("descriptors matched, but endpoints did not");
			}
		}

		/* look at the next type in our list */
		++device_num;
	}

probe_error:
	if (serial) {
		for (i = 0; i < num_bulk_in; ++i)
			if (serial->bulk_in_buffer[i])
				kfree (serial->bulk_in_buffer[i]);
		for (i = 0; i < num_bulk_out; ++i)
			if (serial->bulk_out_buffer[i])
				kfree (serial->bulk_out_buffer[i]);
		for (i = 0; i < num_interrupt_in; ++i)
			if (serial->interrupt_in_buffer[i])
				kfree (serial->interrupt_in_buffer[i]);
	}
	return NULL;
}


static void usb_serial_disconnect(struct usb_device *dev, void *ptr)
{
	struct usb_serial_state *serial = (struct usb_serial_state *) ptr;
	int i;

	if (serial) {
		if (!serial->present) {
			/* something strange is going on */
			dbg("disconnect but not present?");
			return;
			}

		/* need to stop any transfers...*/
		usb_unlink_urb (&serial->write_urb);
		usb_unlink_urb (&serial->read_urb);

		/* free up any memory that we allocated */
		for (i = 0; i < serial->num_bulk_in; ++i)
			if (serial->bulk_in_buffer[i])
				kfree (serial->bulk_in_buffer[i]);
		for (i = 0; i < serial->num_bulk_out; ++i)
			if (serial->bulk_out_buffer[i])
				kfree (serial->bulk_out_buffer[i]);
		for (i = 0; i < serial->num_interrupt_in; ++i)
			if (serial->interrupt_in_buffer[i])
				kfree (serial->interrupt_in_buffer[i]);

		serial->present = 0;
		serial->active = 0;

		info("%s converter now disconnected from ttyUSB%d", serial->type->name, serial->number);

	} else {
		info("device disconnected");
	}
	
	MOD_DEC_USE_COUNT;

}


static struct tty_driver serial_tty_driver = {
	magic:			TTY_DRIVER_MAGIC,
	driver_name:		"usb",
	name:			"ttyUSB",
	major:			SERIAL_MAJOR,
	minor_start:		0,
	num:			NUM_PORTS,
	type:			TTY_DRIVER_TYPE_SERIAL,
	subtype:		SERIAL_TYPE_NORMAL,
	flags:			TTY_DRIVER_REAL_RAW,
	refcount:		&serial_refcount,
	table:			serial_tty,
	proc_entry:		NULL,
	other:			NULL,
	termios:		serial_termios,
	termios_locked:		serial_termios_locked,
	
	open:			serial_open,
	close:			serial_close,
	write:			serial_write,
	put_char:		serial_put_char,
	flush_chars:		NULL,
	write_room:		serial_write_room,
	ioctl:			NULL,
	set_termios:		NULL,
	set_ldisc:		NULL, 
	throttle:		serial_throttle,
	unthrottle:		serial_unthrottle,
	stop:			NULL,
	start:			NULL,
	hangup:			NULL,
	break_ctl:		NULL,
	wait_until_sent:	NULL,
	send_xchar:		NULL,
	read_proc:		NULL,
	chars_in_buffer:	serial_chars_in_buffer,
	flush_buffer:		NULL
};


int usb_serial_init(void)
{
	int i;

	/* Initalize our global data */
	for (i = 0; i < NUM_PORTS; ++i) {
		memset(&serial_state_table[i], 0x00, sizeof(struct usb_serial_state));
	}

	/* register the tty driver */
	serial_tty_driver.init_termios		= tty_std_termios;
	serial_tty_driver.init_termios.c_cflag	= B9600 | CS8 | CREAD | HUPCL | CLOCAL;
	if (tty_register_driver (&serial_tty_driver)) {
		err("failed to register tty driver");
		return -EPERM;
	}
	
	/* register the USB driver */
	if (usb_register(&usb_serial_driver) < 0) {
		tty_unregister_driver(&serial_tty_driver);
		return -1;
	}

	info("support registered");
	return 0;
}


#ifdef MODULE
int init_module(void)
{
	return usb_serial_init();
}

void cleanup_module(void)
{
	tty_unregister_driver(&serial_tty_driver);
	usb_deregister(&usb_serial_driver);
}

#endif