summaryrefslogblamecommitdiff
path: root/drivers/usb/gadget/lh7a40x_udc.c
blob: d554b08956031c15c6bb633ec99005239d5529ce (plain) (tree)






















                                                                            

                                  

















































                                                                                  
                                                                           
                                                                          
                                                                         


                                                                    




















                                                                             
























































































































































                                                                                                                 
                                         






























                                                          
                                         































                                                                             
                                         




























































































                                                                                    
                                                         

                   



                                                          













































                                                                                
                                                                







                                                  
                                      













































                                                                             
                                                                         
































                                                                        
                                                           

























































                                                                                
                                        




























                                                                 
                                        



















                                                         
                                        























                                                           
                                        









                                                                                
                                                           































                                                    
                                        






























                                                                         
                                                         








                                                                             
                                                    
























                                                                                                   
                                            










                                                                      
                                                         


                                                                     
                                                












                                                                                 
                                                                 










                                                           
                                                           








































                                                                             
                                                      




























                                                         
                                                       


















                                                                               
                                                     














































































                                                                              
                                         





                                                                      
                                                              






                                                                  
                                                                     






                                                                      
                                                                     




                                                                     
                                                            









                                                             

                                                      


                                           
                                                       










                                                 
                                         


                                                      
                                                       


















                                                      
                                                        



                                                                   
                                                                 


                                    
                                        
 
                                               


                         








                                                                             
                                        





                                                              




                                                                      
                                         





                                    
                                               




                                                              
                                                    




                                                                      
                                                




                                                                               
                                                                            


























































                                                                                
                                         

































                                                                      
                                                




                                    
                                                                    












                                                                                          
                                                                                 


















































                                                                                              
                                                


                               
                                                  
























                                                                      
                                                



















                                                                             
                                    












                                                                                
                                                                   










































                                                                               
                                    
























































                                                                                     
                                                 




















                                                                                 
                                             

















                                                                                
                                            




                                                                           
                                                                  















                                                           
                                             






                                                                                
                                                      



















                                                                      
                                                                          



                                                                      
                                                          



                                                     
                                                       

































































































                                                                                 
                                               




















































                                                                                
                                                        








                                                                           
                                                      







































                                                                                                
                                             

















                                                                 
                                                   












                                                                            
                                                                            















                                                                               
                                    


























                                                                         
                                                                           

















                                                                             
                                                                       




























                                                                                
                                             





















                                                                              
                                             



















                                                          
                                                  









                                           
                                                 













































































                                                         
                                                          



                                          
                                          

                                   
                              

                                            
                                            

                             
                                        





                                                              
                                                                                 











                                                                             
                                                           
 
                                                             
 
                                          
 


                              

                            


                                   
                                      







                                                                             
                                            
                                   
                                     


                                                



                                            



                                
                                                                            
                                                     



                                 
                                                







                                                 
                                     
/*
 * linux/drivers/usb/gadget/lh7a40x_udc.c
 * Sharp LH7A40x on-chip full speed USB device controllers
 *
 * Copyright (C) 2004 Mikko Lahteenmaki, Nordic ID
 * Copyright (C) 2004 Bo Henriksen, Nordic ID
 *
 * 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 program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <linux/platform_device.h>

#include "lh7a40x_udc.h"

//#define DEBUG printk
//#define DEBUG_EP0 printk
//#define DEBUG_SETUP printk

#ifndef DEBUG_EP0
# define DEBUG_EP0(fmt,args...)
#endif
#ifndef DEBUG_SETUP
# define DEBUG_SETUP(fmt,args...)
#endif
#ifndef DEBUG
# define NO_STATES
# define DEBUG(fmt,args...)
#endif

#define	DRIVER_DESC			"LH7A40x USB Device Controller"
#define	DRIVER_VERSION		__DATE__

#ifndef _BIT			/* FIXME - what happended to _BIT in 2.6.7bk18? */
#define _BIT(x) (1<<(x))
#endif

struct lh7a40x_udc *the_controller;

static const char driver_name[] = "lh7a40x_udc";
static const char driver_desc[] = DRIVER_DESC;
static const char ep0name[] = "ep0-control";

/*
  Local definintions.
*/

#ifndef NO_STATES
static char *state_names[] = {
	"WAIT_FOR_SETUP",
	"DATA_STATE_XMIT",
	"DATA_STATE_NEED_ZLP",
	"WAIT_FOR_OUT_STATUS",
	"DATA_STATE_RECV"
};
#endif

/*
  Local declarations.
*/
static int lh7a40x_ep_enable(struct usb_ep *ep,
			     const struct usb_endpoint_descriptor *);
static int lh7a40x_ep_disable(struct usb_ep *ep);
static struct usb_request *lh7a40x_alloc_request(struct usb_ep *ep, gfp_t);
static void lh7a40x_free_request(struct usb_ep *ep, struct usb_request *);
static int lh7a40x_queue(struct usb_ep *ep, struct usb_request *, gfp_t);
static int lh7a40x_dequeue(struct usb_ep *ep, struct usb_request *);
static int lh7a40x_set_halt(struct usb_ep *ep, int);
static int lh7a40x_fifo_status(struct usb_ep *ep);
static void lh7a40x_fifo_flush(struct usb_ep *ep);
static void lh7a40x_ep0_kick(struct lh7a40x_udc *dev, struct lh7a40x_ep *ep);
static void lh7a40x_handle_ep0(struct lh7a40x_udc *dev, u32 intr);

static void done(struct lh7a40x_ep *ep, struct lh7a40x_request *req,
		 int status);
static void pio_irq_enable(int bEndpointAddress);
static void pio_irq_disable(int bEndpointAddress);
static void stop_activity(struct lh7a40x_udc *dev,
			  struct usb_gadget_driver *driver);
static void flush(struct lh7a40x_ep *ep);
static void udc_enable(struct lh7a40x_udc *dev);
static void udc_set_address(struct lh7a40x_udc *dev, unsigned char address);

static struct usb_ep_ops lh7a40x_ep_ops = {
	.enable = lh7a40x_ep_enable,
	.disable = lh7a40x_ep_disable,

	.alloc_request = lh7a40x_alloc_request,
	.free_request = lh7a40x_free_request,

	.queue = lh7a40x_queue,
	.dequeue = lh7a40x_dequeue,

	.set_halt = lh7a40x_set_halt,
	.fifo_status = lh7a40x_fifo_status,
	.fifo_flush = lh7a40x_fifo_flush,
};

/* Inline code */

static __inline__ int write_packet(struct lh7a40x_ep *ep,
				   struct lh7a40x_request *req, int max)
{
	u8 *buf;
	int length, count;
	volatile u32 *fifo = (volatile u32 *)ep->fifo;

	buf = req->req.buf + req->req.actual;
	prefetch(buf);

	length = req->req.length - req->req.actual;
	length = min(length, max);
	req->req.actual += length;

	DEBUG("Write %d (max %d), fifo %p\n", length, max, fifo);

	count = length;
	while (count--) {
		*fifo = *buf++;
	}

	return length;
}

static __inline__ void usb_set_index(u32 ep)
{
	*(volatile u32 *)io_p2v(USB_INDEX) = ep;
}

static __inline__ u32 usb_read(u32 port)
{
	return *(volatile u32 *)io_p2v(port);
}

static __inline__ void usb_write(u32 val, u32 port)
{
	*(volatile u32 *)io_p2v(port) = val;
}

static __inline__ void usb_set(u32 val, u32 port)
{
	volatile u32 *ioport = (volatile u32 *)io_p2v(port);
	u32 after = (*ioport) | val;
	*ioport = after;
}

static __inline__ void usb_clear(u32 val, u32 port)
{
	volatile u32 *ioport = (volatile u32 *)io_p2v(port);
	u32 after = (*ioport) & ~val;
	*ioport = after;
}

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

#define GPIO_PORTC_DR 	(0x80000E08)
#define GPIO_PORTC_DDR 	(0x80000E18)
#define GPIO_PORTC_PDR 	(0x80000E70)

/* get port C pin data register */
#define get_portc_pdr(bit) 		((usb_read(GPIO_PORTC_PDR) & _BIT(bit)) != 0)
/* get port C data direction register */
#define get_portc_ddr(bit) 		((usb_read(GPIO_PORTC_DDR) & _BIT(bit)) != 0)
/* set port C data register */
#define set_portc_dr(bit, val) 	(val ? usb_set(_BIT(bit), GPIO_PORTC_DR) : usb_clear(_BIT(bit), GPIO_PORTC_DR))
/* set port C data direction register */
#define set_portc_ddr(bit, val) (val ? usb_set(_BIT(bit), GPIO_PORTC_DDR) : usb_clear(_BIT(bit), GPIO_PORTC_DDR))

