patch-2.4.19 linux-2.4.19/arch/mips/au1000/common/usbdev.c

Next file: linux-2.4.19/arch/mips/au1000/pb1000/Makefile
Previous file: linux-2.4.19/arch/mips/au1000/common/time.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.18/arch/mips/au1000/common/usbdev.c linux-2.4.19/arch/mips/au1000/common/usbdev.c
@@ -0,0 +1,1844 @@
+/*
+ * BRIEF MODULE DESCRIPTION
+ *	Au1000 USB Device-Side Serial TTY Driver
+ *
+ * Copyright 2001 MontaVista Software Inc.
+ * Author: MontaVista Software, Inc.
+ *		stevel@mvista.com or source@mvista.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  SOFTWARE  IS PROVIDED	  ``AS	IS'' AND   ANY	EXPRESS OR IMPLIED
+ *  WARRANTIES,	  INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO	EVENT  SHALL   THE AUTHOR  BE	 LIABLE FOR ANY	  DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED	  TO, PROCUREMENT OF  SUBSTITUTE GOODS	OR SERVICES; LOSS OF
+ *  USE, DATA,	OR PROFITS; OR	BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN	 CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/smp_lock.h>
+#define DEBUG
+#include <linux/usb.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/mipsregs.h>
+#include <asm/au1000.h>
+#include <asm/au1000_dma.h>
+
+/* Module information */
+MODULE_AUTHOR("Steve Longerbeam, stevel@mvista.com, www.mvista.com");
+MODULE_DESCRIPTION("Au1000 USB Device-Side Serial TTY Driver");
+
+#undef USBDEV_PIO
+
+#define SERIAL_TTY_MAJOR 189
+
+#define MAX(a,b)	(((a)>(b))?(a):(b))
+
+#define ALLOC_FLAGS (in_interrupt () ? GFP_ATOMIC : GFP_KERNEL)
+
+#define MAX_NUM_PORTS 2
+
+#define NUM_PORTS 1
+#define NUM_EP 2*NUM_PORTS
+
+#define EP0_MAX_PACKET_SIZE 64
+#define EP2_MAX_PACKET_SIZE 64
+#define EP3_MAX_PACKET_SIZE 64
+#define EP4_MAX_PACKET_SIZE 64
+#define EP5_MAX_PACKET_SIZE 64
+
+#ifdef USBDEV_PIO
+#define EP_FIFO_DEPTH 8
+#endif
+
+typedef enum {
+	ATTACHED = 0,
+	POWERED,
+	DEFAULT,
+	ADDRESS,
+	CONFIGURED
+} dev_state_t;
+
+/* 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 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);
+static int serial_ioctl(struct tty_struct *tty, struct file *file,
+			unsigned int cmd, unsigned long arg);
+static void serial_set_termios (struct tty_struct *tty, struct termios * old);
+
+typedef struct {
+	int read_fifo;
+	int write_fifo;
+	int ctrl_stat;
+	int read_fifo_status;
+	int write_fifo_status;
+} endpoint_reg_t;
+
+typedef struct pkt {
+	int size;
+	u8 *bufptr;
+	struct pkt *next;
+	u8 buf[0];
+} pkt_t;
+
+typedef struct {
+	pkt_t *head;
+	pkt_t *tail;
+	int count;
+} pkt_list_t;
+
+typedef struct {
+	struct usb_endpoint_descriptor *desc;
+	endpoint_reg_t *reg;
+	// Only one of these are used, unless this is a control ep
+	pkt_list_t inlist;
+	pkt_list_t outlist;
+	unsigned int indma, outdma;	// DMA channel numbers for IN, OUT
+	int inirq, outirq;	// DMA buffer done irq numbers
+	int max_pkt_size;
+	spinlock_t lock;
+} endpoint_t;
+
+struct usb_serial_port {
+	struct usb_serial *serial;	/* ptr back to the owner of this port */
+	struct tty_struct *tty;	/* the coresponding tty for this port */
+	unsigned char number;
+	char active;		/* someone has this device open */
+	spinlock_t port_lock;
+
+	endpoint_t ep_bulkin;
+	endpoint_t ep_bulkout;
+
+	wait_queue_head_t write_wait;
+
+	/* task queue for line discipline waking up on send packet complete */
+	struct tq_struct send_complete_tq;
+	/* task queue for line discipline wakeup on receive packet complete */
+	struct tq_struct receive_complete_tq;
+
+	int open_count;		/* number of times this port has been opened */
+};
+
+struct usb_serial {
+	struct tty_driver *tty_driver;	/* the tty_driver for this device */
+	unsigned char minor;	/* the minor number for this device */
+
+	endpoint_t ep_ctrl;
+
+	struct usb_device_descriptor *dev_desc;
+	struct usb_interface_descriptor *if_desc;
+	struct usb_config_descriptor *conf_desc;
+	struct usb_string_descriptor *str_desc[6];
+
+	struct usb_serial_port port[NUM_PORTS];
+
+	dev_state_t state;	// device state
+	int suspended;		// suspended flag
+	int address;		// device address
+	int interface;
+	u8 alternate_setting;
+	u8 configuration;	// configuration value
+	int remote_wakeup_en;
+};
+
+
+static struct usb_device_descriptor dev_desc = {
+	bLength:USB_DT_DEVICE_SIZE,
+	bDescriptorType:USB_DT_DEVICE,
+	bcdUSB:0x0110,		//usb rev 1.0
+	bDeviceClass:USB_CLASS_PER_INTERFACE,	//class    (none)
+	bDeviceSubClass:0x00,	//subclass (none)
+	bDeviceProtocol:0x00,	//protocol (none)
+	bMaxPacketSize0:EP0_MAX_PACKET_SIZE,	//max packet size for ep0
+	idVendor:0x6d04,	//vendor  id
+	idProduct:0x0bc0,	//product id
+	bcdDevice:0x0001,	//BCD rev 0.1
+	iManufacturer:0x01,	//manufactuer string index
+	iProduct:0x02,		//product string index
+	iSerialNumber:0x03,	//serial# string index
+	bNumConfigurations:0x01	//num configurations
+};
+
+static struct usb_endpoint_descriptor ep_desc[] = {
+	{
+	 // EP2, Bulk IN for Port 0
+	      bLength:USB_DT_ENDPOINT_SIZE,
+	      bDescriptorType:USB_DT_ENDPOINT,
+	      bEndpointAddress:USB_DIR_IN | 0x02,
+	      bmAttributes:USB_ENDPOINT_XFER_BULK,
+	      wMaxPacketSize:EP2_MAX_PACKET_SIZE,
+	      bInterval:0x00	// ignored for bulk
+	 },
+	{
+	 // EP4, Bulk OUT for Port 0
+	      bLength:USB_DT_ENDPOINT_SIZE,
+	      bDescriptorType:USB_DT_ENDPOINT,
+	      bEndpointAddress:USB_DIR_OUT | 0x04,
+	      bmAttributes:USB_ENDPOINT_XFER_BULK,
+	      wMaxPacketSize:EP4_MAX_PACKET_SIZE,
+	      bInterval:0x00	// ignored for bulk
+	 },
+	{
+	 // EP3, Bulk IN for Port 1
+	      bLength:USB_DT_ENDPOINT_SIZE,
+	      bDescriptorType:USB_DT_ENDPOINT,
+	      bEndpointAddress:USB_DIR_IN | 0x03,
+	      bmAttributes:USB_ENDPOINT_XFER_BULK,
+	      wMaxPacketSize:EP3_MAX_PACKET_SIZE,
+	      bInterval:0x00	// ignored for bulk
+	 },
+	{
+	 // EP5, Bulk OUT for Port 1
+	      bLength:USB_DT_ENDPOINT_SIZE,
+	      bDescriptorType:USB_DT_ENDPOINT,
+	      bEndpointAddress:USB_DIR_OUT | 0x05,
+	      bmAttributes:USB_ENDPOINT_XFER_BULK,
+	      wMaxPacketSize:EP5_MAX_PACKET_SIZE,
+	      bInterval:0x00	// ignored for bulk
+	 },
+};
+
+static struct usb_interface_descriptor if_desc = {
+	bLength:USB_DT_INTERFACE_SIZE,
+	bDescriptorType:USB_DT_INTERFACE,
+	bInterfaceNumber:0x00,
+	bAlternateSetting:0x00,
+	bNumEndpoints:NUM_EP,
+	bInterfaceClass:0xff,
+	bInterfaceSubClass:0xab,
+	bInterfaceProtocol:0x00,
+	iInterface:0x05
+};
+
+#define CONFIG_DESC_LEN \
+ USB_DT_CONFIG_SIZE + USB_DT_INTERFACE_SIZE + NUM_EP*USB_DT_ENDPOINT_SIZE
+
+static struct usb_config_descriptor config_desc = {
+	bLength:USB_DT_CONFIG_SIZE,
+	bDescriptorType:USB_DT_CONFIG,
+	wTotalLength:CONFIG_DESC_LEN,
+	bNumInterfaces:0x01,
+	bConfigurationValue:0x01,
+	iConfiguration:0x04,	// configuration string
+	bmAttributes:0xc0,	// self-powered
+	MaxPower:20		// 40 mA
+};
+
+// These strings will be converted to Unicode before sending
+static char *strings[5] = {
+	"Alchemy Semiconductor",
+	"Alchemy Au1000",
+	"1.0",
+	"Au1000 UART Config",
+	"Au1000 UART Interface"
+};
+
+// String[0] is a list of Language IDs supported by this device
+static struct usb_string_descriptor string_desc0 = {
+	bLength:4,
+	bDescriptorType:USB_DT_STRING,
+	wData:{0x0409}		// English, US
+};
+
+
+static endpoint_reg_t ep_reg[] = {
+	// FIFO's 0 and 1 are EP0 default control
+	{USBD_EP0RD, USBD_EP0WR, USBD_EP0CS, USBD_EP0RDSTAT, USBD_EP0WRSTAT},
+	// FIFO 2 is EP2, Port 0, bulk IN
+	{ -1, USBD_EP2WR, USBD_EP2CS, -1, USBD_EP2WRSTAT },
+	// FIFO 4 is EP4, Port 0, bulk OUT
+	    {USBD_EP4RD, -1, USBD_EP4CS, USBD_EP3WR, -1},
+	// FIFO 3 is EP3, Port 1, bulk IN
+	{ -1, USBD_EP3WRSTAT, USBD_EP3CS, -1, USBD_EP3WRSTAT },
+	// FIFO 5 is EP5, Port 1, bulk OUT
+	    {USBD_EP5RD, -1, USBD_EP5CS, USBD_EP5RDSTAT, -1}
+};
+
+static struct {
+	unsigned int id;
+	const char *str;
+} ep_dma_id[] = {
+	{ DMA_ID_USBDEV_EP0_TX, "USBDev EP0 IN" },
+	{ DMA_ID_USBDEV_EP0_RX, "USBDev EP0 OUT" },
+	{ DMA_ID_USBDEV_EP2_TX, "USBDev EP2 IN" },
+	{ DMA_ID_USBDEV_EP4_RX, "USBDev EP4 OUT" },
+	{ DMA_ID_USBDEV_EP3_TX, "USBDev EP3 IN" },
+	{ DMA_ID_USBDEV_EP5_RX, "USBDev EP5 OUT" }
+};
+
+static int serial_refcount;
+static struct tty_driver serial_tty_driver;
+static struct tty_struct *serial_tty[1];
+static struct termios *serial_termios[1];
+static struct termios *serial_termios_locked[1];
+static struct usb_serial usbserial;
+
+#define DIR_OUT 0
+#define DIR_IN  (1<<3)
+
+static const u32 au1000_config_table[25] __devinitdata = {
+	0x00,
+	    ((EP0_MAX_PACKET_SIZE & 0x380) >> 7) |
+	    (USB_ENDPOINT_XFER_CONTROL << 4),
+	(EP0_MAX_PACKET_SIZE & 0x7f) << 1,
+	0x00,
+	0x01,
+
+	0x10,
+	    ((EP2_MAX_PACKET_SIZE & 0x380) >> 7) | DIR_IN |
+	    (USB_ENDPOINT_XFER_BULK << 4),
+	(EP2_MAX_PACKET_SIZE & 0x7f) << 1,
+	0x00,
+	0x02,
+
+	0x20,
+	    ((EP3_MAX_PACKET_SIZE & 0x380) >> 7) | DIR_IN |
+	    (USB_ENDPOINT_XFER_BULK << 4),
+	(EP3_MAX_PACKET_SIZE & 0x7f) << 1,
+	0x00,
+	0x03,
+
+	0x30,
+	    ((EP4_MAX_PACKET_SIZE & 0x380) >> 7) | DIR_OUT |
+	    (USB_ENDPOINT_XFER_BULK << 4),
+	(EP4_MAX_PACKET_SIZE & 0x7f) << 1,
+	0x00,
+	0x04,
+
+	0x40,
+	    ((EP5_MAX_PACKET_SIZE & 0x380) >> 7) | DIR_OUT |
+	    (USB_ENDPOINT_XFER_BULK << 4),
+	(EP5_MAX_PACKET_SIZE & 0x7f) << 1,
+	0x00,
+	0x05
+};
+
+static inline endpoint_t *
+fifonum_to_ep(struct usb_serial* serial, int fifo_num)
+{
+	switch (fifo_num) {
+	case 0:
+	case 1:
+		return &serial->ep_ctrl;
+	case 2:
+		return &serial->port[0].ep_bulkin;
+	case 3:
+		return &serial->port[1].ep_bulkin;
+	case 4:
+		return &serial->port[0].ep_bulkout;
+	case 5:
+		return &serial->port[1].ep_bulkout;
+	}
+
+	return NULL;
+}
+
+static inline struct usb_serial_port *
+fifonum_to_port(struct usb_serial* serial, int fifo_num)
+{
+	switch (fifo_num) {
+	case 2:
+	case 4:
+		return &serial->port[0];
+	case 3:
+	case 5:
+		return &serial->port[1];
+	}
+
+	return NULL;
+}
+
+static inline endpoint_t *
+epnum_to_ep(struct usb_serial* serial, int ep_num)
+{
+	switch (ep_num) {
+	case 0:
+		return &serial->ep_ctrl;
+	case 2:
+		return &serial->port[0].ep_bulkin;
+	case 3:
+		return &serial->port[1].ep_bulkin;
+	case 4:
+		return &serial->port[0].ep_bulkout;
+	case 5:
+		return &serial->port[1].ep_bulkout;
+	}
+
+	return NULL;
+}
+
+
+static inline int
+port_paranoia_check(struct usb_serial_port *port, const char *function)
+{
+	if (!port) {
+		dbg("%s - port == NULL", function);
+		return -1;
+	}
+	if (!port->serial) {
+		dbg("%s - port->serial == NULL", function);
+		return -1;
+	}
+	if (!port->tty) {
+		dbg("%s - port->tty == NULL", function);
+		return -1;
+	}
+
+	return 0;
+}
+
+static inline struct usb_serial*
+get_usb_serial (struct usb_serial_port *port, const char *function)
+{
+	/* if no port was specified, or it fails a paranoia check */
+	if (!port || port_paranoia_check(port, function)) {
+		/* then say that we dont have a valid usb_serial thing,
+		 * which will end up genrating -ENODEV return values */
+		return NULL;
+	}
+
+	return port->serial;
+}
+
+
+static inline pkt_t *
+alloc_packet(int data_size)
+{
+	pkt_t* pkt = (pkt_t *)kmalloc(sizeof(pkt_t) + data_size, ALLOC_FLAGS);
+	if (!pkt)
+		return NULL;
+	pkt->size = data_size;
+	pkt->bufptr = pkt->buf;
+#ifndef USBDEV_PIO
+	pkt->bufptr = KSEG1ADDR(pkt->bufptr);
+#endif
+	pkt->next = NULL;
+	return pkt;
+}
+
+
+/*
+ * Link a packet to the tail of the enpoint's packet list.
+ */
+static void
+link_packet(endpoint_t * ep, pkt_list_t * list, pkt_t * pkt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	if (!list->tail) {
+		list->head = list->tail = pkt;
+		list->count = 1;
+	} else {
+		list->tail->next = pkt;
+		list->tail = pkt;
+		list->count++;
+	}
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+/*
+ * Unlink and return a packet from the head of the enpoint's packet list.
+ */
+static pkt_t *
+unlink_packet(endpoint_t * ep, pkt_list_t * list)
+{
+	unsigned long flags;
+	pkt_t *pkt;
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	pkt = list->head;
+	if (!pkt || !list->count) {
+		spin_unlock_irqrestore(&ep->lock, flags);
+		return NULL;
+	}
+
+	list->head = pkt->next;
+	if (!list->head) {
+		list->head = list->tail = NULL;
+		list->count = 0;
+	} else
+		list->count--;
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+
+	return pkt;
+}
+
+/*
+ * Create and attach a new packet to the tail of the enpoint's
+ * packet list.
+ */
+static pkt_t *
+add_packet(endpoint_t * ep, pkt_list_t * list, int size)
+{
+	pkt_t *pkt = alloc_packet(size);
+	if (!pkt)
+		return NULL;
+
+	link_packet(ep, list, pkt);
+	return pkt;
+}
+
+
+/*
+ * Unlink and free a packet from the head of the enpoint's
+ * packet list.
+ */
+static inline void
+free_packet(endpoint_t * ep, pkt_list_t * list)
+{
+	kfree(unlink_packet(ep, list));
+}
+
+static inline void
+flush_pkt_list(endpoint_t * ep, pkt_list_t * list)
+{
+	while (list->count)
+		free_packet(ep, list);
+}
+
+
+static inline void
+flush_write_fifo(endpoint_t * ep)
+{
+	if (ep->reg->write_fifo_status >= 0) {
+		outl_sync(USBDEV_FSTAT_FLUSH, ep->reg->write_fifo_status);
+		udelay(100);
+		outl_sync(USBDEV_FSTAT_UF | USBDEV_FSTAT_OF,
+			  ep->reg->write_fifo_status);
+	}
+}
+
+
+static inline void
+flush_read_fifo(endpoint_t * ep)
+{
+	if (ep->reg->read_fifo_status >= 0) {
+		outl_sync(USBDEV_FSTAT_FLUSH, ep->reg->read_fifo_status);
+		udelay(100);
+		outl_sync(USBDEV_FSTAT_UF | USBDEV_FSTAT_OF,
+			  ep->reg->read_fifo_status);
+	}
+}
+
+
+static void
+endpoint_flush(endpoint_t * ep)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	// First, flush all packets
+	flush_pkt_list(ep, &ep->inlist);
+	flush_pkt_list(ep, &ep->outlist);
+
+	// Now flush the endpoint's h/w FIFO(s)
+	flush_write_fifo(ep);
+	flush_read_fifo(ep);
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+
+static void
+endpoint_stall(endpoint_t * ep)
+{
+	unsigned long flags;
+	u32 cs;
+
+	dbg(__FUNCTION__);
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	cs = inl(ep->reg->ctrl_stat) | USBDEV_CS_STALL;
+	outl_sync(cs, ep->reg->ctrl_stat);
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+static void
+endpoint_unstall(endpoint_t * ep)
+{
+	unsigned long flags;
+	u32 cs;
+
+	dbg(__FUNCTION__);
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	cs = inl(ep->reg->ctrl_stat) & ~USBDEV_CS_STALL;
+	outl_sync(cs, ep->reg->ctrl_stat);
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+static void
+endpoint_reset_datatoggle(endpoint_t * ep)
+{
+	// FIXME: is this possible?
+}
+
+
+#ifdef USBDEV_PIO
+static int
+endpoint_fifo_read(endpoint_t * ep)
+{
+	unsigned long flags;
+	int read_count = 0;
+	u8 *bufptr;
+	pkt_t *pkt = ep->outlist.tail;
+
+	if (!pkt)
+		return -EINVAL;
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	bufptr = pkt->bufptr;
+	while (inl(ep->reg->read_fifo_status) & USBDEV_FSTAT_FCNT_MASK) {
+		*bufptr++ = inl(ep->reg->read_fifo) & 0xff;
+		read_count++;
+		pkt->size++;
+	}
+	pkt->bufptr = bufptr;
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+	return read_count;
+}
+
+
+static int
+endpoint_fifo_write(endpoint_t * ep)
+{
+	unsigned long flags;
+	int write_count = 0;
+	u8 *bufptr;
+	pkt_t *pkt = ep->inlist.head;
+
+	if (!pkt)
+		return -EINVAL;
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	bufptr = pkt->bufptr;
+	while ((inl(ep->reg->write_fifo_status) & USBDEV_FSTAT_FCNT_MASK) <
+	       EP_FIFO_DEPTH) {
+		if (bufptr < pkt->buf + pkt->size) {
+			outl_sync(*bufptr++, ep->reg->write_fifo);
+			write_count++;
+		} else {
+			break;
+		}
+	}
+	pkt->bufptr = bufptr;
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+	return write_count;
+}
+#endif				// USBDEV_PIO
+
+/*
+ * This routine is called to restart transmission of a packet.
+ * The endpoint's TSIZE must be set to the new packet's size,
+ * and DMA to the write FIFO needs to be restarted.
+ */
+static void
+kickstart_send_packet(endpoint_t * ep)
+{
+	u32 cs;
+	pkt_t *pkt = ep->inlist.head;
+
+	dbg(__FUNCTION__ ": pkt=%p", pkt);
+
+	if (!pkt)
+		return;
+
+	/*
+	 * The write fifo should already be drained if things are
+	 * working right, but flush it anyway just in case.
+	 */
+	flush_write_fifo(ep);
+	cs = inl(ep->reg->ctrl_stat) & USBDEV_CS_STALL;
+	cs |= (pkt->size << USBDEV_CS_TSIZE_BIT);
+	outl_sync(cs, ep->reg->ctrl_stat);
+#ifdef USBDEV_PIO
+	endpoint_fifo_write(ep);
+#else
+	disable_dma(ep->indma);
+	if (get_dma_active_buffer(ep->indma)) {
+		set_dma_count1(ep->indma, pkt->size);
+		set_dma_addr1(ep->indma, virt_to_phys(pkt->bufptr));
+		enable_dma_buffer1(ep->indma);	// reenable
+	} else {
+		set_dma_count0(ep->indma, pkt->size);
+		set_dma_addr0(ep->indma, virt_to_phys(pkt->bufptr));
+		enable_dma_buffer0(ep->indma);	// reenable
+	}
+	enable_dma(ep->indma);
+#endif
+}
+
+
+/*
+ * This routine is called when a packet in the inlist has been
+ * completed. Frees the completed packet and starts sending the
+ * next.
+ */
+static void
+send_packet_complete(endpoint_t * ep)
+{
+	if (ep->inlist.head)
+		dbg(__FUNCTION__ ": pkt=%p, ab=%d",
+		    ep->inlist.head, get_dma_active_buffer(ep->indma));
+
+	outl_sync(inl(ep->reg->ctrl_stat) & USBDEV_CS_STALL,
+		  ep->reg->ctrl_stat);
+	//disable_dma(ep->indma);
+	free_packet(ep, &ep->inlist);
+	// begin transmitting next packet in the inlist
+	if (ep->inlist.count)
+		kickstart_send_packet(ep);
+}
+
+
+/*
+ * Unlink and return a packet from the head of the given ep's packet
+ * outlist. It is the responsibility of the caller to free the packet.
+ * The receive complete interrupt adds packets to the tail of this list. 
+ */
+static pkt_t *
+receive_packet(endpoint_t * ep)
+{
+	pkt_t *pkt = unlink_packet(ep, &ep->outlist);
+	//dma_cache_inv((unsigned long)pkt->buf, pkt->size);
+	return pkt;
+}
+
+/*
+ * This routine is called to restart reception of a packet.
+ */
+static void
+kickstart_receive_packet(endpoint_t * ep)
+{
+	pkt_t *pkt;
+
+	// get and link a new packet for next reception
+	if (!(pkt = add_packet(ep, &ep->outlist, ep->max_pkt_size))) {
+		err(__FUNCTION__ ": could not alloc new packet");
+		return;
+	}
+
+	/*
+	 * The read fifo should already be drained if things are
+	 * working right, but flush it anyway just in case.
+	 */
+	flush_read_fifo(ep);
+#ifndef USBDEV_PIO
+	if (get_dma_active_buffer(ep->outdma)) {
+		set_dma_count1(ep->outdma, ep->max_pkt_size);
+		set_dma_addr1(ep->outdma, virt_to_phys(pkt->bufptr));
+		enable_dma_buffer1(ep->outdma);	// reenable
+	} else {
+		set_dma_count0(ep->outdma, ep->max_pkt_size);
+		set_dma_addr0(ep->outdma, virt_to_phys(pkt->bufptr));
+		enable_dma_buffer0(ep->outdma);	// reenable
+	}
+	enable_dma(ep->outdma);
+#endif
+}
+
+
+/*
+ * This routine is called when a packet in the outlist has been
+ * completed (received) and we need to prepare for a new packet
+ * to be received. Halts DMA and computes the packet size from the
+ * remaining DMA counter. Then prepares a new packet for reception
+ * and restarts DMA. FIXME: what if another packet comes in
+ * on top of the completed packet? Counter would be wrong.
+ */
+static void
+receive_packet_complete(endpoint_t * ep)
+{
+	pkt_t *pkt = ep->outlist.tail;
+
+	if (!pkt)
+		return;
+
+	disable_dma(ep->outdma);
+	pkt->size = ep->max_pkt_size - get_dma_residue(ep->outdma);
+#ifdef USBDEV_PIO
+	pkt->bufptr = pkt->buf;	// reset bufptr
+#endif
+	dbg(__FUNCTION__ ": size=%d", pkt->size);
+
+	kickstart_receive_packet(ep);
+}
+
+
+
+/*
+ * Add a new packet to the tail of the given ep's packet
+ * inlist. The transmit complete interrupt frees packets from
+ * the head of this list.
+ */
+static int
+send_packet(endpoint_t * ep, u8 * data, int data_len, int from_user)
+{
+	unsigned long flags;
+	pkt_list_t *list = &ep->inlist;
+	pkt_t *pkt;
+
+	if (!data || !data_len)
+		return 0;
+
+	if (!(pkt = alloc_packet(data_len))) {
+		err(__FUNCTION__ ": could not alloc new packet");
+		return -ENOMEM;
+	}
+
+	if (from_user)
+		copy_from_user(pkt->bufptr, data, data_len);
+	else
+		memcpy(pkt->bufptr, data, data_len);
+	au_sync();
+
+	//dma_cache_wback_inv((unsigned long)pkt->buf, data_len);
+
+	link_packet(ep, list, pkt);
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	dbg(__FUNCTION__ ": size=%d, list count=%d", pkt->size, list->count);
+
+	if (list->count == 1) {
+		/*
+		 * if the packet count is one, it means the list was empty,
+		 * and no more data will go out this ep until we kick-start
+		 * it again.
+		 */
+		kickstart_send_packet(ep);
+	}
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+	return data_len;
+}
+
+
+// SETUP packet request parser
+static void
+process_setup (struct usb_serial* serial, devrequest* setup)
+{
+	int desc_len, strnum;
+
+	dbg(__FUNCTION__ ": req %d", setup->request);
+
+	switch (setup->request) {
+	case USB_REQ_SET_ADDRESS:
+		serial->address = le16_to_cpu(setup->value);
+		dbg(__FUNCTION__ ": our address=%d", serial->address);
+		if (serial->address > 127 || serial->state == CONFIGURED) {
+			// usb spec doesn't tell us what to do, so just go to
+			// default state
+			serial->state = DEFAULT;
+			serial->address = 0;
+		} else if (serial->address)
+			serial->state = ADDRESS;
+		else
+			serial->state = DEFAULT;
+		break;
+	case USB_REQ_GET_DESCRIPTOR:
+		desc_len = le16_to_cpu(setup->length);
+		switch (le16_to_cpu(setup->value) >> 8) {
+		case USB_DT_DEVICE:
+			// send device descriptor!
+			desc_len = desc_len > serial->dev_desc->bLength ?
+			    serial->dev_desc->bLength : desc_len;
+			dbg("sending device desc, size=%d", desc_len);
+			send_packet(&serial->ep_ctrl, (u8*)serial->dev_desc,
+				    desc_len, 0);
+			break;
+		case USB_DT_CONFIG:
+			// If the config descr index in low-byte of
+			// setup->value	is valid, send config descr,
+			// otherwise stall ep0.
+			if ((le16_to_cpu(setup->value) & 0xff) == 0) {
+				// send config descriptor!
+				if (desc_len <= USB_DT_CONFIG_SIZE) {
+					dbg("sending partial config desc, size=%d",
+					     desc_len);
+					send_packet(&serial->ep_ctrl,
+						    (u8*)serial->conf_desc,
+						    desc_len, 0);
+				} else {
+					u8 full_conf_desc[CONFIG_DESC_LEN];
+					int i, index = 0;
+					memcpy(&full_conf_desc[index],
+					       serial->conf_desc,
+					       USB_DT_CONFIG_SIZE);
+					index += USB_DT_CONFIG_SIZE;
+					memcpy(&full_conf_desc[index],
+					       serial->if_desc,
+					       USB_DT_INTERFACE_SIZE);
+					index += USB_DT_INTERFACE_SIZE;
+					for (i = 0; i < NUM_PORTS; i++) {
+						memcpy(&full_conf_desc[index],
+						       serial->port[i].ep_bulkin.desc,
+						       USB_DT_ENDPOINT_SIZE);
+						index += USB_DT_ENDPOINT_SIZE;
+						memcpy(&full_conf_desc[index],
+						       serial->port[i].ep_bulkout.desc,
+						       USB_DT_ENDPOINT_SIZE);
+						index += USB_DT_ENDPOINT_SIZE;
+					}
+					dbg("sending whole config desc, size=%d, our size=%d",
+					     desc_len, CONFIG_DESC_LEN);
+					desc_len = desc_len > CONFIG_DESC_LEN ?
+					    CONFIG_DESC_LEN : desc_len;
+					send_packet(&serial->ep_ctrl,
+						    full_conf_desc, desc_len, 0);
+				}
+			} else
+				endpoint_stall(&serial->ep_ctrl);
+			break;
+		case USB_DT_STRING:
+			// If the string descr index in low-byte of setup->value
+			// is valid, send string descr, otherwise stall ep0.
+			strnum = le16_to_cpu(setup->value) & 0xff;
+			if (strnum >= 0 && strnum < 6) {
+				struct usb_string_descriptor *desc =
+				    serial->str_desc[strnum];
+				desc_len = desc_len > desc->bLength ?
+					desc->bLength : desc_len;
+				dbg("sending string desc %d", strnum);
+				send_packet(&serial->ep_ctrl, (u8 *) desc,
+					    desc_len, 0);
+			} else
+				endpoint_stall(&serial->ep_ctrl);
+			break;
+		default:	// Invalid request
+			dbg("invalid get desc=%d, stalled",
+			    le16_to_cpu(setup->value) >> 8);
+			endpoint_stall(&serial->ep_ctrl);	// Stall endpoint 0
+			break;
+		}
+		break;
+	case USB_REQ_SET_DESCRIPTOR:
+		// FIXME: anything to set here?
+		break;
+	case USB_REQ_GET_INTERFACE:
+		// interface must be zero.
+		if ((le16_to_cpu(setup->index) & 0xff) ||
+		    serial->state == ADDRESS) {
+			// FIXME: respond with "request error". how?
+		} else if (serial->state == CONFIGURED) {
+			// send serial->alternate_setting
+			dbg("sending alt setting");
+			send_packet(&serial->ep_ctrl,
+				    &serial->alternate_setting, 1, 0);
+		}
+		break;
+	case USB_REQ_SET_INTERFACE:
+		if (serial->state == ADDRESS) {
+			// FIXME: respond with "request error". how?
+		} else if (serial->state == CONFIGURED) {
+			serial->interface = le16_to_cpu(setup->index) & 0xff;
+			serial->alternate_setting =
+			    le16_to_cpu(setup->value) & 0xff;
+			// interface and alternate_setting must be zero
+			if (serial->interface || serial->alternate_setting) {
+				// FIXME: respond with "request error". how?
+			}
+		}
+		break;
+	case USB_REQ_SET_CONFIGURATION:
+		// set active config to low-byte of serial.value
+		serial->configuration = le16_to_cpu(setup->value) & 0xff;
+		dbg("set config, config=%d", serial->configuration);
+		if (!serial->configuration && serial->state > DEFAULT)
+			serial->state = ADDRESS;
+		else if (serial->configuration == 1)
+			serial->state = CONFIGURED;
+		else {
+			// FIXME: "respond with request error" - how?
+		}
+		break;
+	case USB_REQ_GET_CONFIGURATION:
+		// send serial->configuration
+		dbg("sending config");
+		send_packet(&serial->ep_ctrl, &serial->configuration, 1, 0);
+		break;
+	case USB_REQ_GET_STATUS:
+		// FIXME: looks like the h/w handles this one
+		switch (setup->requesttype) {
+		case 0x80:	// Device
+			// FIXME: send device status
+			break;
+		case 0x81:	// Interface
+			// FIXME: send interface status
+			break;
+		case 0x82:	// End Point
+			// FIXME: send endpoint status
+			break;
+		default:	// Invalid Command
+			endpoint_stall(&serial->ep_ctrl);	// Stall End Point 0
+			break;
+		}
+		break;
+	case USB_REQ_CLEAR_FEATURE:
+		switch (setup->requesttype) {
+		case 0x00:	// Device
+			if ((le16_to_cpu(setup->value) & 0xff) == 1)
+				serial->remote_wakeup_en = 0;
+			else
+				endpoint_stall(&serial->ep_ctrl);
+			break;
+		case 0x02:	// End Point
+			if ((le16_to_cpu(setup->value) & 0xff) == 0) {
+				endpoint_t *ep =
+				    epnum_to_ep(serial,
+						    le16_to_cpu(setup->index) & 0xff);
+
+				endpoint_unstall(ep);
+				endpoint_reset_datatoggle(ep);
+			} else
+				endpoint_stall(&serial->ep_ctrl);
+			break;
+		}
+		break;
+	case USB_REQ_SET_FEATURE:
+		switch (setup->requesttype) {
+		case 0x00:	// Device
+			if ((le16_to_cpu(setup->value) & 0xff) == 1)
+				serial->remote_wakeup_en = 1;
+			else
+				endpoint_stall(&serial->ep_ctrl);
+			break;
+		case 0x02:	// End Point
+			if ((le16_to_cpu(setup->value) & 0xff) == 0) {
+				endpoint_t *ep =
+				    epnum_to_ep(serial,
+						    le16_to_cpu(setup->index) & 0xff);
+
+				endpoint_stall(ep);
+			} else
+				endpoint_stall(&serial->ep_ctrl);
+			break;
+		}
+		break;
+	default:
+		endpoint_stall(&serial->ep_ctrl);	// Stall End Point 0
+		break;
+	}
+}
+
+
+/*
+ * A complete packet (SETUP, DATA0, or DATA1) has been received
+ * on the given endpoint's fifo.
+ */
+static void
+process_complete (struct usb_serial* serial, int fifo_num)
+{
+	endpoint_t *ep = fifonum_to_ep(serial, fifo_num);
+	struct usb_serial_port *port = NULL;
+	pkt_t *pkt = 0;
+	u32 cs;
+
+	cs = inl(ep->reg->ctrl_stat);
+
+	switch (fifo_num) {
+	case 0:
+		spin_lock(&ep->lock);
+		// complete packet and prepare a new packet
+		receive_packet_complete(ep);
+
+		// Get it immediately from endpoint.
+		if (!(pkt = receive_packet(ep))) {
+			spin_unlock(&ep->lock);
+			return;
+		}
+	
+		// SETUP packet received ?
+		//if (cs & USBDEV_CS_SU) { FIXME: uncomment!
+			if (pkt->size == sizeof(devrequest)) {
+				devrequest setup;
+			if ((cs & (USBDEV_CS_NAK | USBDEV_CS_ACK)) ==
+			    USBDEV_CS_ACK)
+					dbg("got SETUP");
+				else
+					dbg("got NAK SETUP, cs=%08x", cs);
+			memcpy(&setup, pkt->bufptr, sizeof(devrequest));
+				process_setup(serial, &setup);
+			//} else  FIXME: uncomment!
+			//dbg(__FUNCTION__ ": wrong size SETUP received");
+		} else {
+			// DATAx packet received on endpoint 0
+			// FIXME: will need a state machine for control
+			// OUT transactions
+			dbg("got DATAx on EP0, size=%d, cs=%08x",
+			    pkt->size, cs);
+		}
+
+		spin_unlock(&ep->lock);
+		// we're done processing the packet, free it
+		kfree(pkt);
+		break;
+
+	case 4:
+	case 5:
+		port = fifonum_to_port(serial, fifo_num);
+		dbg("got DATAx on port %d, cs=%08x", port->number, cs);
+		spin_lock(&ep->lock);
+		receive_packet_complete(ep);
+		spin_unlock(&ep->lock);
+		// mark a bh to push this data up to the tty
+		queue_task(&port->receive_complete_tq, &tq_immediate);
+		mark_bh(IMMEDIATE_BH);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+
+// This ISR needs to ack both the complete and suspend events
+static void
+req_sus_intr (int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct usb_serial *serial = (struct usb_serial *) dev_id;
+	int i;
+	u32 status;
+
+	status = inl(USB_DEV_INT_STATUS);
+	outl_sync(status, USB_DEV_INT_STATUS);	// ack'em
+
+#ifdef USBDEV_PIO
+	for (i = 0; i < 6; i++) {
+		if (status & (1 << (USBDEV_INT_HF_BIT + i))) {
+			endpoint_t *ep = fifonum_to_ep(serial, i);
+
+			if (ep->desc->bEndpointAddress & USB_DIR_IN)
+				endpoint_fifo_write(ep);
+			else
+				endpoint_fifo_read(ep);
+		}
+	}
+#endif
+
+	for (i = 0; i < 6; i++) {
+		if (status & (1 << i))
+			process_complete(serial, i);
+	}
+}
+
+
+static void
+dma_done_ctrl(struct usb_serial* serial)
+{
+	endpoint_t *ep = &serial->ep_ctrl;
+	u32 cs0, buff_done;
+
+	spin_lock(&ep->lock);
+	cs0 = inl(ep->reg->ctrl_stat);
+
+	// first check packet transmit done
+	if ((buff_done = get_dma_buffer_done(ep->indma)) != 0) {
+		// transmitted a DATAx packet on control endpoint 0
+		// clear DMA done bit
+		if (buff_done == DMA_D0)
+			clear_dma_done0(ep->indma);
+		else
+			clear_dma_done1(ep->indma);
+
+		send_packet_complete(ep);
+	}
+
+	/*
+	 * Now check packet receive done. Shouldn't get these,
+	 * the receive packet complete intr should happen
+	 * before the DMA done intr occurs.
+	 */
+	if ((buff_done = get_dma_buffer_done(ep->outdma)) != 0) {
+		// clear DMA done bit
+		if (buff_done == DMA_D0)
+			clear_dma_done0(ep->outdma);
+		else
+			clear_dma_done1(ep->outdma);
+	}
+
+	spin_unlock(&ep->lock);
+}
+
+static void
+dma_done_port(struct usb_serial_port * port)
+{
+	endpoint_t *ep;
+	u32 buff_done;
+
+	// first check packet transmit done (bulk IN ep)
+	ep = &port->ep_bulkin;
+	spin_lock(&ep->lock);
+	if ((buff_done = get_dma_buffer_done(ep->indma)) != 0) {
+		// transmitted a DATAx packet on the port's bulk IN endpoint
+		// clear DMA done bit
+		if (buff_done == DMA_D0)
+			clear_dma_done0(ep->indma);
+		else
+			clear_dma_done1(ep->indma);
+
+		send_packet_complete(ep);
+		// mark a bh to wakeup any tty write system call on the port.
+		queue_task(&port->send_complete_tq, &tq_immediate);
+		mark_bh(IMMEDIATE_BH);
+	}
+	spin_unlock(&ep->lock);
+
+	/*
+	 * Now check packet receive done (bulk OUT ep). Shouldn't
+	 * get these, the receive packet complete intr should happen
+	 * before the DMA done intr occurs.
+	 */
+	ep = &port->ep_bulkout;
+	spin_lock(&ep->lock);
+	if ((buff_done = get_dma_buffer_done(ep->outdma)) != 0) {
+		// received a DATAx packet on the port's bulk OUT endpoint
+		// clear DMA done bit
+		if (buff_done == DMA_D0)
+			clear_dma_done0(ep->outdma);
+		else
+			clear_dma_done1(ep->outdma);
+	}
+	spin_unlock(&ep->lock);
+}
+
+
+// This ISR needs to handle dma done events for ALL endpoints!
+static void
+dma_done_intr (int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct usb_serial *serial = (struct usb_serial *) dev_id;
+	int i;
+
+	dma_done_ctrl(serial);
+	for (i = 0; i < NUM_PORTS; i++)
+		dma_done_port(&serial->port[i]);
+}
+
+
+
+/*****************************************************************************
+ * Here begins the tty driver interface functions
+ *****************************************************************************/
+
+static int serial_open(struct tty_struct *tty, struct file *filp)
+{
+	int portNumber;
+	struct usb_serial_port *port;
+	struct usb_serial *serial = &usbserial;
+	unsigned long flags;
+
+	/* initialize the pointer incase something fails */
+	tty->driver_data = NULL;
+
+	MOD_INC_USE_COUNT;
+
+	/* set up our port structure making the tty driver remember
+	   our port object, and us it */
+	portNumber = MINOR(tty->device) - serial->minor;
+	port = &serial->port[portNumber];
+	tty->driver_data = port;
+	port->tty = tty;
+
+	if (port_paranoia_check(port, __FUNCTION__))
+		return -ENODEV;
+
+	dbg(__FUNCTION__ " - port %d", port->number);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	++port->open_count;
+
+	if (!port->active) {
+		port->active = 1;
+
+		/*
+		 * force low_latency on so that our tty_push actually forces
+		 * the data through, otherwise it is scheduled, and with high
+		 * data rates (like with OHCI) data can get lost.
+		 */
+		port->tty->low_latency = 1;
+
+	}
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return 0;
+}
+
+
+static void serial_close(struct tty_struct *tty, struct file *filp)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+	unsigned long flags;
+
+	if (!serial)
+		return;
+
+	dbg(__FUNCTION__ " - port %d", port->number);
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not opened");
+		return;
+	}
+
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	--port->open_count;
+
+	if (port->open_count <= 0) {
+		port->active = 0;
+		port->open_count = 0;
+	}
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+	MOD_DEC_USE_COUNT;
+}
+
+
+static int serial_write(struct tty_struct *tty, int from_user,
+			const unsigned char *buf, int count)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+	endpoint_t *ep = &port->ep_bulkin;
+
+	if (!serial)
+		return -ENODEV;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not opened");
+		return -EINVAL;
+	}
+
+	if (count == 0) {
+		dbg(__FUNCTION__ " - write request of 0 bytes");
+		return (0);
+	}
+
+	count = (count > ep->max_pkt_size) ? ep->max_pkt_size : count;
+	send_packet(ep, (u8 *) buf, count, from_user);
+
+	return (count);
+}
+
+
+static int serial_write_room(struct tty_struct *tty)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+	endpoint_t *ep = &port->ep_bulkin;
+
+	if (!serial)
+		return -ENODEV;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not open");
+		return -EINVAL;
+	}
+
+	return ep->max_pkt_size;
+}
+
+
+static int serial_chars_in_buffer(struct tty_struct *tty)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+	endpoint_t *ep = &port->ep_bulkin;
+	pkt_list_t *list = &ep->inlist;
+	pkt_t *scan;
+	unsigned long flags;
+	int chars = 0;
+
+	if (!serial)
+		return -ENODEV;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not open");
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&ep->lock, flags);
+	for (scan = list->head; scan; scan = scan->next)
+		chars += scan->size;
+	spin_unlock_irqrestore(&ep->lock, flags);
+
+	return (chars);
+}
+
+
+static void serial_throttle(struct tty_struct *tty)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+
+	if (!serial)
+		return;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not open");
+		return;
+	}
+	// FIXME: anything to do?
+}
+
+
+static void serial_unthrottle(struct tty_struct *tty)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+
+	if (!serial)
+		return;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not open");
+		return;
+	}
+	// FIXME: anything to do?
+}
+
+
+static int serial_ioctl(struct tty_struct *tty, struct file *file,
+			unsigned int cmd, unsigned long arg)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+
+	if (!serial)
+		return -ENODEV;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not open");
+		return -ENODEV;
+	}
+	// FIXME: need any IOCTLs?
+
+	return -ENOIOCTLCMD;
+}
+
+
+static void serial_set_termios(struct tty_struct *tty, struct termios *old)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+
+	if (!serial)
+		return;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not open");
+		return;
+	}
+	// FIXME: anything to do?
+}
+
+
+static void serial_break(struct tty_struct *tty, int break_state)
+{
+	struct usb_serial_port *port =
+	    (struct usb_serial_port *) tty->driver_data;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+
+	if (!serial)
+		return;
+
+	if (!port->active) {
+		dbg(__FUNCTION__ " - port not open");
+		return;
+	}
+	// FIXME: anything to do?
+}
+
+
+static void port_send_complete(void *private)
+{
+	struct usb_serial_port *port = (struct usb_serial_port *) private;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+	struct tty_struct *tty;
+
+	dbg(__FUNCTION__ " - port %d", port->number);
+
+	if (!serial) {
+		return;
+	}
+
+	tty = port->tty;
+	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+	    tty->ldisc.write_wakeup) {
+		dbg(__FUNCTION__ " - write wakeup call.");
+		(tty->ldisc.write_wakeup) (tty);
+	}
+
+	wake_up_interruptible(&tty->write_wait);
+}
+
+
+static void port_receive_complete(void *private)
+{
+	struct usb_serial_port *port = (struct usb_serial_port *) private;
+	struct usb_serial *serial = get_usb_serial(port, __FUNCTION__);
+	struct tty_struct *tty = port->tty;
+	pkt_t *pkt;
+	int i;
+
+	dbg(__FUNCTION__ " - port %d", port->number);
+
+	if (!serial) {
+		return;
+	}
+
+	if (!(pkt = receive_packet(&port->ep_bulkout)))
+		return;
+
+	for (i = 0; i < pkt->size; i++) {
+		/* if we insert more than TTY_FLIPBUF_SIZE characters,
+		   we drop them. */
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+			tty_flip_buffer_push(tty);
+		}
+		/* this doesn't actually push the data through
+		   unless tty->low_latency is set */
+		tty_insert_flip_char(tty, pkt->bufptr[i], 0);
+	}
+	tty_flip_buffer_push(tty);
+
+	// we're done processing the packet, free it
+	kfree(pkt);
+}
+
+
+static struct tty_driver serial_tty_driver = {
+	magic:TTY_DRIVER_MAGIC,
+	driver_name:"usbdev-serial",
+	name:"usb/ttsdev/%d",
+	major:SERIAL_TTY_MAJOR,
+	minor_start:0,
+	num:1,
+	type:TTY_DRIVER_TYPE_SERIAL,
+	subtype:SERIAL_TYPE_NORMAL,
+	flags:TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
+	refcount:&serial_refcount,
+	table:serial_tty,
+	termios:serial_termios,
+	termios_locked:serial_termios_locked,
+
+	open:serial_open,
+	close:serial_close,
+	write:serial_write,
+	write_room:serial_write_room,
+	ioctl:serial_ioctl,
+	set_termios:serial_set_termios,
+	throttle:serial_throttle,
+	unthrottle:serial_unthrottle,
+	break_ctl:serial_break,
+	chars_in_buffer:serial_chars_in_buffer,
+};
+
+
+void usbdev_serial_exit(void)
+{
+	endpoint_t *ep;
+	int i;
+
+	outl_sync(0, USB_DEV_INT_ENABLE);	// disable usb dev ints
+	outl_sync(0, USB_DEV_ENABLE);	// disable usb dev
+
+	// first free all control endpoint resources
+	ep = &usbserial.ep_ctrl;
+	free_irq(AU1000_USB_DEV_REQ_INT, &usbserial);
+	free_irq(AU1000_USB_DEV_SUS_INT, &usbserial);
+	free_irq(ep->inirq, &usbserial);
+	//free_irq(ep->outirq, &usbserial);
+	free_au1000_dma(ep->indma);
+	free_au1000_dma(ep->outdma);
+	endpoint_flush(ep);
+
+	// now free all port resources
+	for (i = 0; i < NUM_PORTS; i++) {
+		// free port's bulk IN endpoint resources
+		ep = &usbserial.port[i].ep_bulkin;
+		free_irq(ep->inirq, &usbserial);
+		free_au1000_dma(ep->indma);
+		endpoint_flush(ep);
+
+		// free port's bulk OUT endpoint resources
+		ep = &usbserial.port[i].ep_bulkout;
+		//free_irq(ep->outirq, &usbserial);
+		free_au1000_dma(ep->outdma);
+		endpoint_flush(ep);
+
+		tty_unregister_devfs(&serial_tty_driver, i);
+		info("usbdev serial converter now disconnected from ttyUSBdev%d",
+		     i);
+	}
+
+	kfree(usbserial.str_desc[0]);
+	tty_unregister_driver(&serial_tty_driver);
+}
+
+int usbdev_serial_init(void)
+{
+	struct usb_serial_port *port;
+	endpoint_t *ep;
+	void *str_desc_buf;
+	int str_desc_len;
+	int i;
+
+	/* 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(__FUNCTION__ ": failed to register tty driver");
+		return -1;
+	}
+
+	memset(&usbserial, 0, sizeof(struct usb_serial));
+	usbserial.minor = 0;
+
+	usbserial.state = DEFAULT;
+
+	usbserial.dev_desc = &dev_desc;
+	usbserial.if_desc = &if_desc;
+	usbserial.conf_desc = &config_desc;
+
+	/*
+	 * initialize the string descriptors
+	 */
+
+	/* alloc buffer big enough for all string descriptors */
+	str_desc_len = string_desc0.bLength;
+	for (i = 0; i < 5; i++)
+		str_desc_len += 2 + 2 * strlen(strings[i]);
+	str_desc_buf = (void *) kmalloc(str_desc_len, ALLOC_FLAGS);
+	if (!str_desc_buf) {
+		err(__FUNCTION__ ": failed to alloc string descriptors");
+		return -1;
+	}
+
+	usbserial.str_desc[0] = (struct usb_string_descriptor *)str_desc_buf;
+	memcpy(usbserial.str_desc[0], &string_desc0, string_desc0.bLength);
+	usbserial.str_desc[1] = (struct usb_string_descriptor *)
+	    (str_desc_buf + string_desc0.bLength);
+	for (i = 1; i < 6; i++) {
+		struct usb_string_descriptor *desc = usbserial.str_desc[i];
+		char *str = strings[i - 1];
+		int j, str_len = strlen(str);
+
+		desc->bLength = 2 + 2 * str_len;
+		desc->bDescriptorType = USB_DT_STRING;
+		for (j = 0; j < str_len; j++) {
+			desc->wData[j] = (u16) str[j];
+		}
+		if (i < 5)
+			usbserial.str_desc[i + 1] =
+			    (struct usb_string_descriptor *)
+			    ((u8 *) desc + desc->bLength);
+	}
+
+	// request the USB device transfer complete interrupt
+	if (request_irq(AU1000_USB_DEV_REQ_INT, req_sus_intr, SA_SHIRQ,
+			"USBdev req", &usbserial)) {
+		err("Can't get device request intr\n");
+		goto err_out;
+	}
+	// request the USB device suspend interrupt
+	if (request_irq(AU1000_USB_DEV_SUS_INT, req_sus_intr, SA_SHIRQ,
+			"USBdev sus", &usbserial)) {
+		err("Can't get device suspend intr\n");
+		goto err_out;
+	}
+
+	// Initialize default control endpoint
+	ep = &usbserial.ep_ctrl;
+	spin_lock_init(&ep->lock);
+	ep->desc = NULL;	// ep0 has no ep descriptor
+	ep->reg = &ep_reg[0];
+	ep->max_pkt_size = usbserial.dev_desc->bMaxPacketSize0;
+	ep->indma = ep->outdma = -1;
+	if ((ep->indma = request_au1000_dma(ep_dma_id[0].id,
+					    ep_dma_id[0].str)) < 0) {
+		err("Can't get %s DMA\n", ep_dma_id[0].str);
+		goto err_out;
+	}
+	if ((ep->outdma = request_au1000_dma(ep_dma_id[1].id,
+					     ep_dma_id[1].str)) < 0) {
+		err("Can't get %s DMA\n", ep_dma_id[1].str);
+		goto err_out;
+	}
+	ep->inirq = get_dma_done_irq(ep->indma);
+	ep->outirq = get_dma_done_irq(ep->outdma);
+	// allocate EP0's DMA done interrupts.
+	if (request_irq(ep->inirq, dma_done_intr, SA_INTERRUPT,
+			"USBdev ep0 IN", &usbserial)) {
+		err("Can't get ep0 IN dma done irq\n");
+		goto err_out;
+	}
+#if 0
+	if (request_irq(ep->outirq, dma_done_intr, SA_INTERRUPT,
+			"USBdev ep0 OUT", &usbserial)) {
+		err("Can't get ep0 OUT dma done irq\n");
+		goto err_out;
+	}
+#endif
+
+	/* initialize the devfs nodes for this device and let the user
+	   know what ports we are bound to */
+	for (i = 0; i < NUM_PORTS; ++i) {
+		tty_register_devfs(&serial_tty_driver, 0, i);
+		info("usbdev serial attached to ttyUSBdev%d (or devfs usb/ttsdev/%d)", 
+		     i, i);
+		port = &usbserial.port[i];
+		port->serial = &usbserial;
+		port->number = i;
+		port->send_complete_tq.routine = port_send_complete;
+		port->send_complete_tq.data = port;
+		port->receive_complete_tq.routine = port_receive_complete;
+		port->receive_complete_tq.data = port;
+		spin_lock_init(&port->port_lock);
+
+		// Initialize the port's bulk IN endpoint
+		ep = &port->ep_bulkin;
+		spin_lock_init(&ep->lock);
+		ep->desc = &ep_desc[NUM_PORTS * i];
+		ep->reg = &ep_reg[1 + NUM_PORTS * i];
+		ep->max_pkt_size = ep->desc->wMaxPacketSize;
+		ep->indma = ep->outdma = -1;
+		if ((ep->indma =
+		     request_au1000_dma(ep_dma_id[2+NUM_PORTS*i].id,
+				 ep_dma_id[2 + NUM_PORTS * i].str)) < 0) {
+			err("Can't get %s DMA\n",
+			    ep_dma_id[2 + NUM_PORTS * i].str);
+			goto err_out;
+		}
+		ep->inirq = get_dma_done_irq(ep->indma);
+		if (request_irq(ep->inirq, dma_done_intr, SA_INTERRUPT,
+				"USBdev bulk IN", &usbserial)) {
+			err("Can't get port %d bulk IN dma done irq\n", i);
+			goto err_out;
+		}
+		// Initialize the port's bulk OUT endpoint
+		ep = &port->ep_bulkout;
+		spin_lock_init(&ep->lock);
+		ep->desc = &ep_desc[NUM_PORTS * i + 1];
+		ep->reg = &ep_reg[1 + NUM_PORTS * i + 1];
+		ep->max_pkt_size = ep->desc->wMaxPacketSize;
+		ep->indma = ep->outdma = -1;
+		if ((ep->outdma =
+		     request_au1000_dma(ep_dma_id[2+NUM_PORTS*i + 1].id,
+					ep_dma_id[2+NUM_PORTS*i + 1].str)) < 0) {
+			err("Can't get %s DMA\n",
+			    ep_dma_id[2 + NUM_PORTS * i + 1].str);
+			goto err_out;
+		}
+		ep->outirq = get_dma_done_irq(ep->outdma);
+#if 0
+		if (request_irq(ep->outirq, dma_done_intr, SA_INTERRUPT,
+				"USBdev bulk OUT", &usbserial)) {
+			err("Can't get port %d bulk OUT dma done irq\n", i);
+			goto err_out;
+		}
+#endif
+	}
+
+	// enable device controller
+	outl_sync(0x0002, USB_DEV_ENABLE);
+	udelay(100);
+	outl_sync(0x0003, USB_DEV_ENABLE);
+	udelay(100);
+	for (i = 0; i < sizeof(au1000_config_table) / sizeof(u32); ++i)
+		outl_sync(au1000_config_table[i], USB_DEV_CONFIG);
+
+	// Flush the endpoint buffers and FIFOs
+	ep = &usbserial.ep_ctrl;
+	endpoint_flush(ep);
+	// start packet reception on control ep
+	kickstart_receive_packet(ep);
+
+	for (i = 0; i < NUM_PORTS; ++i) {
+		struct usb_serial_port *port = &usbserial.port[i];
+		endpoint_flush(&port->ep_bulkin);
+		endpoint_flush(&port->ep_bulkout);
+		// start packet reception on bulk OUT endpoint
+		kickstart_receive_packet(&port->ep_bulkout);
+	}
+
+	/*
+	 * Enable Receive FIFO Complete interrupts only. Transmit
+	 * complete is being handled by the DMA done interrupts.
+	 */
+	outl_sync(0x31, USB_DEV_INT_ENABLE);
+
+	return 0;
+
+      err_out:
+	usbdev_serial_exit();
+	return -1;
+}
+
+
+module_init(usbdev_serial_init);
+module_exit(usbdev_serial_exit);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)