/*
 * linux/drivers/usb/device/bi/pxa.c -- Xscale USB Device Controller driver. 
 *
 * Copyright (c) 2000, 2001, 2002 Lineo
 *
 * By: 
 *      Stuart Lynne <sl@lineo.com>, 
 *      Tom Rushworth <tbr@lineo.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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


/*
 * Testing notes
 *
 * This code was developed on the Intel Lubbock with Cotulla PXA250 processor.
 *
 * The default 
 *
 */

/*****************************************************************************/

#include <linux/config.h>
#include <linux/module.h>

#include "../usbd-export.h"
#include "../usbd-build.h"
#include "../usbd-module.h"

MODULE_AUTHOR ("sl@lineo.com, tbr@lineo.com");
MODULE_DESCRIPTION ("Xscale USB Device Bus Interface");

USBD_MODULE_INFO ("pxa_bi 0.1-alpha");

#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/init.h>

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

#include <linux/netdevice.h>

#include <asm/irq.h>
#include <asm/system.h>

#include <asm/types.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <asm/hardware.h>
#include <asm/dma.h>

#include "../usbd.h"
#include "../usbd-func.h"
#include "../usbd-bus.h"
#include "../usbd-inline.h"
#include "usbd-bi.h"

#include "pxa.h"

static int udc_suspended;
static struct usb_device_instance *udc_device;	// required for the interrupt handler

/*
 * ep_endpoints - map physical endpoints to logical endpoints
 */
static struct usb_endpoint_instance *ep_endpoints[UDC_MAX_ENDPOINTS];

static struct urb *ep0_urb;

extern unsigned int udc_interrupts;

int jifs(void)
{
        static unsigned long jiffies_last;
        int elapsed = jiffies - jiffies_last;

        jiffies_last = jiffies;
        return elapsed;
}


/* ********************************************************************************************* */
/* IO
 */

/*
 * Map logical to physical
 */

typedef enum ep {
        ep_control, ep_bulk_in, ep_bulk_out, ep_iso_in, ep_iso_out, ep_interrupt
} ep_t;

struct ep_map {
        int             logical;
        ep_t            eptype;
        int             size;
        int             dma_chan;
};

#ifdef USE_DMA

int pxa_tx_dma_chan = -1;
int pxa_rx_dma_chan = -1;

pxa_dma_desc *pxa_tx_dma_desc[];
pxa_dma_desc *pxa_rx_dma_desc[];

#endif


/*
 * PXA has lots of endpoints, but they have fixed address and type
 * so logical to physical map is limited to masking top bits so we
 * can find appropriate info.
 */


u32 _UDDRN[16] = {
        0x40600080, 0x40600100, 0x40600180, 0x40600200,
        0x40600400, 0x406000A0, 0x40600600, 0x40600680,
        0x40600700, 0x40600900, 0x406000C0, 0x40600B00, 
        0x40600B80, 0x40600C00, 0x40600E00, 0x406000E0, 
};

u32 _UBCRN[16] = {
        0,          0,          0x40600068, 0,
        0x4060006c, 0,          0,          0x40600070,
        0,          0x40600074, 0,          0,
        0x40600078, 0,          0x4060007c, 0, 
};


#define UDCCSN(x)        __REG2(0x40600010, (x) << 2)

#define UDDRN(x)        __REG(_UDDRN[x])
#define UBCRN(x)        __REG(_UBCRN[x])


static struct ep_map ep_maps[16] = {
        { logical: 0, eptype: ep_control,   size: 16, },
        { logical: 1, eptype: ep_bulk_in,   size: 64, },

        { logical: 2, eptype: ep_bulk_out,  size: 64, },

        { logical: 3, eptype: ep_iso_in,   size: 256, },
        { logical: 4, eptype: ep_iso_out,  size: 256, },

        { logical: 5, eptype: ep_interrupt , size: 8, },

        { logical: 6, eptype: ep_bulk_in,   size: 64, },
        { logical: 7, eptype: ep_bulk_out,  size: 64, },
                                                      
        { logical: 8, eptype: ep_iso_in,   size: 256, },
        { logical: 9, eptype: ep_iso_out,  size: 256, },
                                                      
        { logical: 10, eptype: ep_interrupt, size: 8, },
                                                      
        { logical: 11, eptype: ep_bulk_in,  size: 64, },
        { logical: 12, eptype: ep_bulk_out, size: 64, },
                                                      
        { logical: 13, eptype: ep_iso_in,  size: 256, },
        { logical: 14, eptype: ep_iso_out, size: 256, },
                                                      
        { logical: 15, eptype: ep_interrupt, size: 8, },
};