/*
 * LPD7A404 GPIO's:
 * Port C bit 1 = USB Port 1 Power Enable
 * Port C bit 2 = USB Port 1 Data Carrier Detect
 */
#define is_usb_connected() 		get_portc_pdr(2)

#ifdef CONFIG_USB_GADGET_DEBUG_FILES

static const char proc_node_name[] = "driver/udc";

static int
udc_proc_read(char *page, char **start, off_t off, int count,
	      int *eof, void *_dev)
{
	char *buf = page;
	struct lh7a40x_udc *dev = _dev;
	char *next = buf;
	unsigned size = count;
	unsigned long flags;
	int t;

	if (off != 0)
		return 0;

	local_irq_save(flags);

	/* basic device status */
	t = scnprintf(next, size,
		      DRIVER_DESC "\n"
		      "%s version: %s\n"
		      "Gadget driver: %s\n"
		      "Host: %s\n\n",
		      driver_name, DRIVER_VERSION,
		      dev->driver ? dev->driver->driver.name : "(none)",
		      is_usb_connected()? "full speed" : "disconnected");
	size -= t;
	next += t;

	t = scnprintf(next, size,
		      "GPIO:\n"
		      " Port C bit 1: %d, dir %d\n"
		      " Port C bit 2: %d, dir %d\n\n",
		      get_portc_pdr(1), get_portc_ddr(1),
		      get_portc_pdr(2), get_portc_ddr(2)
	    );
	size -= t;
	next += t;

	t = scnprintf(next, size,
		      "DCP pullup: %d\n\n",
		      (usb_read(USB_PM) & PM_USB_DCP) != 0);
	size -= t;
	next += t;

	local_irq_restore(flags);
	*eof = 1;
	return count - size;
}

#define create_proc_files() 	create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev)
#define remove_proc_files() 	remove_proc_entry(proc_node_name, NULL)

#else	/* !CONFIG_USB_GADGET_DEBUG_FILES */

#define create_proc_files() do {} while (0)
#define remove_proc_files() do {} while (0)

#endif	/* CONFIG_USB_GADGET_DEBUG_FILES */

/*
 * 	udc_disable - disable USB device controller
 */
static void udc_disable(struct lh7a40x_udc *dev)
{
	DEBUG("%s, %p\n", __func__, dev);

	udc_set_address(dev, 0);

	/* Disable interrupts */
	usb_write(0, USB_IN_INT_EN);
	usb_write(0, USB_OUT_INT_EN);
	usb_write(0, USB_INT_EN);

	/* Disable the USB */
	usb_write(0, USB_PM);

#ifdef CONFIG_ARCH_LH7A404
	/* Disable USB power */
	set_portc_dr(1, 0);
#endif

	/* if hardware supports it, disconnect from usb */
	/* make_usb_disappear(); */

	dev->ep0state = WAIT_FOR_SETUP;
	dev->gadget.speed = USB_SPEED_UNKNOWN;
	dev->usb_address = 0;
}

/*
 * 	udc_reinit - initialize software state
 */
static void udc_reinit(struct lh7a40x_udc *dev)
{
	u32 i;

	DEBUG("%s, %p\n", __func__, dev);

	/* device/ep0 records init */
	INIT_LIST_HEAD(&dev->gadget.ep_list);
	INIT_LIST_HEAD(&dev->gadget.ep0->ep_list);
	dev->ep0state = WAIT_FOR_SETUP;

	/* basic endpoint records init */
	for (i = 0; i < UDC_MAX_ENDPOINTS; i++) {
		struct lh7a40x_ep *ep = &dev->ep[i];

		if (i != 0)
			list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list);

		ep->desc = 0;
		ep->stopped = 0;
		INIT_LIST_HEAD(&ep->queue);
		ep->pio_irqs = 0;
	}

	/* the rest was statically initialized, and is read-only */
}

#define BYTES2MAXP(x)	(x / 8)
#define MAXP2BYTES(x)	(x * 8)

/* until it's enabled, this UDC should be completely invisible
 * to any USB host.
 */
static void udc_enable(struct lh7a40x_udc *dev)
{
	int ep;

	DEBUG("%s, %p\n", __func__, dev);

	dev->gadget.speed = USB_SPEED_UNKNOWN;

#ifdef CONFIG_ARCH_LH7A404
	/* Set Port C bit 1 & 2 as output */
	set_portc_ddr(1, 1);
	set_portc_ddr(2, 1);

	/* Enable USB power */
	set_portc_dr(1, 0);
#endif

	/*
	 * C.f Chapter 18.1.3.1 Initializing the USB
	 */

	/* Disable the USB */
	usb_clear(PM_USB_ENABLE, USB_PM);

	/* Reset APB & I/O sides of the USB */
	usb_set(USB_RESET_APB | USB_RESET_IO, USB_RESET);
	mdelay(5);
	usb_clear(USB_RESET_APB | USB_RESET_IO, USB_RESET);

	/* Set MAXP values for each */
	for (ep = 0; ep < UDC_MAX_ENDPOINTS; ep++) {
		struct lh7a40x_ep *ep_reg = &dev->ep[ep];
		u32 csr;

		usb_set_index(ep);

		switch (ep_reg->ep_type) {
		case ep_bulk_in:
		case ep_interrupt:
			usb_clear(USB_IN_CSR2_USB_DMA_EN | USB_IN_CSR2_AUTO_SET,
				  ep_reg->csr2);
			/* Fall through */
		case ep_control:
			usb_write(BYTES2MAXP(ep_maxpacket(ep_reg)),
				  USB_IN_MAXP);
			break;
		case ep_bulk_out:
			usb_clear(USB_OUT_CSR2_USB_DMA_EN |
				  USB_OUT_CSR2_AUTO_CLR, ep_reg->csr2);
			usb_write(BYTES2MAXP(ep_maxpacket(ep_reg)),
				  USB_OUT_MAXP);
			break;
		}

		/* Read & Write CSR1, just in case */
		csr = usb_read(ep_reg->csr1);
		usb_write(csr, ep_reg->csr1);

		flush(ep_reg);
	}

	/* Disable interrupts */
	usb_write(0, USB_IN_INT_EN);
	usb_write(0, USB_OUT_INT_EN);
	usb_write(0, USB_INT_EN);

	/* Enable interrupts */
	usb_set(USB_IN_INT_EP0, USB_IN_INT_EN);
	usb_set(USB_INT_RESET_INT | USB_INT_RESUME_INT, USB_INT_EN);
	/* Dont enable rest of the interrupts */
	/* usb_set(USB_IN_INT_EP3 | USB_IN_INT_EP1 | USB_IN_INT_EP0, USB_IN_INT_EN);
	   usb_set(USB_OUT_INT_EP2, USB_OUT_INT_EN); */

	/* Enable SUSPEND */
	usb_set(PM_ENABLE_SUSPEND, USB_PM);

	/* Enable the USB */
	usb_set(PM_USB_ENABLE, USB_PM);

#ifdef CONFIG_ARCH_LH7A404
	/* NOTE: DOES NOT WORK! */
	/* Let host detect UDC:
	 * Software must write a 0 to the PMR:DCP_CTRL bit to turn this
	 * transistor on and pull the USBDP pin HIGH.
	 */
	/* usb_clear(PM_USB_DCP, USB_PM);
	   usb_set(PM_USB_DCP, USB_PM); */
#endif
}

/*
  Register entry point for the peripheral controller driver.
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
	struct lh7a40x_udc *dev = the_controller;
	int retval;

	DEBUG("%s: %s\n", __func__, driver->driver.name);

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

	/* first hook up the driver ... */
	dev->driver = driver;
	dev->gadget.dev.driver = &driver->driver;

	device_add(&dev->gadget.dev);
	retval = driver->bind(&dev->gadget);
	if (retval) {
		printk("%s: bind to driver %s --> error %d\n", dev->gadget.name,
		       driver->driver.name, retval);
		device_del(&dev->gadget.dev);

		dev->driver = 0;
		dev->gadget.dev.driver = 0;
		return retval;
	}

	/* ... then enable host detection and ep0; and we're ready
	 * for set_configuration as well as eventual disconnect.
	 * NOTE:  this shouldn't power up until later.
	 */
	printk("%s: registered gadget driver '%s'\n", dev->gadget.name,
	       driver->driver.name);

	udc_enable(dev);

	return 0;
}

EXPORT_SYMBOL(usb_gadget_register_driver);

/*
  Unregister entry point for the peripheral controller driver.
*/
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
{
	struct lh7a40x_udc *dev = the_controller;
	unsigned long flags;

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

	spin_lock_irqsave(&dev->lock, flags);
	dev->driver = 0;
	stop_activity(dev, driver);
	spin_unlock_irqrestore(&dev->lock, flags);

	driver->unbind(&dev->gadget);
	dev->gadget.dev.driver = NULL;
	device_del(&dev->gadget.dev);

	udc_disable(dev);

	DEBUG("unregistered gadget driver '%s'\n", driver->driver.name);
	return 0;
}

