/*
 * National 9603/4 USB Device Controller driver
 * Copyright (C) 2004 Technical Solutions Inc. (support@techsol.ca)
 * ported from : The Goku-S driver
 * Copyright (C) 2003 MontaVista Software (source@mvista.com)
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

/*
 * The ep->stage information refers to the state of a setup transaction
 *
 * state 0: no setup packet has been received
 * state 1: setup packet has been received
 * state 2: data has been sent/received
 * state 3: ZLP has been received/sent
 */

#include <common.h>
#include <linux/list.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <malloc.h>
#include <usb/lin_gadget_compat.h>

#include <asm/byteorder.h>
#include <asm/io.h>

#include "mb9bfxxx_udc.h"

#ifdef DEBUG
#define ser_debug(fmt, args...) serial_printf(fmt, ##args)
#else
#define ser_debug(fmt, args...)
#endif

#define	DRIVER_DESC		"MB9BFxxx USB Device Controller"
#define	DRIVER_VERSION		"08-Apr 2014"

static const char driver_name [] = "mb9bfxxx_udc";
static const char driver_desc [] = DRIVER_DESC;

static void nuke(struct mb9bfxxx_ep *ep, int status);
inline void send_zero_length(int endpoint, struct mb9bfxxx_udc *dev);
static int read_mode = 0;

static inline void ep_bfini_clear(struct mb9bfxxx_udc *dev)
{
	int i;
	for (i = 1; i <= 5; i++) {
		struct mb9bfxxx_ep *ep = &dev->ep[i];
		if (dev->ep[i].enable) {
			writel(readw(ep->status) & ~0x8000, ep->status);
			readw(ep->status);
		}
	}
}

//enable an end point, of description desc
static int mb9bfxxx_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) {
	struct mb9bfxxx_udc *dev;
	struct mb9bfxxx_ep  *ep;
	u16              max;

	ep = container_of(_ep, struct mb9bfxxx_ep, ep);

	if (!_ep || !desc || ep->desc || desc->bDescriptorType != USB_DT_ENDPOINT)
		return -EINVAL;

	dev = ep->dev;
	if (!ep->num) 
		return -EINVAL;
	if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)
		return -ESHUTDOWN;
	if (ep->num && !(desc->bEndpointAddress & 0x0f))
		return -EINVAL;

	switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
		case USB_ENDPOINT_XFER_BULK:
		case USB_ENDPOINT_XFER_INT:
		case USB_ENDPOINT_XFER_ISOC:
			break;
		default:
			return -EINVAL;
	}
	if (!ep->configure) {
		ep->is_in = desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK?1:0;
		writeb(readb(USB_UDCC) | USB_UDCC_RST, USB_UDCC);
		writew(USB_EPC_EPEN |
		       ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) << 13) |
		       ((ep->is_in) << 12) |
		       ((ep->num == 1)?MAX_EP1_SIZE:MAX_EP_SIZE),
		       ep->control);

		writeb(readb(USB_UDCC) & ~USB_UDCC_RST, USB_UDCC);
		
		ep->configure = 1;
	} else
		writew(readw(ep->status) & ~0x8000, ep->status);
	max = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize));
	ep->ep.maxpacket = min_t(u16, max, (ep->num == 1)?MAX_EP1_SIZE:MAX_EP_SIZE);
	ep->desc = desc;
	ep->enable = 1;
	ep_bfini_clear(dev);
	return 0;
}

static int mb9bfxxx_ep_disable(struct usb_ep *_ep)//ep > 0
{
	struct mb9bfxxx_ep		*ep;

	ep = container_of(_ep, struct mb9bfxxx_ep, ep);

	if (!_ep || !ep->desc)
		return -ENODEV;

	nuke(ep, -ESHUTDOWN);
	if (ep->num) {
		writew(readw(ep->status) | 0x8000, ep->status);
		writew(readw(ep->control) | 0x0200, ep->control);
	} else {
		writew(readw(USB_EP0IS) | 0x8000, USB_EP0IS);
		writew(readw(USB_EP0OS) | 0x8000, USB_EP0OS);
		writew(readw(USB_EP0C) | 0x0200, USB_EP0C);
	}
	ep->desc = NULL;
	ep->enable = 0;

	return 0;
}