static __inline__ void pxa_enable_ep_interrupt(int ep)
{
        ep &= 0xf;
        if (ep < 8) {
                UICR0 &= ~(1<<ep);
        }
        else {
                UICR1 &= ~(1<<(ep-7));
        }
}

static __inline__ void pxa_disable_ep_interrupt(int ep)
{
        ep &= 0xf;
        if (ep < 8) {
                UICR0 = 1<<ep;
        }
        else {
                UICR1 = 1<<(ep-7);
        }
}

static __inline__ void pxa_ep_reset_irs(int ep)
{
        ep &= 0xf;
        if (ep < 8) {
                USIR0 = (1 << ep);
        }
        else {
                USIR1 = (1 << (ep - 7));
        }
}


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

static void pxa_out_flush(int ep) {
        unsigned char c;
        while (UDCCSN(ep) & UDCCS_BO_RNE) {
                c = UDDRN(ep);
        }
}

#ifndef USE_DMA
static __inline__ void pxa_out_n(int ep, struct usb_endpoint_instance *endpoint)
{
        if (UDCCSN(ep) & UDCCS_BO_RPC) {

                if (endpoint) {
                        if (!endpoint->rcv_urb) {
                                endpoint->rcv_urb = first_urb_detached (&endpoint->rdy);
                        }
                        if (endpoint->rcv_urb) {

                                int len = 0;
                                unsigned char *cp = endpoint->rcv_urb->buffer + endpoint->rcv_urb->actual_length;

                                if (cp) {
                                        // read available bytes (max packetsize) into urb buffer
                                        int count = MIN(UBCRN(ep) + 1, endpoint->rcv_packetSize);
                                        while (count--) {
                                                len++;
                                                *cp++ = UDDRN(ep);
                                        }
                                }
                                else {
                                        printk(KERN_INFO"read[%d:%d] bad arguements\n", udc_interrupts, jifs());
                                        pxa_out_flush(ep);
                                }
                                // fall through if error of any type, len = 0
                                usbd_rcv_complete_irq (endpoint, len, 0);
                        }
                        else {
                                pxa_out_flush(ep);
                        }
                }
        }
        // clear RPC and interrupt
        UDCCSN(ep) = UDCCS_BO_RPC;
        pxa_ep_reset_irs(ep);
}
#else
#endif

#ifdef USE_DMA
static void pxa_usb_rx_dma_irq(int ch, void *dev_id, struct pt_regs *regs)
{
        u_int dcsr;

        dcsr = DCSR(ch);
        DCSR(ch) = dcsr & ~DCSR_STOPIRQEN;

}
#endif

/* ********************************************************************************************* */
/* Bulk IN (tx)
 */

static void __inline__ pxa_start_n (unsigned int ep, struct usb_endpoint_instance *endpoint)
{
        if (endpoint->tx_urb) {
                int last;
                struct urb *urb = endpoint->tx_urb;

                if (( last = MIN (urb->actual_length - (endpoint->sent + endpoint->last), endpoint->tx_packetSize))) 
                {
                        int size = last;
                        unsigned char *cp = urb->buffer + endpoint->sent + endpoint->last;

                        while (size--) {
                                UDDRN(ep) = *cp++;
                        }

                        if (( last < endpoint->tx_packetSize ) ||
                                        ( (endpoint->tx_urb->actual_length - endpoint->sent ) == last )) 
                        {
                                UDCCSN(ep) = UDCCS_BI_TSP;
                        }
                        endpoint->last += last;
                }
        }
}

static void __inline__ pxa_in_n (unsigned int ep, struct usb_endpoint_instance *endpoint)
{
        int udccsn;

        pxa_ep_reset_irs(ep);

        // if TPC update tx urb and clear TPC
        if ((udccsn = UDCCSN(ep)) & UDCCS_BI_TPC) {

                UDCCSN(ep) = UDCCS_BI_TPC;
                usbd_tx_complete_irq(endpoint, 0);
        }

        if (udccsn & UDCCS_BI_TFS) {
                pxa_start_n(ep, endpoint);
        }

        // clear underrun, not much we can do about it
        if (udccsn & UDCCS_BI_TUR) {
                UDCCSN(ep) = UDCCS_BI_TUR;
        }
}

#ifdef USE_DMA
static void pxa_usb_tx_dma_irq(int ch, void *dev_id, struct pt_regs *regs)
{

}
#endif


/* ********************************************************************************************* */
/* Control (endpoint zero)
 */

#define EP0_MAX 10000000