EXPORT_SYMBOL(usb_gadget_unregister_driver);

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

/** Write request to FIFO (max write == maxp size)
 *  Return:  0 = still running, 1 = completed, negative = errno
 *  NOTE: INDEX register must be set for EP
 */
static int write_fifo(struct lh7a40x_ep *ep, struct lh7a40x_request *req)
{
	u32 max;
	u32 csr;

	max = le16_to_cpu(ep->desc->wMaxPacketSize);

	csr = usb_read(ep->csr1);
	DEBUG("CSR: %x %d\n", csr, csr & USB_IN_CSR1_FIFO_NOT_EMPTY);

	if (!(csr & USB_IN_CSR1_FIFO_NOT_EMPTY)) {
		unsigned count;
		int is_last, is_short;

		count = write_packet(ep, req, max);
		usb_set(USB_IN_CSR1_IN_PKT_RDY, ep->csr1);

		/* last packet is usually short (or a zlp) */
		if (unlikely(count != max))
			is_last = is_short = 1;
		else {
			if (likely(req->req.length != req->req.actual)
			    || req->req.zero)
				is_last = 0;
			else
				is_last = 1;
			/* interrupt/iso maxpacket may not fill the fifo */
			is_short = unlikely(max < ep_maxpacket(ep));
		}

		DEBUG("%s: wrote %s %d bytes%s%s %d left %p\n", __func__,
		      ep->ep.name, count,
		      is_last ? "/L" : "", is_short ? "/S" : "",
		      req->req.length - req->req.actual, req);

		/* requests complete when all IN data is in the FIFO */
		if (is_last) {
			done(ep, req, 0);
			if (list_empty(&ep->queue)) {
				pio_irq_disable(ep_index(ep));
			}
			return 1;
		}
	} else {
		DEBUG("Hmm.. %d ep FIFO is not empty!\n", ep_index(ep));
	}

	return 0;
}

/** Read to request from FIFO (max read == bytes in fifo)
 *  Return:  0 = still running, 1 = completed, negative = errno
 *  NOTE: INDEX register must be set for EP
 */
static int read_fifo(struct lh7a40x_ep *ep, struct lh7a40x_request *req)
{
	u32 csr;
	u8 *buf;
	unsigned bufferspace, count, is_short;
	volatile u32 *fifo = (volatile u32 *)ep->fifo;

	/* make sure there's a packet in the FIFO. */
	csr = usb_read(ep->csr1);
	if (!(csr & USB_OUT_CSR1_OUT_PKT_RDY)) {
		DEBUG("%s: Packet NOT ready!\n", __func__);
		return -EINVAL;
	}

	buf = req->req.buf + req->req.actual;
	prefetchw(buf);
	bufferspace = req->req.length - req->req.actual;

	/* read all bytes from this packet */
	count = usb_read(USB_OUT_FIFO_WC1);
	req->req.actual += min(count, bufferspace);

	is_short = (count < ep->ep.maxpacket);
	DEBUG("read %s %02x, %d bytes%s req %p %d/%d\n",
	      ep->ep.name, csr, count,
	      is_short ? "/S" : "", req, req->req.actual, req->req.length);

	while (likely(count-- != 0)) {
		u8 byte = (u8) (*fifo & 0xff);

		if (unlikely(bufferspace == 0)) {
			/* this happens when the driver's buffer
			 * is smaller than what the host sent.
			 * discard the extra data.
			 */
			if (req->req.status != -EOVERFLOW)
				printk("%s overflow %d\n", ep->ep.name, count);
			req->req.status = -EOVERFLOW;
		} else {
			*buf++ = byte;
			bufferspace--;
		}
	}

	usb_clear(USB_OUT_CSR1_OUT_PKT_RDY, ep->csr1);

	/* completion */
	if (is_short || req->req.actual == req->req.length) {
		done(ep, req, 0);
		usb_set(USB_OUT_CSR1_FIFO_FLUSH, ep->csr1);

		if (list_empty(&ep->queue))
			pio_irq_disable(ep_index(ep));
		return 1;
	}

	/* finished that packet.  the next one may be waiting... */
	return 0;
}

/*
 *	done - retire a request; caller blocked irqs
 *  INDEX register is preserved to keep same
 */
static void done(struct lh7a40x_ep *ep, struct lh7a40x_request *req, int status)
{
	unsigned int stopped = ep->stopped;
	u32 index;

	DEBUG("%s, %p\n", __func__, ep);
	list_del_init(&req->queue);

	if (likely(req->req.status == -EINPROGRESS))
		req->req.status = status;
	else
		status = req->req.status;

	if (status && status != -ESHUTDOWN)
		DEBUG("complete %s req %p stat %d len %u/%u\n",
		      ep->ep.name, &req->req, status,
		      req->req.actual, req->req.length);

	/* don't modify queue heads during completion callback */
	ep->stopped = 1;
	/* Read current index (completion may modify it) */
	index = usb_read(USB_INDEX);

	spin_unlock(&ep->dev->lock);
	req->req.complete(&ep->ep, &req->req);
	spin_lock(&ep->dev->lock);

	/* Restore index */
	usb_set_index(index);
	ep->stopped = stopped;
}

/** Enable EP interrupt */
static void pio_irq_enable(int ep)
{
	DEBUG("%s: %d\n", __func__, ep);

	switch (ep) {
	case 1:
		usb_set(USB_IN_INT_EP1, USB_IN_INT_EN);
		break;
	case 2:
		usb_set(USB_OUT_INT_EP2, USB_OUT_INT_EN);
		break;
	case 3:
		usb_set(USB_IN_INT_EP3, USB_IN_INT_EN);
		break;
	default:
		DEBUG("Unknown endpoint: %d\n", ep);
		break;
	}
}

/** Disable EP interrupt */
static void pio_irq_disable(int ep)
{
	DEBUG("%s: %d\n", __func__, ep);

	switch (ep) {
	case 1:
		usb_clear(USB_IN_INT_EP1, USB_IN_INT_EN);
		break;
	case 2:
		usb_clear(USB_OUT_INT_EP2, USB_OUT_INT_EN);
		break;
	case 3:
		usb_clear(USB_IN_INT_EP3, USB_IN_INT_EN);
		break;
	default:
		DEBUG("Unknown endpoint: %d\n", ep);
		break;
	}
}

/*
 * 	nuke - dequeue ALL requests
 */
void nuke(struct lh7a40x_ep *ep, int status)
{
	struct lh7a40x_request *req;

	DEBUG("%s, %p\n", __func__, ep);

	/* Flush FIFO */
	flush(ep);

	/* called with irqs blocked */
	while (!list_empty(&ep->queue)) {
		req = list_entry(ep->queue.next, struct lh7a40x_request, queue);
		done(ep, req, status);
	}

	/* Disable IRQ if EP is enabled (has descriptor) */
	if (ep->desc)
		pio_irq_disable(ep_index(ep));
}

/*
void nuke_all(struct lh7a40x_udc *dev)
{
	int n;
	for(n=0; n<UDC_MAX_ENDPOINTS; n++) {
		struct lh7a40x_ep *ep = &dev->ep[n];
		usb_set_index(n);
		nuke(ep, 0);
	}
}*/

/*
static void flush_all(struct lh7a40x_udc *dev)
{
	int n;
    for (n = 0; n < UDC_MAX_ENDPOINTS; n++)
    {
		struct lh7a40x_ep *ep = &dev->ep[n];
		flush(ep);
    }
}
*/

/** Flush EP
 * NOTE: INDEX register must be set before this call
 */
static void flush(struct lh7a40x_ep *ep)
{
	DEBUG("%s, %p\n", __func__, ep);

	switch (ep->ep_type) {
	case ep_control:
		/* check, by implication c.f. 15.1.2.11 */
		break;

	case ep_bulk_in:
	case ep_interrupt:
		/* if(csr & USB_IN_CSR1_IN_PKT_RDY) */
		usb_set(USB_IN_CSR1_FIFO_FLUSH, ep->csr1);
		break;

	case ep_bulk_out:
		/* if(csr & USB_OUT_CSR1_OUT_PKT_RDY) */
		usb_set(USB_OUT_CSR1_FIFO_FLUSH, ep->csr1);
		break;
	}
}

/**
 * lh7a40x_in_epn - handle IN interrupt
 */