/*-------------------------------------------------------------------------*/

static struct usb_request *
mb9bfxxx_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
{
	struct mb9bfxxx_request	*req;

	if (!_ep)
		return 0;
	req = malloc(sizeof *req);
	if (!req)
		return 0;

	memset(req, 0, sizeof *req);
	INIT_LIST_HEAD(&req->queue);
	return &req->req;
}

static void
mb9bfxxx_free_request(struct usb_ep *_ep, struct usb_request *_req)
{
	struct mb9bfxxx_request	*req;

	if (!_ep || !_req)
		return;

	req = container_of(_req, struct mb9bfxxx_request, req);
	WARN_ON(!list_empty(&req->queue));
	free(req);
}

/*-------------------------------------------------------------------------*/

static void done(struct mb9bfxxx_ep *ep, struct mb9bfxxx_request *req, int status);

static inline int
write_packet(struct mb9bfxxx_ep *ep, u8 *buf, struct mb9bfxxx_request *req)
{
	unsigned        written_length, desired_length, available_length, maximum_length, loop_length;

	u32 fifo = ep->fifo;
	u32 status = ep->status;

	if (!ep->num) {
		fifo = USB_EP0DTL;
		status = USB_EP0IS;
	}

	ep->packets++;
	
	desired_length = req->req.length - req->req.actual;

	available_length = (ep->num == 1)?MAX_EP1_SIZE:MAX_EP_SIZE;
	written_length = 0;
	maximum_length = (ep->num == 1)?MAX_EP1_SIZE:MAX_EP_SIZE;
	while ((loop_length = min(min(available_length, desired_length), maximum_length))) {
		int i = 0;
		while (i < loop_length) { 
			writeb(*buf++, fifo + (i & 1));
			i++;
		}
		written_length += loop_length;
		desired_length -= loop_length;
		maximum_length -= loop_length;
		if (desired_length && maximum_length)
			available_length = (readw(status) & ep->maxlenmask);
	}
	
	req->req.actual += written_length;

	udelay(1);
	writew(readw(status) & ~0x0400, status);
	readw(status);
	if (!written_length) req->req.zero = 0;//just wrote zero bytes, there is no more need for req.zero 
	return written_length;
}       

// return:  0 = still running, 1 = completed, negative = errno
static int write_fifo(struct mb9bfxxx_ep *ep, struct mb9bfxxx_request *req)
{
	u8              *buf;
	unsigned        count;
	int             is_last;

	buf = req->req.buf + req->req.actual;
	
	count = write_packet(ep, buf, req);
	if (count < 0)
		return count;
	
	/* last packet often short (sometimes a zlp, especially on ep0) */
	if ((req->req.length > req->req.actual) || req->req.zero)
		is_last = 0;
	else
		is_last = 1;
	
	/* requests complete when all IN data is in the FIFO,
	 * or sometimes later, if a zlp was needed.
	 */
	if (is_last) {
		done(ep, req, 0);
		return 1;
	}
	return 0;
}

static int read_fifo(struct mb9bfxxx_ep *ep, struct mb9bfxxx_request *req)
{
	u32                     size;
	u8                      *buf;
	int			bufferspace_available, num_bytes_read;
	int 			fifo, status;
	int 			i;
	ep->packets++;
	if (!ep->num) { 
		fifo = USB_EP0DTL;
		status = USB_EP0OS;
	} else {
		fifo = ep->fifo;
		status = ep->status;
	}
	num_bytes_read = 0;
	buf = req->req.buf + req->req.actual;
	bufferspace_available = req->req.length - req->req.actual;
	size = readw(status) & (ep->maxlenmask);
	ser_debug("%s %d\n", __func__, ep->num);
	/* read all bytes from this packet */
	i = 0;
	while (size-- != 0) {
		u8 byte = readb(fifo + (i & 1));
		i++;
		if (unlikely(bufferspace_available == 0)) {
			/* this happens when the driver's buffer
			 * is smaller than what the host sent.
			 * discard the extra data in this packet.
			 */
			writew(readw(status) & ~0x0400, status);
			readw(status);
			done(ep, req, -EOVERFLOW);
			return 1;
		} else {
			*buf++ = byte;
			bufferspace_available--;
			num_bytes_read++;
		}
	}
	writew(readw(status) & ~0x0400, status);
	readw(status);
	/* completion */
	req->req.actual = req->req.actual + num_bytes_read;
	if (req->req.actual == req->req.length) {
		done(ep, req, 0);
		return 1;
	}
	return 0;
}