#if 0
static void __inline__ pxa_in_0 (unsigned int ep, struct usb_endpoint_instance *endpoint, int restart)
{
	if (endpoint->tx_urb) {
		struct urb *urb = endpoint->tx_urb;

		if (( endpoint->last = MIN (urb->actual_length - endpoint->sent, endpoint->tx_packetSize))) {

                        unsigned char *cp = urb->buffer + endpoint->sent;
                        int size = endpoint->last;

                        if (cp) {
                                while (size--) {
                                        UDDRN(ep) = *cp++;
                                }
                        }
                        
                        // XXX XXX needed, timing, !!!???
                        udelay(10);
                        // XXX |= also seems to work here instead of udelay(10) above
                        
                        UDCCS0 = UDCCS0_IPR;

                        // XXX XXX needed, timing, !!!???
                        printk(KERN_INFO"in0[%d:%d] sending: %d\n", udc_interrupts, jifs(), endpoint->last);
                        udelay(60);
                }
        }
}
#endif

void pxa_ep0xmit(struct usb_endpoint_instance *endpoint)
{
	int short_packet;
	int size;
	struct urb *urb = endpoint->tx_urb;
        //printk(KERN_INFO"tx[%d:%d] CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);

        // check for premature status stage - host abandoned previous IN
        if ((UDCCS0 & UDCCS0_OPR) && !(UDCCS0 & UDCCS0_SA) ) {

                // clear tx fifo and opr
                //UDCCS0 |= UDCCS0_FTF;
                //UDCCS0 |= UDCCS0_OPR;
                UDCCS0 = UDCCS0_FTF | UDCCS0_OPR;
                //printk(KERN_INFO"tx[%d:%d]: clear FTF OPR CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);
                endpoint->state = WAIT_FOR_SETUP;
                endpoint->tx_urb = NULL;
                return;
        }

        // check for stall
        if (UDCCS0 & UDCCS0_SST) {
                // clear stall and tx fifo
                //UDCCS0 |= UDCCS0_SST;
                //UDCCS0 |= UDCCS0_FTF;
                UDCCS0 = UDCCS0_SST | UDCCS0_FTF;
                //printk(KERN_INFO"tx[%d:%d]: clear FTF SST CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);
                endpoint->state = WAIT_FOR_SETUP;
                endpoint->tx_urb = NULL;
                return;
        }

	/* How much are we sending this time? (May be zero!)
           (Note that later call of tx_complete() will add last to sent.) */
	if (NULL == urb) {
		size = 0;
	} else {
		endpoint->last = size = MIN (urb->actual_length - endpoint->sent, endpoint->tx_packetSize);
	}

	/* Will this be a short packet?
          (It may be the last, but still be full size, in which case we will need a ZLP later.) */
        short_packet = (size < endpoint->tx_packetSize);

	if (size > 0 && urb->buffer) {
		// Stuff the FIFO
                unsigned char *cp = urb->buffer + endpoint->sent;

		while (size--) {
			UDDRN(0) = *cp++;
		}
        }

        // Is this the end of the data state? (We've sent all the data, plus any required ZLP.)
        if (!endpoint->tx_urb || (endpoint->last < endpoint->tx_packetSize) || 
                        ((endpoint->last + endpoint->sent) == endpoint->tx_urb->actual_length)) 
	{
		// Tell the UDC we are at the end of the packet.
		UDCCS0 = UDCCS0_IPR;
                endpoint->state = WAIT_FOR_OUT_STATUS;
	}
		
        //printk(KERN_INFO"tx[%d:%d] CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);

        //if (!endpoint->tx_urb) {
        //        endpoint->state = WAIT_FOR_OUT_STATUS;
        //}

        // XXX check if necessary
        // UDCCS0 = UDCCS0_OPR;

        //printk(KERN_INFO"tx[%d:%d]: OPR CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);

}

void pxa_ep0setup(struct usb_endpoint_instance *endpoint)
{
        if ((UDCCS0 & (UDCCS0_SA | UDCCS0_OPR | UDCCS0_RNE)) == (UDCCS0_SA | UDCCS0_OPR | UDCCS0_RNE)) {

                int len = 0;
                int max = 8;
                unsigned char *cp = (unsigned char *)&ep0_urb->device_request;

                memset(cp, 0, max);

                while (max-- && (UDCCS0 & UDCCS0_RNE)) {
                        len++;
                        *cp++ = UDDR0;
                }

                // process setup packet
                if (usbd_recv_setup(ep0_urb)) {
                        // setup processing failed 
                        UDCCS0 = UDCCS0_FST;
                        return;
                }

                // check direction
                if ((ep0_urb->device_request.bmRequestType & USB_REQ_DIRECTION_MASK) == 
                                USB_REQ_HOST2DEVICE) 
                {
                        // Control Write - we are receiving more data from the host

                        // should we setup to receive data
                        if (le16_to_cpu (ep0_urb->device_request.wLength)) {
                                //printk(KERN_INFO"sl11_ep0: read data %d\n",
                                //      le16_to_cpu(ep0_urb->device_request.wLength));
                                endpoint->rcv_urb = ep0_urb;
                                endpoint->rcv_urb->actual_length = 0;
                                // XXX this has not been tested
                                // pxa_out_0 (0, endpoint);
                                return;
                        }

                        // allow for premature IN (c.f. 12.5.3 #8)
                        UDCCS0 = UDCCS0_IPR;

                        // should be finished, send ack
                        return;

                }
                else {
                        // Control Read - we are sending data to the host

                        // verify that we have non-zero request length
                        if (!le16_to_cpu (ep0_urb->device_request.wLength)) {
                                udc_stall_ep (0);
                                return;
                        }
                        // verify that we have non-zero length response
                        if (!ep0_urb->actual_length) {
                                udc_stall_ep (0);
                                return;
                        }


                        // start sending
                        endpoint->tx_urb = ep0_urb;
                        endpoint->sent = 0;
                        endpoint->last = 0;
                        endpoint->state = DATA_STATE_XMIT;
			pxa_ep0xmit(endpoint);

                        // Clear SA and OPR bits
                        UDCCS0 = UDCCS0_SA | UDCCS0_OPR;
                }
        }
}