static void lh7a40x_in_epn(struct lh7a40x_udc *dev, u32 ep_idx, u32 intr)
{
	u32 csr;
	struct lh7a40x_ep *ep = &dev->ep[ep_idx];
	struct lh7a40x_request *req;

	usb_set_index(ep_idx);

	csr = usb_read(ep->csr1);
	DEBUG("%s: %d, csr %x\n", __func__, ep_idx, csr);

	if (csr & USB_IN_CSR1_SENT_STALL) {
		DEBUG("USB_IN_CSR1_SENT_STALL\n");
		usb_set(USB_IN_CSR1_SENT_STALL /*|USB_IN_CSR1_SEND_STALL */ ,
			ep->csr1);
		return;
	}

	if (!ep->desc) {
		DEBUG("%s: NO EP DESC\n", __func__);
		return;
	}

	if (list_empty(&ep->queue))
		req = 0;
	else
		req = list_entry(ep->queue.next, struct lh7a40x_request, queue);

	DEBUG("req: %p\n", req);

	if (!req)
		return;

	write_fifo(ep, req);
}

/* ********************************************************************************************* */
/* Bulk OUT (recv)
 */

static void lh7a40x_out_epn(struct lh7a40x_udc *dev, u32 ep_idx, u32 intr)
{
	struct lh7a40x_ep *ep = &dev->ep[ep_idx];
	struct lh7a40x_request *req;

	DEBUG("%s: %d\n", __func__, ep_idx);

	usb_set_index(ep_idx);

	if (ep->desc) {
		u32 csr;
		csr = usb_read(ep->csr1);

		while ((csr =
			usb_read(ep->
				 csr1)) & (USB_OUT_CSR1_OUT_PKT_RDY |
					   USB_OUT_CSR1_SENT_STALL)) {
			DEBUG("%s: %x\n", __func__, csr);

			if (csr & USB_OUT_CSR1_SENT_STALL) {
				DEBUG("%s: stall sent, flush fifo\n",
				      __func__);
				/* usb_set(USB_OUT_CSR1_FIFO_FLUSH, ep->csr1); */
				flush(ep);
			} else if (csr & USB_OUT_CSR1_OUT_PKT_RDY) {
				if (list_empty(&ep->queue))
					req = 0;
				else
					req =
					    list_entry(ep->queue.next,
						       struct lh7a40x_request,
						       queue);

				if (!req) {
					printk("%s: NULL REQ %d\n",
					       __func__, ep_idx);
					flush(ep);
					break;
				} else {
					read_fifo(ep, req);
				}
			}

		}

	} else {
		/* Throw packet away.. */
		printk("%s: No descriptor?!?\n", __func__);
		flush(ep);
	}
}

static void stop_activity(struct lh7a40x_udc *dev,
			  struct usb_gadget_driver *driver)
{
	int i;

	/* don't disconnect drivers more than once */
	if (dev->gadget.speed == USB_SPEED_UNKNOWN)
		driver = 0;
	dev->gadget.speed = USB_SPEED_UNKNOWN;

	/* prevent new request submissions, kill any outstanding requests  */
	for (i = 0; i < UDC_MAX_ENDPOINTS; i++) {
		struct lh7a40x_ep *ep = &dev->ep[i];
		ep->stopped = 1;

		usb_set_index(i);
		nuke(ep, -ESHUTDOWN);
	}

	/* report disconnect; the driver is already quiesced */
	if (driver) {
		spin_unlock(&dev->lock);
		driver->disconnect(&dev->gadget);
		spin_lock(&dev->lock);
	}

	/* re-init driver-visible data structures */
	udc_reinit(dev);
}

/** Handle USB RESET interrupt
 */
static void lh7a40x_reset_intr(struct lh7a40x_udc *dev)
{
#if 0				/* def CONFIG_ARCH_LH7A404 */
	/* Does not work always... */

	DEBUG("%s: %d\n", __func__, dev->usb_address);

	if (!dev->usb_address) {
		/*usb_set(USB_RESET_IO, USB_RESET);
		   mdelay(5);
		   usb_clear(USB_RESET_IO, USB_RESET); */
		return;
	}
	/* Put the USB controller into reset. */
	usb_set(USB_RESET_IO, USB_RESET);

	/* Set Device ID to 0 */
	udc_set_address(dev, 0);

	/* Let PLL2 settle down */
	mdelay(5);

	/* Release the USB controller from reset */
	usb_clear(USB_RESET_IO, USB_RESET);

	/* Re-enable UDC */
	udc_enable(dev);

#endif
	dev->gadget.speed = USB_SPEED_FULL;
}

/*
 *	lh7a40x usb client interrupt handler.
 */
static irqreturn_t lh7a40x_udc_irq(int irq, void *_dev)
{
	struct lh7a40x_udc *dev = _dev;

	DEBUG("\n\n");

	spin_lock(&dev->lock);

	for (;;) {
		u32 intr_in = usb_read(USB_IN_INT);
		u32 intr_out = usb_read(USB_OUT_INT);
		u32 intr_int = usb_read(USB_INT);

		/* Test also against enable bits.. (lh7a40x errata).. Sigh.. */
		u32 in_en = usb_read(USB_IN_INT_EN);
		u32 out_en = usb_read(USB_OUT_INT_EN);

		if (!intr_out && !intr_in && !intr_int)
			break;

		DEBUG("%s (on state %s)\n", __func__,
		      state_names[dev->ep0state]);
		DEBUG("intr_out = %x\n", intr_out);
		DEBUG("intr_in  = %x\n", intr_in);
		DEBUG("intr_int = %x\n", intr_int);

		if (intr_in) {
			usb_write(intr_in, USB_IN_INT);

			if ((intr_in & USB_IN_INT_EP1)
			    && (in_en & USB_IN_INT_EP1)) {
				DEBUG("USB_IN_INT_EP1\n");
				lh7a40x_in_epn(dev, 1, intr_in);
			}
			if ((intr_in & USB_IN_INT_EP3)
			    && (in_en & USB_IN_INT_EP3)) {
				DEBUG("USB_IN_INT_EP3\n");
				lh7a40x_in_epn(dev, 3, intr_in);
			}
			if (intr_in & USB_IN_INT_EP0) {
				DEBUG("USB_IN_INT_EP0 (control)\n");
				lh7a40x_handle_ep0(dev, intr_in);
			}
		}

		if (intr_out) {
			usb_write(intr_out, USB_OUT_INT);

			if ((intr_out & USB_OUT_INT_EP2)
			    && (out_en & USB_OUT_INT_EP2)) {
				DEBUG("USB_OUT_INT_EP2\n");
				lh7a40x_out_epn(dev, 2, intr_out);
			}
		}

		if (intr_int) {
			usb_write(intr_int, USB_INT);

			if (intr_int & USB_INT_RESET_INT) {
				lh7a40x_reset_intr(dev);
			}

			if (intr_int & USB_INT_RESUME_INT) {
				DEBUG("USB resume\n");

				if (dev->gadget.speed != USB_SPEED_UNKNOWN
				    && dev->driver
				    && dev->driver->resume
				    && is_usb_connected()) {
					dev->driver->resume(&dev->gadget);
				}
			}

			if (intr_int & USB_INT_SUSPEND_INT) {
				DEBUG("USB suspend%s\n",
				      is_usb_connected()? "" : "+disconnect");
				if (!is_usb_connected()) {
					stop_activity(dev, dev->driver);
				} else if (dev->gadget.speed !=
					   USB_SPEED_UNKNOWN && dev->driver
					   && dev->driver->suspend) {
					dev->driver->suspend(&dev->gadget);
				}
			}

		}
	}

	spin_unlock(&dev->lock);

	return IRQ_HANDLED;
}

static int lh7a40x_ep_enable(struct usb_ep *_ep,
			     const struct usb_endpoint_descriptor *desc)
{
	struct lh7a40x_ep *ep;
	struct lh7a40x_udc *dev;
	unsigned long flags;

	DEBUG("%s, %p\n", __func__, _ep);

	ep = container_of(_ep, struct lh7a40x_ep, ep);
	if (!_ep || !desc || ep->desc || _ep->name == ep0name
	    || desc->bDescriptorType != USB_DT_ENDPOINT
	    || ep->bEndpointAddress != desc->bEndpointAddress
	    || ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) {
		DEBUG("%s, bad ep or descriptor\n", __func__);
		return -EINVAL;
	}

	/* xfer types must match, except that interrupt ~= bulk */
	if (ep->bmAttributes != desc->bmAttributes
	    && ep->bmAttributes != USB_ENDPOINT_XFER_BULK
	    && desc->bmAttributes != USB_ENDPOINT_XFER_INT) {
		DEBUG("%s, %s type mismatch\n", __func__, _ep->name);
		return -EINVAL;
	}

	/* hardware _could_ do smaller, but driver doesn't */
	if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK
	     && le16_to_cpu(desc->wMaxPacketSize) != ep_maxpacket(ep))
	    || !desc->wMaxPacketSize) {
		DEBUG("%s, bad %s maxpacket\n", __func__, _ep->name);
		return -ERANGE;
	}

	dev = ep->dev;
	if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) {
		DEBUG("%s, bogus device state\n", __func__);
		return -ESHUTDOWN;
	}

	spin_lock_irqsave(&ep->dev->lock, flags);

	ep->stopped = 0;
	ep->desc = desc;
	ep->pio_irqs = 0;
	ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);

	spin_unlock_irqrestore(&ep->dev->lock, flags);

	/* Reset halt state (does flush) */
	lh7a40x_set_halt(_ep, 0);

	DEBUG("%s: enabled %s\n", __func__, _ep->name);
	return 0;
}