/*-------------------------------------------------------------------------*/

static int request_voodoo = 0;//number of bytes the host requested

static inline void
ep_rw(struct mb9bfxxx_ep *ep)
{
	struct mb9bfxxx_request     *req;

	if (list_empty (&ep->queue)) {
		if (!ep->num) {
			writew(readw(USB_EP0OS) & ~0x0400, USB_EP0OS);
			readw(USB_EP0OS);
			if (ep->is_in && (ep->stage == 2)) {
				ep->is_in = 0;//switch modes
				writew(readw(USB_EP0OS) | 0x8000, USB_EP0OS);//needed to receive a ZLP after tx
				writew(readw(USB_EP0OS) & ~0x8400, USB_EP0OS);
				readw(USB_EP0OS);
				ep->stage++;//and bump the stage number 
			} else if (ep->stage == 3)
					ep->stage = 0;
		} else {
			writew(readw(ep->status) & ~0x0400, ep->status);
			readw(ep->status);
		}
		return ;
	}
	req = list_entry(ep->queue.next, struct mb9bfxxx_request, queue);
	(ep->is_in ? write_fifo : read_fifo)(ep, req);
}

/*-------------------------------------------------------------------------*/

static void * mb9bfxxx_alloc_buffer(struct usb_ep *_ep, unsigned bytes, dma_addr_t *dma, gfp_t gfp_flags)
{
	return malloc(bytes);
}

static void mb9bfxxx_free_buffer(struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes)
{
	free (buf);
}



/*-------------------------------------------------------------------------*/

static void
done(struct mb9bfxxx_ep *ep, struct mb9bfxxx_request *req, int status)
{
	struct mb9bfxxx_udc         *dev;

	list_del_init(&req->queue);
	ep->queue_active--;
	
	if (req->req.status == -EINPROGRESS)
		req->req.status = status;
	else
		status = req->req.status;
	
	dev = ep->dev;
	
	if (!ep->nuking) {
		ep->stage++;
		ep->toggle = 1;//other endpoints stay in their flipping mode between transactions
		if (ep->stage == 2) {//we are in stage 2 now
			if (ep->num == 0 && !ep->is_in) {
				ep->is_in = 1;//switch modes
				request_voodoo = 1;//prevents mb9bfxxx_queue from calling us again before doing anything
				send_zero_length(0, dev);
			} else {//we have to receive a ZLP
				//this will happen when the tx is complete, the pio_advance fcn will activate it for us
			}
		}
	}
	req->req.complete(&ep->ep, &req->req);
}


/*-------------------------------------------------------------------------*/

static int
mb9bfxxx_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
{
	struct mb9bfxxx_request	*req;
	struct mb9bfxxx_ep		*ep;
	struct mb9bfxxx_udc	*dev;
	int			status;

	req = container_of(_req, struct mb9bfxxx_request, req);
	if (unlikely(!_req || !_req->complete
			|| !_req->buf || !list_empty(&req->queue)))
		return -EINVAL;
	ep = container_of(_ep, struct mb9bfxxx_ep, ep);
	if (unlikely(!_ep || (!ep->desc && ep->num != 0)))
		return -EINVAL;
	dev = ep->dev;
	if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) {
		return -ESHUTDOWN;
	}
	if (ep->nuking)
		return -ESHUTDOWN;

	ep->queue_reqs++;
	ep->queue_active++;

	_req->status = -EINPROGRESS;
	_req->actual = 0;

	/* for ep0 IN without premature status, zlp is required and
	 * writing EOP starts the status stage (OUT).
	 */
	if (ep->num == 0) {
		if ((request_voodoo > _req->length) && !(_req->length % MAX_EP_SIZE) && (_req->length != 0)) {
			_req->zero = 1;
		}
		if (!request_voodoo && !ep->is_in) {//this is a zero length request
			done(ep, req, 0);//this doesn't check if the list is empty (probably not an issue)
			return 0;	//shouldn't this be handled by the rx irq fcn, and passed to pio_advance
		}//that may conflict with the voodoo stuff, maybe best to leave it
	}
	
	/* kickstart this i/o queue? */
	status = 0;
	if (list_empty(&ep->queue) && ep->is_in) {
		status = write_fifo(ep, req);
		if (status == -EBUSY)
			;//we should queue up the request then
		else {
			if (status != 0) {
				if (status > 0)
					status = 0;
				req = NULL;
			}
		}
	} /* else pio or dma irq handler advances the queue. */

	if (req) {
		list_add_tail(&req->queue, &ep->queue);
	}

	return status;
}