void pxa_ep0outstatus(struct usb_endpoint_instance *endpoint)
{
        // make sure that OPR is set and SA and RNE(XXX?) are not set

        
        //printk(KERN_INFO"sts[%d:%d] CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);
        if ((UDCCS0 & (UDCCS0_OPR | UDCCS0_SA)) == UDCCS0_OPR) {
                // Out status received, reset OPR
                //printk(KERN_INFO"out0[%d:%d] CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);
                UDCCS0 |= UDCCS0_OPR;
                endpoint->state = WAIT_FOR_SETUP;
        }
        else {
                // missed status
                // XXX stall?
                endpoint->state = WAIT_FOR_SETUP;
        }
}

static void pxa_ep0(struct usb_endpoint_instance *endpoint)
{
        int j = 0;
        int retry;

        if (!endpoint) {
                printk(KERN_INFO"ep0[%d:%d] endpoint: %p is NULL\n", udc_interrupts, jifs(), endpoint);
                return;
        }

        //printk(KERN_INFO"ep0[%d:%d] CS0[%02x]\n", udc_interrupts, jifs(), UDCCS0);
        do {

                retry = 0;

                if (endpoint->tx_urb) {
                        usbd_tx_complete_irq (endpoint, 0);
                        if (!endpoint->tx_urb) {
                                endpoint->state = WAIT_FOR_SETUP;
                        }
                }

                if (UDCCS0 & UDCCS0_SA) {
                        endpoint->state = WAIT_FOR_SETUP;
                        endpoint->tx_urb = NULL;
                }

                
                switch (endpoint->state) {

                case WAIT_FOR_SETUP:
                        pxa_ep0setup(endpoint);
                        break;

                case DATA_STATE_XMIT:
                        pxa_ep0xmit(endpoint);
                        break;

                case WAIT_FOR_OUT_STATUS:
                        pxa_ep0outstatus(endpoint);
                        break;
                }

                pxa_ep_reset_irs(0);

                if (j++ > 1000) {
                        if ((UDCCS0 & (UDCCS0_OPR | UDCCS0_SA | UDCCS0_RNE)) == (UDCCS0_OPR | UDCCS0_SA)) {
                                UDCCS0 = UDCCS0_OPR | UDCCS0_SA;
                                //printk(KERN_INFO"ep0[%d:%d] CS0[%02x] force OPR and SA\n", udc_interrupts, jifs(), UDCCS0);
                        }
                        else {
                                UDCCS0 = UDCCS0_OPR | UDCCS0_SA;
                                //printk(KERN_INFO"ep0[%d:%d] CS0[%02x] force OPR and SA and return\n", udc_interrupts, jifs(), UDCCS0);
                                return;
                        }
                }

                if (UDCCS0 & UDCCS0_OPR) {
                }
                if (UDCCS0 & UDCCS0_RNE) {
                }

        } while (UDCCS0 & (UDCCS0_OPR | UDCCS0_RNE));

}

/* ********************************************************************************************* */
/* Interrupt Handler
 */

static void pregs(void) {
        printk(KERN_INFO
                        "int[%d:%d] USIR[%02x %02x] "
                        "CCR[%02x] UICR[%02x %02x] UFNHR[%02x %02x] CS0[%02x]\n", 
                        udc_interrupts, jifs(), USIR1, USIR0, 
                        UDCCR, UICR1, UICR0, UFNHR, UFNLR, UDCCS0);
}

/**
 * int_hndlr - interrupt handler
 *
 */