/** Disable EP
 *  NOTE: Sets INDEX register
 */
static int lh7a40x_ep_disable(struct usb_ep *_ep)
{
	struct lh7a40x_ep *ep;
	unsigned long flags;

	DEBUG("%s, %p\n", __func__, _ep);

	ep = container_of(_ep, struct lh7a40x_ep, ep);
	if (!_ep || !ep->desc) {
		DEBUG("%s, %s not enabled\n", __func__,
		      _ep ? ep->ep.name : NULL);
		return -EINVAL;
	}

	spin_lock_irqsave(&ep->dev->lock, flags);

	usb_set_index(ep_index(ep));

	/* Nuke all pending requests (does flush) */
	nuke(ep, -ESHUTDOWN);

	/* Disable ep IRQ */
	pio_irq_disable(ep_index(ep));

	ep->desc = 0;
	ep->stopped = 1;

	spin_unlock_irqrestore(&ep->dev->lock, flags);

	DEBUG("%s: disabled %s\n", __func__, _ep->name);
	return 0;
}

static struct usb_request *lh7a40x_alloc_request(struct usb_ep *ep,
						 gfp_t gfp_flags)
{
	struct lh7a40x_request *req;

	DEBUG("%s, %p\n", __func__, ep);

	req = kzalloc(sizeof(*req), gfp_flags);
	if (!req)
		return 0;

	INIT_LIST_HEAD(&req->queue);

	return &req->req;
}

static void lh7a40x_free_request(struct usb_ep *ep, struct usb_request *_req)
{
	struct lh7a40x_request *req;

	DEBUG("%s, %p\n", __func__, ep);

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

/** Queue one request
 *  Kickstart transfer if needed
 *  NOTE: Sets INDEX register
 */
static int lh7a40x_queue(struct usb_ep *_ep, struct usb_request *_req,
			 gfp_t gfp_flags)
{
	struct lh7a40x_request *req;
	struct lh7a40x_ep *ep;
	struct lh7a40x_udc *dev;
	unsigned long flags;

	DEBUG("\n\n\n%s, %p\n", __func__, _ep);

	req = container_of(_req, struct lh7a40x_request, req);
	if (unlikely
	    (!_req || !_req->complete || !_req->buf
	     || !list_empty(&req->queue))) {
		DEBUG("%s, bad params\n", __func__);
		return -EINVAL;
	}

	ep = container_of(_ep, struct lh7a40x_ep, ep);
	if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) {
		DEBUG("%s, bad ep\n", __func__);
		return -EINVAL;
	}

	dev = ep->dev;
	if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) {
		DEBUG("%s, bogus device state %p\n", __func__, dev->driver);
		return -ESHUTDOWN;
	}

	DEBUG("%s queue req %p, len %d buf %p\n", _ep->name, _req, _req->length,
	      _req->buf);

	spin_lock_irqsave(&dev->lock, flags);

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

	/* kickstart this i/o queue? */
	DEBUG("Add to %d Q %d %d\n", ep_index(ep), list_empty(&ep->queue),
	      ep->stopped);
	if (list_empty(&ep->queue) && likely(!ep->stopped)) {
		u32 csr;

		if (unlikely(ep_index(ep) == 0)) {
			/* EP0 */
			list_add_tail(&req->queue, &ep->queue);
			lh7a40x_ep0_kick(dev, ep);
			req = 0;
		} else if (ep_is_in(ep)) {
			/* EP1 & EP3 */
			usb_set_index(ep_index(ep));
			csr = usb_read(ep->csr1);
			pio_irq_enable(ep_index(ep));
			if ((csr & USB_IN_CSR1_FIFO_NOT_EMPTY) == 0) {
				if (write_fifo(ep, req) == 1)
					req = 0;
			}
		} else {
			/* EP2 */
			usb_set_index(ep_index(ep));
			csr = usb_read(ep->csr1);
			pio_irq_enable(ep_index(ep));
			if (!(csr & USB_OUT_CSR1_FIFO_FULL)) {
				if (read_fifo(ep, req) == 1)
					req = 0;
			}
		}
	}

	/* pio or dma irq handler advances the queue. */
	if (likely(req != 0))
		list_add_tail(&req->queue, &ep->queue);

	spin_unlock_irqrestore(&dev->lock, flags);

	return 0;
}

/* dequeue JUST ONE request */
static int lh7a40x_dequeue(struct usb_ep *_ep, struct usb_request *_req)
{
	struct lh7a40x_ep *ep;
	struct lh7a40x_request *req;
	unsigned long flags;

	DEBUG("%s, %p\n", __func__, _ep);

	ep = container_of(_ep, struct lh7a40x_ep, ep);
	if (!_ep || ep->ep.name == ep0name)
		return -EINVAL;

	spin_lock_irqsave(&ep->dev->lock, flags);

	/* 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) {
		spin_unlock_irqrestore(&ep->dev->lock, flags);
		return -EINVAL;
	}

	done(ep, req, -ECONNRESET);

	spin_unlock_irqrestore(&ep->dev->lock, flags);
	return 0;
}

/** Halt specific EP
 *  Return 0 if success
 *  NOTE: Sets INDEX register to EP !
 */
static int lh7a40x_set_halt(struct usb_ep *_ep, int value)
{
	struct lh7a40x_ep *ep;
	unsigned long flags;

	ep = container_of(_ep, struct lh7a40x_ep, ep);
	if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) {
		DEBUG("%s, bad ep\n", __func__);
		return -EINVAL;
	}

	usb_set_index(ep_index(ep));

	DEBUG("%s, ep %d, val %d\n", __func__, ep_index(ep), value);

	spin_lock_irqsave(&ep->dev->lock, flags);

	if (ep_index(ep) == 0) {
		/* EP0 */
		usb_set(EP0_SEND_STALL, ep->csr1);
	} else if (ep_is_in(ep)) {
		u32 csr = usb_read(ep->csr1);
		if (value && ((csr & USB_IN_CSR1_FIFO_NOT_EMPTY)
			      || !list_empty(&ep->queue))) {
			/*
			 * Attempts to halt IN endpoints will fail (returning -EAGAIN)
			 * if any transfer requests are still queued, or if the controller
			 * FIFO still holds bytes that the host hasn't collected.
			 */
			spin_unlock_irqrestore(&ep->dev->lock, flags);
			DEBUG
			    ("Attempt to halt IN endpoint failed (returning -EAGAIN) %d %d\n",
			     (csr & USB_IN_CSR1_FIFO_NOT_EMPTY),
			     !list_empty(&ep->queue));
			return -EAGAIN;
		}
		flush(ep);
		if (value)
			usb_set(USB_IN_CSR1_SEND_STALL, ep->csr1);
		else {
			usb_clear(USB_IN_CSR1_SEND_STALL, ep->csr1);
			usb_set(USB_IN_CSR1_CLR_DATA_TOGGLE, ep->csr1);
		}

	} else {

		flush(ep);
		if (value)
			usb_set(USB_OUT_CSR1_SEND_STALL, ep->csr1);
		else {
			usb_clear(USB_OUT_CSR1_SEND_STALL, ep->csr1);
			usb_set(USB_OUT_CSR1_CLR_DATA_REG, ep->csr1);
		}
	}

	if (value) {
		ep->stopped = 1;
	} else {
		ep->stopped = 0;
	}

	spin_unlock_irqrestore(&ep->dev->lock, flags);

	DEBUG("%s %s halted\n", _ep->name, value == 0 ? "NOT" : "IS");

	return 0;
}

/** Return bytes in EP FIFO
 *  NOTE: Sets INDEX register to EP
 */
static int lh7a40x_fifo_status(struct usb_ep *_ep)
{
	u32 csr;
	int count = 0;
	struct lh7a40x_ep *ep;

	ep = container_of(_ep, struct lh7a40x_ep, ep);
	if (!_ep) {
		DEBUG("%s, bad ep\n", __func__);
		return -ENODEV;
	}

	DEBUG("%s, %d\n", __func__, ep_index(ep));

	/* LPD can't report unclaimed bytes from IN fifos */
	if (ep_is_in(ep))
		return -EOPNOTSUPP;

	usb_set_index(ep_index(ep));

	csr = usb_read(ep->csr1);
	if (ep->dev->gadget.speed != USB_SPEED_UNKNOWN ||
	    csr & USB_OUT_CSR1_OUT_PKT_RDY) {
		count = usb_read(USB_OUT_FIFO_WC1);
	}

	return count;
}