/* dequeue ALL requests */
static void nuke(struct mb9bfxxx_ep *ep, int status)
{
	struct mb9bfxxx_request	*req;
	
	if (list_empty(&ep->queue))
		return;
	ep->nuking = 1;
	while (!list_empty(&ep->queue)) {
		req = list_entry(ep->queue.next, struct mb9bfxxx_request, queue);
		done(ep, req, status);
	}
	ep->nuking = 0;
}

/* dequeue JUST ONE request */
static int mb9bfxxx_dequeue(struct usb_ep *_ep, struct usb_request *_req)
{
	struct mb9bfxxx_request	*req;
	struct mb9bfxxx_ep		*ep;
	struct mb9bfxxx_udc	*dev;

	ep = container_of(_ep, struct mb9bfxxx_ep, ep);
	if (!_ep || !_req || (!ep->desc && ep->num != 0))
		return -EINVAL;
	dev = ep->dev;

	if (!dev->driver)
		return -ESHUTDOWN;

	/* make sure it's actually queued on this endpoint */
	list_for_each_entry (req, &ep->queue, queue) {
		if (&req->req == _req)
			break;
	}
	if (&req->req != _req) {
		return -EINVAL;
	}

	return req ? 0 : -EOPNOTSUPP;
}

static int mb9bfxxx_clear_halt(struct usb_ep *_ep) {
	struct mb9bfxxx_ep *ep;
	ep = container_of (_ep, struct mb9bfxxx_ep, ep);

	if (ep->num)
		writew(readw(ep->control) & ~USB_EPC_STAL, ep->control);
	else
		writew(readw(USB_EP0C) & ~USB_EPC_STAL, USB_EP0C);

	ep_rw(ep);
	return 0;
}

static int mb9bfxxx_set_halt(struct usb_ep *_ep, int value) {
	struct mb9bfxxx_ep *ep;
	int             retval = 0;

	if (!_ep) {
		retval = -ENODEV; goto exit;
	}
	ep = container_of (_ep, struct mb9bfxxx_ep, ep);

	if (ep->num == 0) {//is this valid?
		if (!value) {
			 retval = -EINVAL; goto exit; }

	/* don't change EPxSTATUS_EP_INVALID to READY */
	} else if (!ep->desc) {
		retval = -EINVAL; goto exit;
	}

	if (!list_empty(&ep->queue))
		retval = -EAGAIN;
	else if (!value)
		mb9bfxxx_clear_halt(_ep);
	else {
		writew(readw(ep->control) | USB_EPC_STAL, ep->control);
	}
exit:
	return retval;
}

static int mb9bfxxx_fifo_status(struct usb_ep *_ep) {
	struct mb9bfxxx_ep *ep;
	ep = container_of (_ep, struct mb9bfxxx_ep, ep);
	if (ep->num == 0) {
		return readw(USB_EP0OS) & 0x7f;
	} else {
		return readw(ep->status) & ep->maxlenmask;
	}
	return -1;
}

static void mb9bfxxx_fifo_flush(struct usb_ep *_ep) {
	struct mb9bfxxx_ep *ep;
	ep = container_of (_ep, struct mb9bfxxx_ep, ep);
	if (ep->num == 0) {
		writew(readw(USB_EP0IS) | 0x8000, USB_EP0IS);
		writew(readw(USB_EP0OS) | 0x8000, USB_EP0OS);
		writew(readw(USB_EP0IS) & ~0x8000, USB_EP0IS);
		writew(readw(USB_EP0OS) & ~0x8000, USB_EP0OS);
	} else {
		writew(readw(ep->status) | 0x8000, ep->status);
		writew(readw(ep->status) & ~0x8000, ep->status);
	}
}