static void int_hndlr (int irq, void *dev_id, struct pt_regs *regs)
{
        int usiro;
        int udccr;
        int ep;

	udc_interrupts++;

        // check for common, high priority interrupts first, i.e. per endpoint service requests
        // XXX if ISO supported it might be necessary to give the ISO endpoints priority
        
        while ((usiro = USIR0)) {
                for (ep = 0; usiro; usiro >>= 1, ep++) {
                        if (usiro & 1) {
                                switch (ep_maps[ep].eptype) {
                                case ep_control:
                                        pxa_ep0(ep_endpoints[0]);
                                        return;
                                        break;

                                case ep_bulk_in:
                                case ep_interrupt:
                                        pxa_in_n(ep, ep_endpoints[ep]);
                                        break;

                                case ep_bulk_out:
#ifndef USE_DMA
                                        pxa_out_n(ep, ep_endpoints[ep]);
                                        break;
#endif
                                case ep_iso_in:
                                case ep_iso_out:
                                        pxa_ep_reset_irs(ep);
                                        break;
                                }
                        }
                }
        }

        // sof interrupt
        if (UFNHR & UFNHR_SIR) {
                UFNHR = UFNHR_SIR;
        }

        // uncommon interrupts

        if ((udccr = UDCCR) & (UDCCR_RSTIR | UDCCR_RESIR | UDCCR_SUSIR)) {

                printk(KERN_INFO"int_hndlr[%d:%d] UDCCR: %2x\n", udc_interrupts, jifs(), UDCCR & (UDCCR_RSTIR | UDCCR_RESIR | UDCCR_SUSIR));

                // UDC Reset
                if (udccr & UDCCR_RSTIR) {
                        printk(KERN_INFO"int_hndlr[%d:%d] Reset\n", udc_interrupts, jifs());
                                        
                        udc_suspended = 0;
                        usbd_device_event (udc_device, DEVICE_RESET, 0);
                        usbd_device_event (udc_device, DEVICE_ADDRESS_ASSIGNED, 0);	
                        UDCCR |= UDCCR_RSTIR;
                }

                // UDC Resume
                if (udccr & UDCCR_RESIR) {
                        if (udc_suspended) {
                                udc_suspended = 0;
                                usbd_device_event (udc_device, DEVICE_BUS_ACTIVITY, 0);
                        }
                        UDCCR |= UDCCR_RESIR;
                }

                // UDC Suspend
                if (udccr & UDCCR_SUSIR) {
                        if (!udc_suspended) {
                                udc_suspended = 1;
                                usbd_device_event (udc_device, DEVICE_BUS_INACTIVE, 0);
                        }
                        UDCCR |= UDCCR_SUSIR;
                }
        }
}


/* ********************************************************************************************* */


/* ********************************************************************************************* */
/*
 * Start of public functions.
 */

/**
 * udc_start_in_irq - start transmit
 * @eendpoint: endpoint instance
 *
 * Called by bus interface driver to see if we need to start a data transmission.
 */
void udc_start_in_irq (struct usb_endpoint_instance *endpoint)
{
        if (UDCCSN(endpoint->endpoint_address & 0xf) & UDCCS_BI_TFS) {
                pxa_start_n (endpoint->endpoint_address & 0xf, endpoint);
        }
}

/**
 * udc_init - initialize
 *
 * Return non-zero if we cannot see device.
 **/
int udc_init (void)
{
	return 0;
}


/**
 * udc_start_in - start transmit
 * @eendpoint: endpoint instance
 *
 * Called by bus interface driver to see if we need to start a data transmission.
 */
void udc_start_in (struct usb_endpoint_instance *endpoint)
{
	if (endpoint) {
		unsigned long flags;
		local_irq_save (flags);
                udc_start_in_irq(endpoint);
		local_irq_restore (flags);
	}
}


/**
 * udc_stall_ep - stall endpoint
 * @ep: physical endpoint
 *
 * Stall the endpoint.
 */
void udc_stall_ep (unsigned int ep)
{
	if (ep < UDC_MAX_ENDPOINTS) {
		// stall
	}
}


/**
 * udc_reset_ep - reset endpoint
 * @ep: physical endpoint
 * reset the endpoint.
 *
 * returns : 0 if ok, -1 otherwise
 */
void udc_reset_ep (unsigned int ep)
{
	if (ep < UDC_MAX_ENDPOINTS) {


	}
}


/**
 * udc_endpoint_halted - is endpoint halted
 * @ep:
 *
 * Return non-zero if endpoint is halted
 */