/** Flush EP FIFO
 *  NOTE: Sets INDEX register to EP
 */
static void lh7a40x_fifo_flush(struct usb_ep *_ep)
{
	struct lh7a40x_ep *ep;

	ep = container_of(_ep, struct lh7a40x_ep, ep);
	if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) {
		DEBUG("%s, bad ep\n", __func__);
		return;
	}

	usb_set_index(ep_index(ep));
	flush(ep);
}

/****************************************************************/
/* End Point 0 related functions                                */
/****************************************************************/

/* return:  0 = still running, 1 = completed, negative = errno */
static int write_fifo_ep0(struct lh7a40x_ep *ep, struct lh7a40x_request *req)
{
	u32 max;
	unsigned count;
	int is_last;

	max = ep_maxpacket(ep);

	DEBUG_EP0("%s\n", __func__);

	count = write_packet(ep, req, max);

	/* last packet is usually short (or a zlp) */
	if (unlikely(count != max))
		is_last = 1;
	else {
		if (likely(req->req.length != req->req.actual) || req->req.zero)
			is_last = 0;
		else
			is_last = 1;
	}

	DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __func__,
		  ep->ep.name, count,
		  is_last ? "/L" : "", req->req.length - req->req.actual, req);

	/* requests complete when all IN data is in the FIFO */
	if (is_last) {
		done(ep, req, 0);
		return 1;
	}

	return 0;
}

static __inline__ int lh7a40x_fifo_read(struct lh7a40x_ep *ep,
					unsigned char *cp, int max)
{
	int bytes;
	int count = usb_read(USB_OUT_FIFO_WC1);
	volatile u32 *fifo = (volatile u32 *)ep->fifo;

	if (count > max)
		count = max;
	bytes = count;
	while (count--)
		*cp++ = *fifo & 0xFF;
	return bytes;
}

static __inline__ void lh7a40x_fifo_write(struct lh7a40x_ep *ep,
					  unsigned char *cp, int count)
{
	volatile u32 *fifo = (volatile u32 *)ep->fifo;
	DEBUG_EP0("fifo_write: %d %d\n", ep_index(ep), count);
	while (count--)
		*fifo = *cp++;
}

static int read_fifo_ep0(struct lh7a40x_ep *ep, struct lh7a40x_request *req)
{
	u32 csr;
	u8 *buf;
	unsigned bufferspace, count, is_short;
	volatile u32 *fifo = (volatile u32 *)ep->fifo;

	DEBUG_EP0("%s\n", __func__);

	csr = usb_read(USB_EP0_CSR);
	if (!(csr & USB_OUT_CSR1_OUT_PKT_RDY))
		return 0;

	buf = req->req.buf + req->req.actual;
	prefetchw(buf);
	bufferspace = req->req.length - req->req.actual;

	/* read all bytes from this packet */
	if (likely(csr & EP0_OUT_PKT_RDY)) {
		count = usb_read(USB_OUT_FIFO_WC1);
		req->req.actual += min(count, bufferspace);
	} else			/* zlp */
		count = 0;

	is_short = (count < ep->ep.maxpacket);
	DEBUG_EP0("read %s %02x, %d bytes%s req %p %d/%d\n",
		  ep->ep.name, csr, count,
		  is_short ? "/S" : "", req, req->req.actual, req->req.length);

	while (likely(count-- != 0)) {
		u8 byte = (u8) (*fifo & 0xff);

		if (unlikely(bufferspace == 0)) {
			/* this happens when the driver's buffer
			 * is smaller than what the host sent.
			 * discard the extra data.
			 */
			if (req->req.status != -EOVERFLOW)
				DEBUG_EP0("%s overflow %d\n", ep->ep.name,
					  count);
			req->req.status = -EOVERFLOW;
		} else {
			*buf++ = byte;
			bufferspace--;
		}
	}

	/* completion */
	if (is_short || req->req.actual == req->req.length) {
		done(ep, req, 0);
		return 1;
	}

	/* finished that packet.  the next one may be waiting... */
	return 0;
}

/**
 * udc_set_address - set the USB address for this device
 * @address:
 *
 * Called from control endpoint function after it decodes a set address setup packet.
 */
static void udc_set_address(struct lh7a40x_udc *dev, unsigned char address)
{
	DEBUG_EP0("%s: %d\n", __func__, address);
	/* c.f. 15.1.2.2 Table 15-4 address will be used after DATA_END is set */
	dev->usb_address = address;
	usb_set((address & USB_FA_FUNCTION_ADDR), USB_FA);
	usb_set(USB_FA_ADDR_UPDATE | (address & USB_FA_FUNCTION_ADDR), USB_FA);
	/* usb_read(USB_FA); */
}

/*
 * DATA_STATE_RECV (OUT_PKT_RDY)
 *      - if error
 *              set EP0_CLR_OUT | EP0_DATA_END | EP0_SEND_STALL bits
 *      - else
 *              set EP0_CLR_OUT bit
 				if last set EP0_DATA_END bit
 */
static void lh7a40x_ep0_out(struct lh7a40x_udc *dev, u32 csr)
{
	struct lh7a40x_request *req;
	struct lh7a40x_ep *ep = &dev->ep[0];
	int ret;

	DEBUG_EP0("%s: %x\n", __func__, csr);

	if (list_empty(&ep->queue))
		req = 0;
	else
		req = list_entry(ep->queue.next, struct lh7a40x_request, queue);

	if (req) {

		if (req->req.length == 0) {
			DEBUG_EP0("ZERO LENGTH OUT!\n");
			usb_set((EP0_CLR_OUT | EP0_DATA_END), USB_EP0_CSR);
			dev->ep0state = WAIT_FOR_SETUP;
			return;
		}
		ret = read_fifo_ep0(ep, req);
		if (ret) {
			/* Done! */
			DEBUG_EP0("%s: finished, waiting for status\n",
				  __func__);

			usb_set((EP0_CLR_OUT | EP0_DATA_END), USB_EP0_CSR);
			dev->ep0state = WAIT_FOR_SETUP;
		} else {
			/* Not done yet.. */
			DEBUG_EP0("%s: not finished\n", __func__);
			usb_set(EP0_CLR_OUT, USB_EP0_CSR);
		}
	} else {
		DEBUG_EP0("NO REQ??!\n");
	}
}

/*
 * DATA_STATE_XMIT
 */
static int lh7a40x_ep0_in(struct lh7a40x_udc *dev, u32 csr)
{
	struct lh7a40x_request *req;
	struct lh7a40x_ep *ep = &dev->ep[0];
	int ret, need_zlp = 0;

	DEBUG_EP0("%s: %x\n", __func__, csr);

	if (list_empty(&ep->queue))
		req = 0;
	else
		req = list_entry(ep->queue.next, struct lh7a40x_request, queue);

	if (!req) {
		DEBUG_EP0("%s: NULL REQ\n", __func__);
		return 0;
	}

	if (req->req.length == 0) {

		usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR);
		dev->ep0state = WAIT_FOR_SETUP;
		return 1;
	}

	if (req->req.length - req->req.actual == EP0_PACKETSIZE) {
		/* Next write will end with the packet size, */
		/* so we need Zero-length-packet */
		need_zlp = 1;
	}

	ret = write_fifo_ep0(ep, req);

	if (ret == 1 && !need_zlp) {
		/* Last packet */
		DEBUG_EP0("%s: finished, waiting for status\n", __func__);

		usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR);
		dev->ep0state = WAIT_FOR_SETUP;
	} else {
		DEBUG_EP0("%s: not finished\n", __func__);
		usb_set(EP0_IN_PKT_RDY, USB_EP0_CSR);
	}

	if (need_zlp) {
		DEBUG_EP0("%s: Need ZLP!\n", __func__);
		usb_set(EP0_IN_PKT_RDY, USB_EP0_CSR);
		dev->ep0state = DATA_STATE_NEED_ZLP;
	}

	return 1;
}

static int lh7a40x_handle_get_status(struct lh7a40x_udc *dev,
				     struct usb_ctrlrequest *ctrl)
{
	struct lh7a40x_ep *ep0 = &dev->ep[0];
	struct lh7a40x_ep *qep;
	int reqtype = (ctrl->bRequestType & USB_RECIP_MASK);
	u16 val = 0;