/*-------------------------------------------------------------------------*/

static struct usb_ep_ops mb9bfxxx_ep_ops = {
	.enable         = mb9bfxxx_ep_enable,
	.disable        = mb9bfxxx_ep_disable,

	.alloc_request  = mb9bfxxx_alloc_request,//io request objects called struct usb_request
	.free_request   = mb9bfxxx_free_request,

	.queue          = mb9bfxxx_queue,//submit a struct usb_request object to an endpoint
	.dequeue        = mb9bfxxx_dequeue,

	.set_halt       = mb9bfxxx_set_halt,//halts an endpoint
	.fifo_status    = mb9bfxxx_fifo_status,//bytes in FIFO + data ready to go in FIFO
	.fifo_flush     = mb9bfxxx_fifo_flush,//flush all the data, endpoint is probably been reconfigured
};

/*-------------------------------------------------------------------------*/

static int mb9bfxxx_get_frame(struct usb_gadget *_gadget)
{
	return -EOPNOTSUPP;
}

static const struct usb_gadget_ops mb9bfxxx_ops = {
	.get_frame	= mb9bfxxx_get_frame,
};

/*-------------------------------------------------------------------------*/

static void udc_reinit (struct mb9bfxxx_udc *dev)
{
	static char *names [] = { "ep0", "ep1", "ep2", "ep3", "ep4", "ep5"};
	unsigned i;

	INIT_LIST_HEAD (&dev->gadget.ep_list);
	dev->gadget.ep0 = &dev->ep[0].ep;
	dev->gadget.speed = USB_SPEED_UNKNOWN;
	writeb(readb(USB_UDCC) | USB_UDCC_RST, USB_UDCC);

	for (i = 0; i < ARRAY_SIZE(names); i++) {
		struct mb9bfxxx_ep	*ep = &dev->ep[i];
		ep->num = i;
		ep->numActual = i; 
		ep->ep.name = names[i];
		if (i > 0) {
			ep->fifo = ((i - 1) * 4) + USB_EP1DTL;
			ep->control = ((i - 1) * 4) + USB_EP1C;
			ep->status = ((i - 1) * 4) + USB_EP1S;
			ep->ep.maxpacket = (i == 1)?MAX_EP1_SIZE:MAX_EP_SIZE;
			ep->maxlenmask = (i == 1)?0x1ff:0x7f;
		} else {//were are endpoint 0
			ep->fifo = ep->control = ep->status = 0xff;//this should force an error
										//we need to do this since we don't know if 
										//this is tx or rx
			writew(0x8000, USB_EP0IS);
			writew(readw(USB_EP0OS) | 0x8000, USB_EP0OS);
			ep->stage = 0;
			ep->ep.maxpacket = 64;
			ep->maxlenmask = 0x7f;
			writew((readw(USB_EP0C) & ~ep->maxlenmask) | ep->ep.maxpacket, USB_EP0C);
		}
		ep->fifoNum = i;//ignored for endpoint 0
		ep->ep.ops = &mb9bfxxx_ep_ops;
		list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list);
		ep->dev = dev;
		INIT_LIST_HEAD (&ep->queue);
		ep->nuking=0;
		ep->queue_reqs = 0;
		ep->queue_active = 0;
		ep->packets = 0;
		ep->desc = 0;
	}
	writeb(readb(USB_UDCC) & ~USB_UDCC_RST, USB_UDCC);
	dev->gadget.speed = USB_SPEED_FULL;
	for (i = 0; i < ARRAY_SIZE(names); i++) {
		struct mb9bfxxx_ep	*ep = &dev->ep[i];
		if (i) {
			writew(readw(ep->status) & ~0x8000, ep->status);
			readw(ep->status);
		} else {
			writew(0x0400, USB_EP0IS);
			writew(readw(USB_EP0OS) & ~0x8000, USB_EP0OS);
			readw(USB_EP0IS);
			readw(USB_EP0OS);
		}
	}
	list_del_init (&dev->ep[0].ep.ep_list);
}