int udc_endpoint_halted (unsigned int ep)
{
	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.
 */
void udc_set_address (unsigned char address)
{
}

/**
 * udc_serial_init - set a serial number if available
 */
int __init udc_serial_init (struct usb_bus_instance *bus)
{
	return -EINVAL;
}

/* ********************************************************************************************* */

/**
 * udc_max_endpoints - max physical endpoints 
 *
 * Return number of physical endpoints.
 */
int udc_max_endpoints (void)
{
	return UDC_MAX_ENDPOINTS;
}


/**
 * udc_check_ep - check logical endpoint 
 * @lep:
 *
 * Return physical endpoint number to use for this logical endpoint or zero if not valid.
 */
int udc_check_ep (int logical_endpoint, int packetsize)
{
        // XXX check ep table

        return ( ((logical_endpoint & 0xf) >= UDC_MAX_ENDPOINTS) || (packetsize > 64)) 
                ?  0 : (logical_endpoint & 0xf);
}

#ifdef USE_DMA

void pxa_free_dma_bufs(pxa_dma_desc *buffers[], int num)
{
        int i;
        for (i = 0; i < num ; i++) {
                consistent_free(buffers[i], sizeof(pxa_dma_desc), NULL);
        }
        kfree(buffers);
}

pxa_dma_desc **pxa_setup_dma(usb_endpoint_instance *endpoint, int tx)
{
        int i;
        int num;
        pxa_dma_desc *buffers[];
        dma_addr_t dma_buf_phys = 0;

        if (tx) {
                num = (endpoint->tx_transferSize + endpoint->tx_packetSize - 1) / endpoint->tx_packetSize;
        }
        else {
                num = (endpoint->rcv_transferSize + endpoint->rcv_packetSize - 1) / endpoint->rcv_packetSize;
        }

        if (!(buffers = kmalloc(sizeof(pxa_dma_desc *) * num))) {
                return NULL;
        }

        for (i = 0; i < num; i++) {

                if (!(buffers[i] = consistent_alloc(GFP_KERNEL, num * sizeof(pxa_dma_desc), &dma_buf_phys))) {
                        pxa_free_dma_bufs(buffers);
                        return NULL;
                }
                buffers[i]->ddadr = 0;
                buffers[i]->dsadr = 0;
                buffers[i]->dtadr = 0;
                buffers[i]->dcmd = 0;
                if (i) {
                        buffers[i-1]->ddadr = buffers[i];
                }
        }
        return buffers;
}
#endif

/**
 * udc_set_ep - setup endpoint 
 * @ep:
 * @endpoint:
 *
 * Associate a physical endpoint with endpoint_instance
 */
void udc_setup_ep (struct usb_device_instance *device, unsigned int ep,
		   struct usb_endpoint_instance *endpoint)
{
	if (ep < UDC_MAX_ENDPOINTS) {

		ep_endpoints[ep] = endpoint;

		// ep0
		if (ep == 0) {
                        printk(KERN_INFO"udc_setup_ep: 0 do nothing\n");
		}
		// IN
		else if (endpoint->endpoint_address & 0x80) {
                        printk(KERN_INFO"udc_setup_ep: IN do nothing\n");
#ifdef USE_DMA
                        pxa_rx_dma_desc = pxa_setup_dma(ep, endpoint);
#endif
		}
		// OUT
		else if (endpoint->endpoint_address) {

                        printk(KERN_INFO"udc_setup_ep: OUT alloc urbs \n");

			usbd_fill_rcv (device, endpoint, 5);
			endpoint->rcv_urb = first_urb_detached (&endpoint->rdy);
#ifdef USE_DMA

                        pxa_rx_dma_desc = pxa_setup_dma(ep, endpoint);
                        pxa_start_out_dma(ep, endpoint);
#endif
		}
                pxa_enable_ep_interrupt(ep_endpoints[ep]->endpoint_address);
	}
}

/**
 * udc_disable_ep - disable endpoint
 * @ep:
 *
 * Disable specified endpoint 
 */
void udc_disable_ep (unsigned int ep)
{
	if (ep < UDC_MAX_ENDPOINTS) {
		struct usb_endpoint_instance *endpoint;

		if ((endpoint = ep_endpoints[ep])) {
			ep_endpoints[ep] = NULL;
			usbd_flush_ep (endpoint);
		}
	}
}

/* ********************************************************************************************* */

/**
 * udc_connected - is the USB cable connected
 *
 * Return non-zeron if cable is connected.
 */
int udc_connected ()
{
	return 1;
}

/**
 * udc_connect - enable pullup resistor
 *
 * Turn on the USB connection by enabling the pullup resistor.
 */
void udc_connect (void)
{
#ifdef CONFIG_SABINAL_DISCOVERY
        /* Set GPIO pin function to I/O */
        GPAFR1_U &= ~(0x3 << ((USBD_CONNECT_GPIO & 0xF) << 1));
        /* Set pin direction to output */
        GPDR(1) |= GPIO_GPIO(USBD_CONNECT_GPIO);
#if defined(USBD_CONNECT_HIGH)
	/* Set GPIO pin high to connect */
	GPSR(1) |= GPIO_GPIO(USBD_CONNECT_GPIO);
#else
	/* Set GPIO pin low to connect */
	GPCR(1) |= GPIO_GPIO(USBD_CONNECT_GPIO);
#endif
#else
#warning NO USB Device connect
#endif
}

/**
 * udc_disconnect - disable pullup resistor
 *
 * Turn off the USB connection by disabling the pullup resistor.
 */
void udc_disconnect (void)
{
#ifdef CONFIG_SABINAL_DISCOVERY
        /* Set GPIO pin function to I/O */
        GPAFR1_U &= ~(0x3 << ((USBD_CONNECT_GPIO & 0xF) << 1));
        /* Set pin direction to output */
        GPDR(1) |= GPIO_GPIO(USBD_CONNECT_GPIO);
#if defined(USBD_CONNECT_HIGH)
	/* Set GPIO pin low to disconnect */
	GPCR(1) |= GPIO_GPIO(USBD_CONNECT_GPIO);
#else
	/* Set GPIO pin high to disconnect */
	GPSR(1) |= GPIO_GPIO(USBD_CONNECT_GPIO);
#endif
#else
#warning NO USB Device connect
#endif
}

#if 0
/**
 * udc_int_hndlr_cable - interrupt handler for cable
 */
static void udc_int_hndlr_cable (int irq, void *dev_id, struct pt_regs *regs)
{
	// GPIOn interrupt
}
#endif

/* ********************************************************************************************* */

/**
 * udc_enable_interrupts - enable interrupts
 *
 * Switch on UDC interrupts.
 *
 */
void udc_all_interrupts (struct usb_device_instance *device)
{
        int i;

        UDCCR &= ~(UDCCR_REM | UDCCR_SUSIR);
        UDCCR |= UDCCR_RESIR;

        // XXX SOF UFNHR &= ~UFNHR_SIM;

        // always enable control endpoint
        pxa_enable_ep_interrupt(0);

        for (i = 1; i < UDC_MAX_ENDPOINTS; i++) {

                printk (KERN_INFO "udc_enable_interrupts[%d]:\n", i);
                if (ep_endpoints[i] && ep_endpoints[i]->endpoint_address) {
                        pxa_enable_ep_interrupt(ep_endpoints[i]->endpoint_address);
                }
        }
}


/**
 * udc_suspended_interrupts - enable suspended interrupts
 *
 * Switch on only UDC resume interrupt.
 *
 */
void udc_suspended_interrupts (struct usb_device_instance *device)
{
        UDCCR |= UDCCR_REM | UDCCR_SUSIR;
        UDCCR &= ~UDCCR_RESIR;
}


/**
 * udc_disable_interrupts - disable interrupts.
 *
 * switch off interrupts
 */
void udc_disable_interrupts (struct usb_device_instance *device)
{
        UICR0 = UICR1 = 0xff;
        UFNHR |= UFNHR_SIM;
}

/* ********************************************************************************************* */

/**
 * udc_ep0_packetsize - return ep0 packetsize
 */
int udc_ep0_packetsize (void)
{
	return EP0_PACKETSIZE;
}

/**
 * udc_enable - enable the UDC
 *
 * Switch on the UDC
 */
void udc_enable (struct usb_device_instance *device)
{
	// save the device structure pointer
	udc_device = device;

	// ep0 urb
	if (!ep0_urb) {
		if (!(ep0_urb = usbd_alloc_urb (device, device->function_instance_array, 0, 512))) {
			printk (KERN_ERR "udc_enable: usbd_alloc_urb failed\n");
		}
                printk(KERN_ERR"udc_enable: ep0_urb: %p\n", ep0_urb);
	} 
        else {
		printk (KERN_ERR "udc_enable: ep0_urb already allocated\n");
	}


	// enable UDC

	// c.f. 3.6.2 Clock Enable Register
        printk(KERN_INFO "udc_enable: -> CKEN: %02x %02x\n", CKEN, CKEN11_USB);
	CKEN |= CKEN11_USB;
        printk(KERN_INFO "udc_enable: <- CKEN: %02x\n", CKEN);

        // c.f. 12.4.11 GPIOn and GPIOx
        // enable cable interrupt
        

        // c.f. 12.5 UDC Operation - after reset on EP0 interrupt is enabled.

        // c.f. 12.6.1.1 UDC Enable
        
        printk(KERN_INFO "udc_enable: -> UDCCR: %02x %02x\n", CKEN, UDCCR_UDE | UDCCR_REM);
        UDCCR |= UDCCR_UDE | UDCCR_REM;
        printk(KERN_INFO "udc_enable: -> UDCCR: %02x\n", CKEN);
}


/**
 * udc_disable - disable the UDC
 *
 * Switch off the UDC
 */
void udc_disable (void)
{
	// disable UDC
        // c.f. 12.6.1.1 UDC Enable
        UDCCR &= ~( UDCCR_UDE | UDCCR_REM );

	// c.f. 3.6.2 Clock Enable Register
	CKEN &= ~CKEN11_USB;

        // disable cable interrupt
        
	// reset device pointer
	udc_device = NULL;

	// ep0 urb

	if (ep0_urb) {
		usbd_dealloc_urb (ep0_urb);
		ep0_urb = 0;
	} else {
		printk (KERN_ERR "udc_disable: ep0_urb already NULL\n");
	}
}


/**
 * udc_startup - allow udc code to do any additional startup
 */
void udc_startup_events (struct usb_device_instance *device)
{
	usbd_device_event (device, DEVICE_INIT, 0);
	usbd_device_event (device, DEVICE_CREATE, 0);
	usbd_device_event (device, DEVICE_HUB_CONFIGURED, 0);
}


/* ********************************************************************************************* */

/**
 * udc_name - return name of USB Device Controller
 */
char *udc_name (void)
{
	return UDC_NAME;
}

/**
 * udc_request_udc_irq - request UDC interrupt
 *
 * Return non-zero if not successful.
 */
int udc_request_udc_irq ()
{
	// request IRQ  and IO region
	if (request_irq (IRQ_USB, int_hndlr, SA_INTERRUPT | SA_SAMPLE_RANDOM,
	     UDC_NAME " USBD Bus Interface", NULL) != 0) 
        {
		printk (KERN_INFO "usb_ctl: Couldn't request USB irq\n");
		return -EINVAL;
	}
	return 0;
}

/**
 * udc_request_cable_irq - request Cable interrupt
 *
 * Return non-zero if not successful.
 */
int udc_request_cable_irq ()
{
#ifdef XXXX_CABLE_IRQ
	// request IRQ  and IO region
	if (request_irq
	    (XXXX_CABLE_IRQ, int_hndlr, SA_INTERRUPT | SA_SAMPLE_RANDOM, UDC_NAME " Cable",
	     NULL) != 0) {
		printk (KERN_INFO "usb_ctl: Couldn't request USB irq\n");
		return -EINVAL;
	}
#endif
	return 0;
}


/**
 * udc_request_udc_io - request UDC io region
 *
 * Return non-zero if not successful.
 */
int udc_request_io ()
{
#ifdef USE_DMA
        // XXX verify prio
        if ((pxa_tx_dma_chan = pxa_request_dma("USB transmit", DMA_PRIO_HIGH, pxa_usb_tx_dma_irq, NULL)) < 0) {
                printk(KERN_INFO"pxa: failed to allocate tx dma channel\n");
                return -EINVAL;
        }
        if ((pxa_rx_dma_chan = pxa_request_dma("USB receive", DMA_PRIO_HIGH, pxa_usb_rx_dma_irq, NULL)) < 0) {
                printk(KERN_INFO"pxa: failed to allocate rx dma channel\n");
                pxa_free_dma(pxa_tx_dma_chan);
                return -EINVAL;
        }

        printk(KERN_INFO"pxa: DMA channels TX: %d RX: %d\n", pxa_tx_dma_chan, pxa_rx_dma_chan);
#endif
	return 0;
}

/**
 * udc_release_udc_irq - release UDC irq
 */
void udc_release_udc_irq ()
{
	free_irq (IRQ_USB, NULL);
}

/**
 * udc_release_cable_irq - release Cable irq
 */
void udc_release_cable_irq ()
{
#ifdef XXXX_IRQ
	free_irq (XXXX_CABLE_IRQ, NULL);
#endif
}

/**
 * udc_release_release_io - release UDC io region
 */
void udc_release_io ()
{
#ifdef USE_DMA
        if (pxa_tx_dma_chan >= 0) {
            pxa_free_dma(pxa_tx_dma_chan);
        }
        if (pxa_rx_dma_chan >= 0) {
            pxa_free_dma(pxa_rx_dma_chan);
        }
#endif
}

/**
 * udc_regs - dump registers
 *
 * Dump registers with printk
 */
void udc_regs (void)
{
	printk ("[%d:%d] CCR[%02x] UICR[%02x %02x] UFNH[%02x %02x] UDCCS[%02x %02x %02x %02x]\n", 
                        udc_interrupts, jifs(), UDCCR, UICR1, UICR0, UFNHR, UFNLR, UDCCS0, UDCCS1, UDCCS2, UDCCS5);
}