	if (reqtype == USB_RECIP_INTERFACE) {
		/* This is not supported.
		 * And according to the USB spec, this one does nothing..
		 * Just return 0
		 */
		DEBUG_SETUP("GET_STATUS: USB_RECIP_INTERFACE\n");
	} else if (reqtype == USB_RECIP_DEVICE) {
		DEBUG_SETUP("GET_STATUS: USB_RECIP_DEVICE\n");
		val |= (1 << 0);	/* Self powered */
		/*val |= (1<<1); *//* Remote wakeup */
	} else if (reqtype == USB_RECIP_ENDPOINT) {
		int ep_num = (ctrl->wIndex & ~USB_DIR_IN);

		DEBUG_SETUP
		    ("GET_STATUS: USB_RECIP_ENDPOINT (%d), ctrl->wLength = %d\n",
		     ep_num, ctrl->wLength);

		if (ctrl->wLength > 2 || ep_num > 3)
			return -EOPNOTSUPP;

		qep = &dev->ep[ep_num];
		if (ep_is_in(qep) != ((ctrl->wIndex & USB_DIR_IN) ? 1 : 0)
		    && ep_index(qep) != 0) {
			return -EOPNOTSUPP;
		}

		usb_set_index(ep_index(qep));

		/* Return status on next IN token */
		switch (qep->ep_type) {
		case ep_control:
			val =
			    (usb_read(qep->csr1) & EP0_SEND_STALL) ==
			    EP0_SEND_STALL;
			break;
		case ep_bulk_in:
		case ep_interrupt:
			val =
			    (usb_read(qep->csr1) & USB_IN_CSR1_SEND_STALL) ==
			    USB_IN_CSR1_SEND_STALL;
			break;
		case ep_bulk_out:
			val =
			    (usb_read(qep->csr1) & USB_OUT_CSR1_SEND_STALL) ==
			    USB_OUT_CSR1_SEND_STALL;
			break;
		}

		/* Back to EP0 index */
		usb_set_index(0);

		DEBUG_SETUP("GET_STATUS, ep: %d (%x), val = %d\n", ep_num,
			    ctrl->wIndex, val);
	} else {
		DEBUG_SETUP("Unknown REQ TYPE: %d\n", reqtype);
		return -EOPNOTSUPP;
	}

	/* Clear "out packet ready" */
	usb_set((EP0_CLR_OUT), USB_EP0_CSR);
	/* Put status to FIFO */
	lh7a40x_fifo_write(ep0, (u8 *) & val, sizeof(val));
	/* Issue "In packet ready" */
	usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR);

	return 0;
}

/*
 * WAIT_FOR_SETUP (OUT_PKT_RDY)
 *      - read data packet from EP0 FIFO
 *      - decode command
 *      - if error
 *              set EP0_CLR_OUT | EP0_DATA_END | EP0_SEND_STALL bits
 *      - else
 *              set EP0_CLR_OUT | EP0_DATA_END bits
 */
static void lh7a40x_ep0_setup(struct lh7a40x_udc *dev, u32 csr)
{
	struct lh7a40x_ep *ep = &dev->ep[0];
	struct usb_ctrlrequest ctrl;
	int i, bytes, is_in;

	DEBUG_SETUP("%s: %x\n", __func__, csr);

	/* Nuke all previous transfers */
	nuke(ep, -EPROTO);

	/* read control req from fifo (8 bytes) */
	bytes = lh7a40x_fifo_read(ep, (unsigned char *)&ctrl, 8);

	DEBUG_SETUP("Read CTRL REQ %d bytes\n", bytes);
	DEBUG_SETUP("CTRL.bRequestType = %d (is_in %d)\n", ctrl.bRequestType,
		    ctrl.bRequestType == USB_DIR_IN);
	DEBUG_SETUP("CTRL.bRequest = %d\n", ctrl.bRequest);
	DEBUG_SETUP("CTRL.wLength = %d\n", ctrl.wLength);
	DEBUG_SETUP("CTRL.wValue = %d (%d)\n", ctrl.wValue, ctrl.wValue >> 8);
	DEBUG_SETUP("CTRL.wIndex = %d\n", ctrl.wIndex);

	/* Set direction of EP0 */
	if (likely(ctrl.bRequestType & USB_DIR_IN)) {
		ep->bEndpointAddress |= USB_DIR_IN;
		is_in = 1;
	} else {
		ep->bEndpointAddress &= ~USB_DIR_IN;
		is_in = 0;
	}

	dev->req_pending = 1;

	/* Handle some SETUP packets ourselves */
	switch (ctrl.bRequest) {
	case USB_REQ_SET_ADDRESS:
		if (ctrl.bRequestType != (USB_TYPE_STANDARD | USB_RECIP_DEVICE))
			break;

		DEBUG_SETUP("USB_REQ_SET_ADDRESS (%d)\n", ctrl.wValue);
		udc_set_address(dev, ctrl.wValue);
		usb_set((EP0_CLR_OUT | EP0_DATA_END), USB_EP0_CSR);
		return;

	case USB_REQ_GET_STATUS:{
			if (lh7a40x_handle_get_status(dev, &ctrl) == 0)
				return;

	case USB_REQ_CLEAR_FEATURE:
	case USB_REQ_SET_FEATURE:
			if (ctrl.bRequestType == USB_RECIP_ENDPOINT) {
				struct lh7a40x_ep *qep;
				int ep_num = (ctrl.wIndex & 0x0f);

				/* Support only HALT feature */
				if (ctrl.wValue != 0 || ctrl.wLength != 0
				    || ep_num > 3 || ep_num < 1)
					break;

				qep = &dev->ep[ep_num];
				spin_unlock(&dev->lock);
				if (ctrl.bRequest == USB_REQ_SET_FEATURE) {
					DEBUG_SETUP("SET_FEATURE (%d)\n",
						    ep_num);
					lh7a40x_set_halt(&qep->ep, 1);
				} else {
					DEBUG_SETUP("CLR_FEATURE (%d)\n",
						    ep_num);
					lh7a40x_set_halt(&qep->ep, 0);
				}
				spin_lock(&dev->lock);
				usb_set_index(0);

				/* Reply with a ZLP on next IN token */
				usb_set((EP0_CLR_OUT | EP0_DATA_END),
					USB_EP0_CSR);
				return;
			}
			break;
		}

	default:
		break;
	}

	if (likely(dev->driver)) {
		/* device-2-host (IN) or no data setup command, process immediately */
		spin_unlock(&dev->lock);
		i = dev->driver->setup(&dev->gadget, &ctrl);
		spin_lock(&dev->lock);

		if (i < 0) {
			/* setup processing failed, force stall */
			DEBUG_SETUP
			    ("  --> ERROR: gadget setup FAILED (stalling), setup returned %d\n",
			     i);
			usb_set_index(0);
			usb_set((EP0_CLR_OUT | EP0_DATA_END | EP0_SEND_STALL),
				USB_EP0_CSR);

			/* ep->stopped = 1; */
			dev->ep0state = WAIT_FOR_SETUP;
		}
	}
}

/*
 * DATA_STATE_NEED_ZLP
 */
static void lh7a40x_ep0_in_zlp(struct lh7a40x_udc *dev, u32 csr)
{
	DEBUG_EP0("%s: %x\n", __func__, csr);

	/* c.f. Table 15-14 */
	usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR);
	dev->ep0state = WAIT_FOR_SETUP;
}

/*
 * handle ep0 interrupt
 */