static void udc_reset(struct mb9bfxxx_udc *dev)
{
	writeb(readb(USB_UDCC) | USB_UDCC_RST | USB_UDCC_HCONX, USB_UDCC);
}

static void udc_enable(struct mb9bfxxx_udc *dev)
{
	udc_reset(dev); //this is to prevent a pullup resistor

	// enable ep0 interrupts
	dev->ep[0].is_in = 0;
	
	udc_reinit (dev);
	writeb(readb(USB_UDCC) & ~USB_UDCC_HCONX, USB_UDCC);//this activates the pull-up and turns on interrupts
}

/*-------------------------------------------------------------------------*/

/* keeping it simple:
 * - one bus driver, initted first;
 * - one function driver, initted second
 */

static struct mb9bfxxx_udc        *the_controller;

/* when a driver is successfully registered, it will receive
 * control requests including set_configuration(), which enables
 * non-control requests.  then usb traffic follows until a
 * disconnect is reported.  then a host may connect again, or
 * the driver might get unbound.
 */
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
	struct mb9bfxxx_udc	*dev = the_controller;
	int			retval;

	if (!driver
			|| driver->speed != USB_SPEED_FULL
			|| !driver->bind
			|| !driver->unbind
			|| !driver->disconnect
			|| !driver->setup)
		return -EINVAL;
	if (!dev)
		return -ENODEV;
	if (dev->driver)
		return -EBUSY;

	/* hook up the driver */
	dev->driver = driver;
	retval = driver->bind(&dev->gadget);
	if (retval) {
		dev->driver = 0;
		return retval;
	}

	/* then enable host detection and ep0; and we're ready
	 * for set_configuration as well as eventual disconnect.
	 */
	udc_reinit(dev);//this is necessary as it sets up the epx functions
	udc_enable(dev);

	return 0;
}

int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
{
	struct mb9bfxxx_udc	*dev = the_controller;
	int i;

	if (!dev)
		return -ENODEV;
	if (!driver || driver != dev->driver)
		return -EINVAL;

	dev->driver = 0;
	
	udc_reset(dev);//reset & diable irqs
	for (i = 0; i < ARRAY_SIZE(dev->ep); i++)
		nuke(&dev->ep [i], -ESHUTDOWN);

	if (dev->gadget.speed != USB_SPEED_UNKNOWN)
		driver->disconnect(&dev->gadget);
	driver->unbind(&dev->gadget);

	return 0;
}

static void my_req_complete(struct usb_ep *_ep, struct usb_request *req) {//this was for the setup packet, but I guess I could use it for anything
	mb9bfxxx_free_buffer(_ep, req->buf, req->dma, req->length);
	mb9bfxxx_free_request(_ep, req);
}

inline void send_dummy_packet(int endpoint, struct mb9bfxxx_udc *dev, int length) {
	struct usb_request *my_req;
	my_req = mb9bfxxx_alloc_request(&dev->ep[endpoint].ep, 0);
	my_req->length = length;
	my_req->buf = mb9bfxxx_alloc_buffer(&dev->ep[endpoint].ep, length, &my_req->dma, 0);
	my_req->complete = my_req_complete;
	mb9bfxxx_queue(&dev->ep[endpoint].ep, my_req, 0);
}

inline void send_zero_length(int endpoint, struct mb9bfxxx_udc *dev) {
	send_dummy_packet(endpoint, dev, 0);
}

static inline void process_setup(struct mb9bfxxx_ep *ep, struct usb_ctrlrequest *ctrl)
{
	struct mb9bfxxx_udc *dev = ep->dev;

	if (ctrl->bRequestType & 0x80) {//This is an IN transaction
		ep->is_in = 1;//David: is this correct for both cases//check with mb9bfxxx_queue
		read_mode = 0;
		if (ctrl->wLength) {//should be followed by ZLP out packet
		} else {//host expects ZLP out packet
			ep->stage = 2;
			}
	} else {//This is an out transaction
		if (ctrl->wLength) {
			ep->is_in = 0;
			read_mode = 1;
		} else {//host expects ZLP in packet
			read_mode = 0;
			ep->stage = 2;
			ep->is_in = 1;
		}
	}
	if (dev->driver->setup(&dev->gadget, ctrl) < 0)//there was an error
		if (((ctrl->bRequestType & 0x80) && ctrl->wLength) || (!(ctrl->bRequestType & 0x80) && !ctrl->wLength))
			send_zero_length(0, dev);
}