static void lh7a40x_handle_ep0(struct lh7a40x_udc *dev, u32 intr)
{
	struct lh7a40x_ep *ep = &dev->ep[0];
	u32 csr;

	/* Set index 0 */
	usb_set_index(0);
 	csr = usb_read(USB_EP0_CSR);

	DEBUG_EP0("%s: csr = %x\n", __func__, csr);

	/*
	 * For overview of what we should be doing see c.f. Chapter 18.1.2.4
	 * We will follow that outline here modified by our own global state
	 * indication which provides hints as to what we think should be
	 * happening..
	 */

	/*
	 * if SENT_STALL is set
	 *      - clear the SENT_STALL bit
	 */
	if (csr & EP0_SENT_STALL) {
		DEBUG_EP0("%s: EP0_SENT_STALL is set: %x\n", __func__, csr);
		usb_clear((EP0_SENT_STALL | EP0_SEND_STALL), USB_EP0_CSR);
		nuke(ep, -ECONNABORTED);
		dev->ep0state = WAIT_FOR_SETUP;
		return;
	}

	/*
	 * if a transfer is in progress && IN_PKT_RDY and OUT_PKT_RDY are clear
	 *      - fill EP0 FIFO
	 *      - if last packet
	 *      -       set IN_PKT_RDY | DATA_END
	 *      - else
	 *              set IN_PKT_RDY
	 */
	if (!(csr & (EP0_IN_PKT_RDY | EP0_OUT_PKT_RDY))) {
		DEBUG_EP0("%s: IN_PKT_RDY and OUT_PKT_RDY are clear\n",
			  __func__);

		switch (dev->ep0state) {
		case DATA_STATE_XMIT:
			DEBUG_EP0("continue with DATA_STATE_XMIT\n");
			lh7a40x_ep0_in(dev, csr);
			return;
		case DATA_STATE_NEED_ZLP:
			DEBUG_EP0("continue with DATA_STATE_NEED_ZLP\n");
			lh7a40x_ep0_in_zlp(dev, csr);
			return;
		default:
			/* Stall? */
			DEBUG_EP0("Odd state!! state = %s\n",
				  state_names[dev->ep0state]);
			dev->ep0state = WAIT_FOR_SETUP;
			/* nuke(ep, 0); */
			/* usb_set(EP0_SEND_STALL, ep->csr1); */
			break;
		}
	}

	/*
	 * if SETUP_END is set
	 *      - abort the last transfer
	 *      - set SERVICED_SETUP_END_BIT
	 */
	if (csr & EP0_SETUP_END) {
		DEBUG_EP0("%s: EP0_SETUP_END is set: %x\n", __func__, csr);

		usb_set(EP0_CLR_SETUP_END, USB_EP0_CSR);

		nuke(ep, 0);
		dev->ep0state = WAIT_FOR_SETUP;
	}

	/*
	 * if EP0_OUT_PKT_RDY is set
	 *      - read data packet from EP0 FIFO
	 *      - decode command
	 *      - if error
	 *              set SERVICED_OUT_PKT_RDY | DATA_END bits | SEND_STALL
	 *      - else
	 *              set SERVICED_OUT_PKT_RDY | DATA_END bits
	 */
	if (csr & EP0_OUT_PKT_RDY) {

		DEBUG_EP0("%s: EP0_OUT_PKT_RDY is set: %x\n", __func__,
			  csr);

		switch (dev->ep0state) {
		case WAIT_FOR_SETUP:
			DEBUG_EP0("WAIT_FOR_SETUP\n");
			lh7a40x_ep0_setup(dev, csr);
			break;

		case DATA_STATE_RECV:
			DEBUG_EP0("DATA_STATE_RECV\n");
			lh7a40x_ep0_out(dev, csr);
			break;

		default:
			/* send stall? */
			DEBUG_EP0("strange state!! 2. send stall? state = %d\n",
				  dev->ep0state);
			break;
		}
	}
}

static void lh7a40x_ep0_kick(struct lh7a40x_udc *dev, struct lh7a40x_ep *ep)
{
	u32 csr;

	usb_set_index(0);
	csr = usb_read(USB_EP0_CSR);

	DEBUG_EP0("%s: %x\n", __func__, csr);

	/* Clear "out packet ready" */
	usb_set(EP0_CLR_OUT, USB_EP0_CSR);

	if (ep_is_in(ep)) {
		dev->ep0state = DATA_STATE_XMIT;
		lh7a40x_ep0_in(dev, csr);
	} else {
		dev->ep0state = DATA_STATE_RECV;
		lh7a40x_ep0_out(dev, csr);
	}
}

/* ---------------------------------------------------------------------------
 * 	device-scoped parts of the api to the usb controller hardware
 * ---------------------------------------------------------------------------
 */

static int lh7a40x_udc_get_frame(struct usb_gadget *_gadget)
{
	u32 frame1 = usb_read(USB_FRM_NUM1);	/* Least significant 8 bits */
	u32 frame2 = usb_read(USB_FRM_NUM2);	/* Most significant 3 bits */
	DEBUG("%s, %p\n", __func__, _gadget);
	return ((frame2 & 0x07) << 8) | (frame1 & 0xff);
}

static int lh7a40x_udc_wakeup(struct usb_gadget *_gadget)
{
	/* host may not have enabled remote wakeup */
	/*if ((UDCCS0 & UDCCS0_DRWF) == 0)
	   return -EHOSTUNREACH;
	   udc_set_mask_UDCCR(UDCCR_RSM); */
	return -ENOTSUPP;
}

static const struct usb_gadget_ops lh7a40x_udc_ops = {
	.get_frame = lh7a40x_udc_get_frame,
	.wakeup = lh7a40x_udc_wakeup,
	/* current versions must always be self-powered */
};

static void nop_release(struct device *dev)
{
	DEBUG("%s %s\n", __func__, dev_name(dev));
}

static struct lh7a40x_udc memory = {
	.usb_address = 0,

	.gadget = {
		   .ops = &lh7a40x_udc_ops,
		   .ep0 = &memory.ep[0].ep,
		   .name = driver_name,
		   .dev = {
			   .init_name = "gadget",
			   .release = nop_release,
			   },
		   },

	/* control endpoint */
	.ep[0] = {
		  .ep = {
			 .name = ep0name,
			 .ops = &lh7a40x_ep_ops,
			 .maxpacket = EP0_PACKETSIZE,
			 },
		  .dev = &memory,

		  .bEndpointAddress = 0,
		  .bmAttributes = 0,

		  .ep_type = ep_control,
		  .fifo = io_p2v(USB_EP0_FIFO),
		  .csr1 = USB_EP0_CSR,
		  .csr2 = USB_EP0_CSR,
		  },

	/* first group of endpoints */
	.ep[1] = {
		  .ep = {
			 .name = "ep1in-bulk",
			 .ops = &lh7a40x_ep_ops,
			 .maxpacket = 64,
			 },
		  .dev = &memory,

		  .bEndpointAddress = USB_DIR_IN | 1,
		  .bmAttributes = USB_ENDPOINT_XFER_BULK,

		  .ep_type = ep_bulk_in,
		  .fifo = io_p2v(USB_EP1_FIFO),
		  .csr1 = USB_IN_CSR1,
		  .csr2 = USB_IN_CSR2,
		  },

	.ep[2] = {
		  .ep = {
			 .name = "ep2out-bulk",
			 .ops = &lh7a40x_ep_ops,
			 .maxpacket = 64,
			 },
		  .dev = &memory,

		  .bEndpointAddress = 2,
		  .bmAttributes = USB_ENDPOINT_XFER_BULK,

		  .ep_type = ep_bulk_out,
		  .fifo = io_p2v(USB_EP2_FIFO),
		  .csr1 = USB_OUT_CSR1,
		  .csr2 = USB_OUT_CSR2,
		  },

	.ep[3] = {
		  .ep = {
			 .name = "ep3in-int",
			 .ops = &lh7a40x_ep_ops,
			 .maxpacket = 64,
			 },
		  .dev = &memory,

		  .bEndpointAddress = USB_DIR_IN | 3,
		  .bmAttributes = USB_ENDPOINT_XFER_INT,

		  .ep_type = ep_interrupt,
		  .fifo = io_p2v(USB_EP3_FIFO),
		  .csr1 = USB_IN_CSR1,
		  .csr2 = USB_IN_CSR2,
		  },
};

/*
 * 	probe - binds to the platform device
 */
static int lh7a40x_udc_probe(struct platform_device *pdev)
{
	struct lh7a40x_udc *dev = &memory;
	int retval;

	DEBUG("%s: %p\n", __func__, pdev);

	spin_lock_init(&dev->lock);
	dev->dev = &pdev->dev;

	device_initialize(&dev->gadget.dev);
	dev->gadget.dev.parent = &pdev->dev;

	the_controller = dev;
	platform_set_drvdata(pdev, dev);

	udc_disable(dev);
	udc_reinit(dev);

	/* irq setup after old hardware state is cleaned up */
	retval =
	    request_irq(IRQ_USBINTR, lh7a40x_udc_irq, IRQF_DISABLED, driver_name,
			dev);
	if (retval != 0) {
		DEBUG(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name,
		      IRQ_USBINTR, retval);
		return -EBUSY;
	}

	create_proc_files();

	return retval;
}

static int lh7a40x_udc_remove(struct platform_device *pdev)
{
	struct lh7a40x_udc *dev = platform_get_drvdata(pdev);

	DEBUG("%s: %p\n", __func__, pdev);

	if (dev->driver)
		return -EBUSY;

	udc_disable(dev);
	remove_proc_files();

	free_irq(IRQ_USBINTR, dev);

	platform_set_drvdata(pdev, 0);

	the_controller = 0;

	return 0;
}

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

static struct platform_driver udc_driver = {
	.probe = lh7a40x_udc_probe,
	.remove = lh7a40x_udc_remove,
	    /* FIXME power management support */
	    /* .suspend = ... disable UDC */
	    /* .resume = ... re-enable UDC */
	.driver	= {
		.name = (char *)driver_name,
		.owner = THIS_MODULE,
	},
};

static int __init udc_init(void)
{
	DEBUG("%s: %s version %s\n", __func__, driver_name, DRIVER_VERSION);
	return platform_driver_register(&udc_driver);
}

static void __exit udc_exit(void)
{
	platform_driver_unregister(&udc_driver);
}

module_init(udc_init);
module_exit(udc_exit);

MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR("Mikko Lahteenmaki, Bo Henriksen");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lh7a40x_udc");