static inline void mb9bfxxx_udc_ep0_rx_irq(struct mb9bfxxx_udc *dev)
{
	struct mb9bfxxx_ep *ep;

	u8 udcs = readb(USB_UDCS);
	ep = &dev->ep[0];
	if (udcs & USB_UDCS_SETP) {
		struct usb_ctrlrequest ctrl;
		u8 *p = (u8 *)&ctrl;
		int i;
		ep->packets++;
		ep->stage = 1;
		for (i = 0; i < 8; i++)
			*p++ = (i & 1)?readb(USB_EP0DTH):readb(USB_EP0DTL);
		ep->toggle = 1;
		request_voodoo = le16_to_cpu(ctrl.wLength);
		writew(0x0400, USB_EP0IS);
		readw(USB_EP0IS);
		ser_debug("%s %02x %02x %04x %04x %04x\n", __func__, 
			  ctrl.bRequestType, ctrl.bRequest, ctrl.wValue,
			  ctrl.wIndex, ctrl.wLength);
		process_setup(ep, &ctrl);
		writeb(udcs & ~0x02, USB_UDCS);
		readb(USB_UDCS);
	}//setup
	else if (read_mode)
		ep_rw(ep);
	else {
		ep->stage = 0;
		ep->packets++;
	}
}

static void mb9bfxxx_udc_irq(struct mb9bfxxx_udc *dev)
{
	u8 irq_src = readb(USB_UDCS);
	int i;

	if (irq_src & USB_UDCS_BRST) {
		dev->driver->disconnect(&dev->gadget);
		for (i = 0; i < ARRAY_SIZE(dev->ep); i++)
			nuke(&dev->ep [i], -ESHUTDOWN);//this should be handled 
		udc_reinit(dev);
	}
	if ((irq_src & USB_UDCS_CONF)) {
		struct usb_ctrlrequest ctrl;
		ctrl.bRequestType = 0x00;
		ctrl.bRequest     = 0x09;
		ctrl.wValue       = 0x01;
		ctrl.wIndex = ctrl.wLength = 0;
		process_setup(&dev->ep[0], &ctrl);
	}
	writeb(irq_src & 0x2, USB_UDCS);
}

/* EP1-5 Tx/Rx Interrupt */
static void mb9bfxxx_udc_ep_irq(struct mb9bfxxx_udc *dev, int epnum)
{
	ep_rw(&dev->ep[epnum]);
}

int usb_gadget_handle_interrupts(void) {
	struct mb9bfxxx_udc         *dev = the_controller;
	int i;

	if (readb(USB_UDCS) & ~0x2) {
		mb9bfxxx_udc_irq (dev);
	}
	if (readw(USB_EP0OS) & 0x0400) {
		mb9bfxxx_udc_ep0_rx_irq(dev);
	}
	if (readw(USB_EP0IS) & 0x0400) {
		mb9bfxxx_udc_ep_irq(dev, 0);
	}
	for (i = 0; i < 5; i++) {
		if (readw(USB_EP1S + i * 4) & 0x0400) {
			mb9bfxxx_udc_ep_irq(dev, i + 1);
		}
	}
	
	return 0;
}

/*-------------------------------------------------------------------------*/
int mb9bfxxx_udc_probe (void)
{
	struct mb9bfxxx_udc	*dev;

	if (the_controller) 
		return -EBUSY;

	/* alloc, and start init */
	dev = malloc(sizeof *dev);
	if (dev == NULL){
		printf("No memory\n");
		return -ENOMEM;
	}
	memset(dev, 0, sizeof *dev);
	dev->gadget.ops = &mb9bfxxx_ops;
	dev->gadget.is_dualspeed = 0;

	/* the "gadget" abstracts/virtualizes the controller */
	dev->gadget.name = driver_name;

	/* initialize the hardware */

	udc_reset(dev);
	udc_reinit(dev);
	the_controller = dev;

	return 0;
}
