/***********************************************************************
 * $Id:: hw_usbd_ip3511.c 2220 2015-12-09 18:35:56Z usb00423           $
 *
 * Project: IP3511 USB device controller definitions
 *
 * Description:
 *     This file contains USBD driver support for the IP3511 USB device
 *     Controller.
 *
 ***********************************************************************
 *   Copyright(C) 2011, NXP Semiconductor
 *   All rights reserved.
 *
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * products. This software is supplied "AS IS" without any warranties.
 * NXP Semiconductors assumes no responsibility or liability for the
 * use of the software, conveys no license or title under any patent,
 * copyright, or mask work right to the product. NXP Semiconductors
 * reserves the right to make changes in the software without
 * notification. NXP Semiconductors also make no representation or
 * warranty that such application will be suitable for the specified
 * use without further testing or modification.
 **********************************************************************/

/** @ingroup HAL USBD_STACK
 * @{
 */

#include <string.h>

#include "mw_usbd.h"
#include "mw_usbd_core.h"
#include "mw_usbd_hw.h"
#include "hw_usbd_ip3511.h"

typedef struct __USBD_HW_DATA_T {
	uint32_t *EPList_hw;/* EPList for Hardware */
	EP_LIST  *EPList;	/* EPlist for Software */
	volatile uint32_t ep_zero_base;
	/* The BufferUsed flag is to deal with double buffer situation, This is a bit mask
	 flag for non-zero EPs. If BufferUsed bit is set, buffer 0 is used, switch to buffer 1.
	 If BufferUsed flag is not set, buffer 0 can be used. */
	volatile uint32_t BufferUsed;
	volatile uint32_t nak_on_ep;/* Keep track NAK enabled EPs */
	USB_REGS_T *regs;
	USB_CORE_CTRL_T *pCtrl;
	int32_t ip_type;/* Flag used to store device speed */
} USBD_HW_DATA_T;

extern const USBD_HW_API_T hw_api;

void def_ep_handler(void) {}

/*
 *    Based on the logic EP number and direction bit 7,
 *    when bit 7 is set, it's an IN EP, otherwise,
 *    it's an OUT EP. The return value is the
 *    location of the EP in the interrupt(status/enable/
 *    routing, etc) register.
 *    The bit info. of the interrupt register is
 *    bit 0 is EP0 OUT, bit 1 is EP0 IN,...
 *    bit 28 is EP14 OUT, bit 29 is EP14 IN....
 *    e.g. EPNum(0x80) is EP0 IN, the return
 *    value is 1; EPNum(0x8E) is EP14 IN, the return
 *    value is 29.
 *    Parameters:      Logical EP with direction bit
 *    Return Value:    EP address in interrupt status.
 */

static uint32_t EPAdr(uint32_t EPNum)
{
	uint32_t val;

	val = (EPNum & 0x0F) << 1;
	if (EPNum & 0x80) {
		val += 1;
	}
	return val;
}

/*
 *  Deactivate USB Endpoint
 *    Set the EP bits in the SKIP register and until the bits are
 *    cleared by the H/W. Clear the EP interrupts as well.
 *    It's used for non-zero EPs. For EP0 IN and OUT, clear
 *    the ACTIVE bit in EP Command/Status list will serve the purpose.
 *    Parameters:      EPNum: Endpoint Number
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 *    Return Value:    None
 */

void USB_DeactivateEP(USBD_HANDLE_T hUsb, uint32_t EPNum)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t epbit;

	epbit = 0x1 << EPAdr(EPNum);
	drv->regs->EPSKIP |= epbit;
	while ( drv->regs->EPSKIP & epbit ) {}
	drv->regs->INTSTAT = epbit;			/* Clear EP interrupt(s). */
}

/**
 * @brief   Force Full Speed.
 * @param [in] con.
 * @retval  None.
 *
 * Example Usage:
 * @code
 *    hwUSB_ForceFullSpeed(hUsb, 0x1); // Force Full speed.
 * @endcode
 */
void hwUSB_ForceFullSpeed(USBD_HANDLE_T hUsb, uint32_t con)
{
	/* Not needed */
}

/**
 * @brief   Get memory required by USB Driver.
 * @param [in/out] param parameter structure used for initialisation.
 * @retval  Length required for device controller data structure and buffers.
 * @Endpoint buffer size is not included as device descriptor is not passed.
 * @However hwUSB_Init function allocates memory for endpoint buffers.
 *
 * Example Usage:
 * @code
 *    mem_req = hwUSB_GetMemSize(param);
 * @endcode
 */
uint32_t hwUSB_GetMemSize(USBD_API_INIT_PARAM_T *param)
{
	uint32_t req_len = 0;
	uint32_t ep_buf_len = 0;

	/* calculate required length */
	req_len += (2 * USB_MAX_EP_NUM * EP_CMD_INFO_SIZE);	/* EPlist Hardware */
	req_len += ((4 * (param->max_num_ep)) * sizeof(EP_LIST));	/* EPlist Software */
	req_len += sizeof(USBD_HW_DATA_T);	/* memory for hw driver data structure */
	/* for alignment overhead */
	req_len = (req_len + 0x3) & ~0x03u;
	req_len += sizeof(USB_CORE_CTRL_T);	/* memory for USBD controller structure */
	/* for alignment overhead */
	req_len = (req_len + 0x3F) & ~0x3Fu;
	/* EP 0 buffers */
	req_len += EP_ZERO_BUF_MAX_BYTES;
	/* Accurate buffer requirements calculated in init function based on EP descriptors.
	 Here we assume all non-zero EPS are bulk and guestimate memory requirement. */
	if(param->high_speed_capable)
		ep_buf_len = 2 * (param->max_num_ep - 1) * USB_HS_MAX_BULK_PACKET;
	else
		ep_buf_len = 2 * (param->max_num_ep - 1) * USB_FS_MAX_BULK_PACKET;
	
	if(param->double_buffer)
		req_len += 2 * ep_buf_len;
	else
		req_len += ep_buf_len;

	return req_len;
}

/*
 *  USB Connect Function
 *   Called by the User to Connect/Disconnect USB
 *    Parameters:      con:   Connect/Disconnect
 *    Return Value:    None
 */
void hwUSB_Connect(USBD_HANDLE_T hUsb, uint32_t con)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;

	if ( con ) {
		drv->regs->DEVCMDSTAT |= USB_DCON;
	}
	else {
		drv->regs->DEVCMDSTAT &= ~USB_DCON;
	}
}

/*
 *  USB Remote Wakeup Function
 *   Called automatically on USB Remote Wakeup
 *    Return Value:    None
 */
void hwUSB_WakeUp(USBD_HANDLE_T hUsb)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;

    if((drv->regs->DEVCMDSTAT & USB_LPM) && (drv->regs->DEVCMDSTAT & USB_REMOTE_WAKE) && (drv->regs->DEVCMDSTAT & USB_LPM_SUS)) {
        drv->regs->DEVCMDSTAT &= ~USB_LPM_SUS;
    }
    else {
        if((pCtrl->device_status & USB_GETSTATUS_REMOTE_WAKEUP) && ( drv->regs->DEVCMDSTAT & USB_DSUS )) {
            drv->regs->DEVCMDSTAT &= ~USB_DSUS;
        }
    }
}

/*
 *  USB Remote Wakeup Configuration Function
 *    Parameters:      cfg:   Enable/Disable
 *    Return Value:    None
 */
void hwUSB_WakeUpCfg(USBD_HANDLE_T hUsb, uint32_t cfg)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;

	if ( !cfg ) {
		drv->regs->DEVCMDSTAT &= ~USB_PLL_ON;	/* NeedClk functional */
	}
	else {
		drv->regs->DEVCMDSTAT |= USB_PLL_ON;/* NeedClk always "1" */
	}
}

/*
 *  USB Set Address Function
 *    Parameters:      adr:   USB Address
 *    Return Value:    None
 */
void hwUSB_SetAddress(USBD_HANDLE_T hUsb, uint32_t adr)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t devcmdstat;

	devcmdstat = drv->regs->DEVCMDSTAT;
	devcmdstat &= ~0x7F;
	devcmdstat |= ((adr & 0x7F) | USB_EN);

	drv->regs->DEVCMDSTAT = devcmdstat;
}

/*
 *  USB set test mode Function
 *    Parameters:      mode:   test mode
 *    Return Value:    TRUE if supported else FALSE
 */
ErrorCode_t hwUSB_SetTestMode(USBD_HANDLE_T hUsb, uint8_t mode)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t test_mode;

	if ( drv->ip_type != USB_HIGH_SPEED ) {
		return ERR_USBD_INVALID_REQ;
	}
	if ((mode > 0) && (mode < 8)) {
		/* If ip_type is high speed, test mode needs to be supported. */
		/* read device cmd status register bit 29, 30, and 31. */
		test_mode = drv->regs->DEVCMDSTAT & 0x1FFFFFFF;
		/* set the test mode */
		drv->regs->DEVCMDSTAT = test_mode | ((uint32_t) mode << 29);
		return LPC_OK;
	}
	return ERR_USBD_INVALID_REQ;
}

/*
 *  USB Configure Function
 *    Parameters:      cfg:   Configure/Deconfigure
 *    Return Value:    None
 */
void hwUSB_Configure(USBD_HANDLE_T hUsb, uint32_t cfg)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	/* if SOF/FRAME_INT is enabled keep it enabled */
	uint32_t sof_int_en = FRAME_INT & drv->regs->INTEN;

	if ( cfg ) {
		/* All the non-zero EPs are configured and enabled per configuration
		   descriptor. Enable all interrupts. */
		drv->regs->INTEN  = DEV_STAT_INT | MAX_PHY_EP_INTS | sof_int_en;
	}
	else {
		/* if the configuration is FALSE, turn off all the non-zero EPs. Only
		   CTRL EP interrupts are enabled. */
		drv->regs->INTEN  = DEV_STAT_INT | 0x03 | sof_int_en;
	}
}

/*
 *  Configure USB Endpoint according to Descriptor
 *    Parameters:      pEPD:  Pointer to Endpoint Descriptor
 *    Return Value:    None
 */
void hwUSB_ConfigEP(USBD_HANDLE_T hUsb, USB_ENDPOINT_DESCRIPTOR *pEPD)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t ep_index;
	uint32_t *addr;
	EP_LIST *EPList; 
	uint32_t addr_offset_mask, ep_nbyte_offset;
	uint32_t ep_flags = EP_DISABLED;

	ep_index = EPAdr(pEPD->bEndpointAddress) * 2;
	addr = drv->EPList_hw + ep_index;
	EPList = &drv->EPList[ep_index];

	if ( (pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_ISOCHRONOUS ) 
		ep_flags |= EP_ISO_TYPE;
		
	if ( drv->ip_type == USB_HIGH_SPEED ) {
		addr_offset_mask = HS_ADDR_OFFSET_MASK;
		ep_nbyte_offset = HS_EP_NBYTE_OFFSET;
		
		if ( (pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_INTERRUPT )
			ep_flags |= EP_RF_TV | EP_ISO_TYPE;
	}
	else {
		addr_offset_mask = FS_ADDR_OFFSET_MASK;
		ep_nbyte_offset = FS_EP_NBYTE_OFFSET;
	}

	/* Set EP buffer address and configurations */
	addr[0] = ep_flags | (pEPD->wMaxPacketSize << ep_nbyte_offset) | 
		(addr_offset_mask & (EPList[0].buf_ptr >> 6));
	/* Remember EP's max packet size */
	EPList[0].buf_length = pEPD->wMaxPacketSize;
	/* if double buffer is enabled */
	if ( drv->regs->EPBUFCFG & (0x1U << EPAdr(pEPD->bEndpointAddress))) {
		addr[1] = ep_flags | (pEPD->wMaxPacketSize << ep_nbyte_offset) | 
			(addr_offset_mask & (EPList[1].buf_ptr >> 6));
		EPList[1].buf_length = pEPD->wMaxPacketSize;
	}
}

/*
 *  Set Direction for USB Control Endpoint
 *    Parameters:      dir:   Out (dir == 0), In (dir <> 0)
 *    Return Value:    None
 */
void hwUSB_DirCtrlEP(USBD_HANDLE_T hUsb, uint32_t dir)
{
	/* Not needed */
}

/*
 *  Enable USB Endpoint
 *    Parameters:      EPNum: Endpoint Number
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 *    Return Value:    None
 */
void hwUSB_EnableEP(USBD_HANDLE_T hUsb, uint32_t EPNum)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t *addr;

	addr = drv->EPList_hw + (EPAdr(EPNum) * 2);
	addr[0] &= ~EP_DISABLED;
	/* For EP_OUT, in addition to clear EP_DISABLED bits,
			   set the ACTIVE bit indicating that EP is ready to read. */
	if ( !(EPNum & 0x80) )
		addr[0] |= BUF_ACTIVE;
	
	/* if double buffer is enabled */
	if ( drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum))) {

		/* clear EP_DISABLED to both buffer. */
		addr[1] &= ~EP_DISABLED;

		/* For double buffered EPx_OUT, set ACTIVE bit and clear 
			EP_DISABLED bit for both buffer0 and 1. */
		if ( !(EPNum & 0x80) ) {
			/* reset EPINUSE to buffer0 before setting BUF_ACTIVE */
			drv->regs->EPINUSE &= ~(0x1ul << EPAdr(EPNum));
			drv->BufferUsed &= ~(0x1U << EPAdr(EPNum));
			/* configure second buffer. */
			addr[1] |= BUF_ACTIVE;
		}
	}
}

/*
 *  Disable USB Endpoint
 *    Parameters:      EPNum: Endpoint Number
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 *    Return Value:    None
 */

void hwUSB_DisableEP(USBD_HANDLE_T hUsb, uint32_t EPNum)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t *addr;

	/* set EP_DISABLED bit */
	addr = drv->EPList_hw + (EPAdr(EPNum) * 2);
	addr[0] |= EP_DISABLED;
	if ( drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum))) {
		/* For double buffer */
		addr[1] |= EP_DISABLED;
	}
}

/*
 *  Reset USB Endpoint
 *    Parameters:      EPNum: Endpoint Number
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 *    Return Value:    None
 */
void hwUSB_ResetEP(USBD_HANDLE_T hUsb, uint32_t EPNum)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t *addr = drv->EPList_hw + (EPAdr(EPNum) * 2);
	uint32_t buf_idx = 0;

	/* if double buffer is enabled */
	if ( drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum))) {
		buf_idx = (drv->regs->EPINUSE >> EPAdr(EPNum)) & 0x1;
		/* clear STALL bit on non-active buffer also */
		addr[buf_idx ^ 0x1] &= ~EP_STALL;
	}
	
	/* Based on EPInUse register to decide which buffer needs to toggle
	   reset. When this happens, the STALL bits need to be cleared for both
	   buffer 0 and 1. */
	addr[buf_idx] &= ~EP_STALL;
	addr[buf_idx] |= EP_RESET;
}

/**
 * @brief   Genrates STALL signalling for requested endpoint.
 * @param [in] hUsb  Handle to USBD stack instance.
 * @param [in] EPNum Endpoint Number.
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 * @retval  void.
 *
 * Example Usage:
 * @code
 *    USB_SetStallEP(hUsb, 0x83); 
 * @endcode
 */
void hwUSB_SetStallEP(USBD_HANDLE_T hUsb, uint32_t EPNum)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t *addr = drv->EPList_hw + (EPAdr(EPNum) * 2);
	uint32_t num_bufs = 1;
	int i;
	
	/* Check if the EP is enabled to have double buffers */
	if (drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum))) {
		num_bufs = 2;
	}
	for ( i = 0; i < num_bufs; i++) {
		/* STALL bit can't be set until ACTIVE bit is gone. */
		if ( addr[i] & BUF_ACTIVE ) {
			USB_DeactivateEP(hUsb, EPNum);
		}
		addr[i] |= EP_STALL;
	}
}

/**
 * @name    USB_ClrStallEP
 * @brief   Clear Stall for USB Endpoint.
 * @ingroup USB Device Stack
 *
 * Clear STALL state for the requested endpoint.
 *
 * @param [in] hUsb  Handle to USBD stack instance.
 * @param [in] EPNum Endpoint Number.
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 * @retval  void.
 *
 * Example Usage:
 * @code
 *    USB_ClrStallEP(hUsb, 0x83); // clear stall ep3_IN.
 * @endcode
 */
void hwUSB_ClrStallEP(USBD_HANDLE_T hUsb, uint32_t EPNum)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t *addr;
	uint32_t j, i;
	EP_LIST *EPList;
	uint32_t num_bufs = 1;
	uint32_t buf_idx = 0;

	EPList = drv->EPList;
	j = EPAdr(EPNum) * 2;
	addr = drv->EPList_hw + j;
	
	/* Check if the EP is enabled to have double buffers */
	if (drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum))) {
		buf_idx = (drv->regs->EPINUSE >> EPAdr(EPNum)) & 0x1;
		addr[buf_idx ^ 0x1] &= ~EP_STALL;
		drv->BufferUsed &= ~(0x01u << EPAdr(EPNum));
		drv->BufferUsed |= (buf_idx << EPAdr(EPNum));
		num_bufs = 2;
	}
	/* clear the STALL and toggle value bit. */
	addr[buf_idx] &= ~EP_STALL;
	/* Based on EPInUse register to decide which buffer needs to toggle reset. */
	addr[buf_idx] |= EP_RESET;

	if ((!(EPNum & 0x80)) && ((EPNum & 0x0F) != 0)) {

        /* For EPx_OUT, ACTIVE bit and length field need to
           be set again after clearing STALL. */
		for ( i = 0; i < num_bufs; i++) {
			if ( drv->ip_type == USB_HIGH_SPEED ) {
				addr[i] &= ~(HS_PKT_LNGTH_MASK << HS_EP_NBYTE_OFFSET);
				addr[i] |= ((EPList[j].buf_length << HS_EP_NBYTE_OFFSET) | BUF_ACTIVE);
			}
			else {
				addr[i] &= ~(FS_PKT_LNGTH_MASK << FS_EP_NBYTE_OFFSET);
				addr[i] |= ((EPList[j].buf_length << FS_EP_NBYTE_OFFSET) | BUF_ACTIVE);
			}
		}
	}
}

/*
 *  Function to enable/disable selected USB event.
 */

ErrorCode_t hwUSB_EnableEvent(USBD_HANDLE_T hUsb, uint32_t EPNum,
							  uint32_t event_type, uint32_t enable)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t epbit;
	ErrorCode_t ret = LPC_OK;
	volatile uint32_t *reg = &drv->regs->INTEN;
	uint32_t bitmask = 0;
	switch (event_type) {
	case USB_EVT_RESET:
		/* enable reset event */
		/* Device Reset and State change interrupts are
		   controlled by single control bit */
		break;

	case USB_EVT_DEV_STATE:
		/* Enable Device status interrupt */
		bitmask = DEV_STAT_INT;
		break;

	case USB_EVT_SOF:
		/* enable SOF event */
		bitmask = FRAME_INT;
		break;

	case USB_EVT_DEV_ERROR:
		/* enable error event */
		/* No Interrupt control bit */
		break;

	case USB_EVT_OUT_NAK:
	case USB_EVT_IN_NAK:
		reg = &drv->regs->DEVCMDSTAT;
		epbit = 0x1 << EPAdr(EPNum);
		if ( EPNum & 0xF ) {/* Non Zero Endpoint */
			if ( enable ) {
				drv->nak_on_ep |= epbit;
				if ( event_type == USB_EVT_OUT_NAK ) {
					bitmask = USB_IntOnNAK_AO;
				}
				else {
					bitmask = USB_IntOnNAK_AI;
				}
			}
			else {
				/* Disable only if all non zero EPS have
				   interrupt on NAK disabled */
				drv->nak_on_ep &= ~epbit;
				if ( event_type == USB_EVT_OUT_NAK ) {
					if ( (drv->nak_on_ep & NZ_EP_OUT_MASK) == 0x0 ) {
						bitmask = USB_IntOnNAK_AO;
					}
				}
				else {
					drv->nak_on_ep &= ~epbit;
					if ( (drv->nak_on_ep & NZ_EP_IN_MASK) == 0x0 ) {
						bitmask = USB_IntOnNAK_AI;
					}
				}
			}
		}
		else {		/* Control Endpoint */
			if ( event_type == USB_EVT_OUT_NAK ) {
				bitmask = USB_IntOnNAK_CO;
			}
			else {
				bitmask = USB_IntOnNAK_CI;
			}
			if ( enable ) {
				drv->nak_on_ep |= epbit;
			}
			else {
				drv->nak_on_ep &= ~epbit;
			}
		}
		break;

	default:
		ret = ERR_USBD_INVALID_REQ;
		break;
	}
	if (enable) {
		*reg |= bitmask;
	}
	else {
		*reg &= ~bitmask;
	}
	return ret;
}

/*
 *  Read USB Setup Packet
 *    Parameters:      EPNum: Endpoint Number
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 *                     pData: Pointer to Data Buffer
 *    Return Value:    Number of bytes read
 */
#if defined (__ICCARM__)
#pragma optimize=speed
#elif defined ( __GNUC__ )
__attribute__((optimize("Ofast")))
#elif defined ( __CC_ARM )
#pragma Otime
#endif
uint32_t hwUSB_ReadSetupPkt(USBD_HANDLE_T hUsb, uint32_t EPNum, uint32_t *pData)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t cnt = 0;
	uint32_t *addr = drv->EPList_hw;
	uint32_t *dataptr;
	EP_LIST *EPList = drv->EPList;

	/* This routine should be invoked for EP0 only */
	if (EPNum & 0x0F) {
		return 0;
	}

	/* Check/Clear STALL on both EP0 IN and OUT when SETUP is received. */
	addr[0] &= ~EP_STALL;
	addr[2] &= ~EP_STALL;

	cnt = sizeof(USB_SETUP_PACKET);
	dataptr = (uint32_t *) EPList[1].buf_ptr;
	memcpy(pData, dataptr, cnt);

	/* Use EP0 buffer 1 for SETUP packet */
	/* Fixed Command/Status location(EPList[1] for SETUP. Reset buffer pointer
	   field, SETUP length is fixed with eight bytes. */
	if ( drv->ip_type == USB_HIGH_SPEED ) {
		addr[1] = (HS_ADDR_OFFSET_MASK & ((EPList[1].buf_ptr) >> 6));
	}
	else {
		addr[1] = (FS_ADDR_OFFSET_MASK & ((EPList[1].buf_ptr) >> 6));
	}
	return cnt;
}

uint32_t hwUSB_ReadReqEP(USBD_HANDLE_T hUsb, uint32_t EPNum, uint8_t *pData, uint32_t len)
{
	return LPC_OK;
}

/*
 *  Read USB Endpoint Data
 *    Parameters:      EPNum: Endpoint Number
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 *                     pData: Pointer to Data Buffer
 *    Return Value:    Number of bytes read
 */
uint32_t hwUSB_ReadEP(USBD_HANDLE_T hUsb, uint32_t EPNum, uint8_t *pData)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t cnt;
	uint32_t *addr;
	uint32_t buf_idx = 0;
	uint32_t *dataptr;
	uint32_t index;
	/* Get rid of warning Pa082 from IAR EWB. */
	uint32_t buf_length, buf_ptr;
	EP_LIST *EPList;
	uint32_t packet_length_mask, ep_nbyte_offset, addr_offset_mask;

	if ( drv->ip_type == USB_HIGH_SPEED ) {
		packet_length_mask = HS_PKT_LNGTH_MASK;
		ep_nbyte_offset = HS_EP_NBYTE_OFFSET;
		addr_offset_mask = HS_ADDR_OFFSET_MASK;
	}
	else {
		packet_length_mask = FS_PKT_LNGTH_MASK;
		ep_nbyte_offset = FS_EP_NBYTE_OFFSET;
		addr_offset_mask = FS_ADDR_OFFSET_MASK;
	}

	index = EPAdr(EPNum) * 2;
	addr = drv->EPList_hw + index;
	EPList = drv->EPList;

	if (drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum))) {
		/* For EPs with double buffer, check if BufferUsed bit mask is set,
		     buffer0 is used, switch to buffer 1, index needs to be changed
		     accordingly too. */
		buf_idx = (drv->BufferUsed >> EPAdr(EPNum)) & 0x1U;
		/* check if BufferUsed is in sync with HW. If buffer is still ACTIVE
			then we should have received packet in other buffer. */
		if (addr[buf_idx] & BUF_ACTIVE) {
			buf_idx ^= 1;
			/* if both buffers are active then hwUSB_ReadEP() shouldn't be called.
			Return 0 indicating no data is received */
			if (addr[buf_idx] & BUF_ACTIVE) {
				return 0;
			}
			/* correct BufferUsed state */
			drv->BufferUsed ^= (0x1U << EPAdr(EPNum));
		}
	}

	cnt = (addr[buf_idx] >> ep_nbyte_offset) & packet_length_mask;

	/* The NBytes field is decremented by H/W with the packet byte each time. */
	cnt = EPList[index + buf_idx].buf_length - cnt;

	dataptr = (uint32_t *) EPList[index + buf_idx].buf_ptr;
    memcpy(pData, dataptr, cnt);

	/* Toggle BufferUsed state before requeuing the free buffer */
	drv->BufferUsed ^= (0x1U << EPAdr(EPNum));
	/* Requeue the buffer after EP read, reset EP length and buffer pointer field */
	addr[buf_idx] &= ~0x3FFFFFF;
	/* Get rid of warning Pa082 from IAR EWB. */
	buf_length = EPList[index + buf_idx].buf_length;
	buf_ptr = EPList[index + buf_idx].buf_ptr;
	addr[buf_idx] |= ((buf_length << ep_nbyte_offset)
			| (addr_offset_mask & (buf_ptr >> 6)) | BUF_ACTIVE);
	return cnt;
}

/*
 *  Write USB Endpoint Data
 *    Parameters:      EPNum: Endpoint Number
 *                       EPNum.0..3: Address
 *                       EPNum.7:    Dir
 *                     pData: Pointer to Data Buffer
 *                     cnt:   Number of bytes to write
 *    Return Value:    Number of bytes written
 */

uint32_t hwUSB_WriteEP(USBD_HANDLE_T hUsb, uint32_t EPNum, uint8_t *pData, uint32_t cnt)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t *addr;
	uint32_t *dataptr;
	uint32_t index;
	EP_LIST *EPList;
	uint32_t buf_idx = 0;

	index = EPAdr(EPNum) * 2;
	addr = drv->EPList_hw + index;
	EPList = drv->EPList;

	if (drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum))) {
		/* If double buffer is used check EPInUse, if set, buffer0
		   is used, otherwise, buffer1 is used. */
		buf_idx = (drv->regs->EPINUSE >> EPAdr(EPNum)) & 0x1;
		/* make sure the buffer is not actively used by HW */
		if ( addr[buf_idx] & BUF_ACTIVE ) {
			buf_idx ^= 1;	
		}
		index += buf_idx;
	}
	/* if buffer is primed/active then return 0 indicaing no bytes are written*/
	if ( addr[buf_idx] & BUF_ACTIVE ) {
		return 0;
	}

	/* Limit length to max packet size */
	if ( cnt > EPList[index].buf_length ) {
		cnt = EPList[index].buf_length;
	}

	/* Get EP command/status List, update the length field and data pointer. */
	addr[buf_idx] &= ~0x3FFFFFF;
	if ( drv->ip_type == USB_HIGH_SPEED ) {
		cnt &= HS_PKT_LNGTH_MASK;
		addr[buf_idx] |= (cnt << HS_EP_NBYTE_OFFSET) | ( HS_ADDR_OFFSET_MASK & ((EPList[index].buf_ptr) >> 6));
	}
	else {
		cnt &= FS_PKT_LNGTH_MASK;
		addr[buf_idx] |= (cnt << FS_EP_NBYTE_OFFSET) | ( FS_ADDR_OFFSET_MASK & ((EPList[index].buf_ptr) >> 6));
	}

	dataptr = (uint32_t *) EPList[index].buf_ptr;
	/* Stuff the data first, whether send out or not(set ACTIVE bit) is based
		 on STALL condition. */
	memcpy(dataptr, pData, cnt);

	/* When transmitting on EP0_IN, prime EP0_OUT. To receive 0 byte handshake packet on EP0_OUT */
	if ( !(EPNum & 0x0F) ) {
        addr[buf_idx] |= BUF_ACTIVE;
		addr = drv->EPList_hw; /* Get EP0_OUT buffer location */
		addr[0] |= BUF_ACTIVE;	
	}
    else {
        if(!(addr[buf_idx] & EP_STALL)) {
            addr[buf_idx] |= BUF_ACTIVE;
        }
        else {
            cnt = 0;
        }
    }
	return cnt;
}

/*
 *  USB Reset Function
 *   Called automatically on USB Reset
 *    Return Value:    None
 */
#if defined (__ICCARM__)
#pragma optimize=size
#elif defined ( __GNUC__ )
__attribute__((optimize("Os")))
#elif defined ( __CC_ARM )
#pragma Ospace
#endif
void hwUSB_Reset(USBD_HANDLE_T hUsb)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t i;
	uint32_t *addr;
	/* Get rid of warning Pa082 from IAR EWB. */
	uint32_t buf_length, buf_ptr;

	EP_LIST *EPList;
	uint32_t ep_nbyte_offset, addr_offset_mask;
	/* if SOF/FRAME_INT is enabled keep enabled */
	uint32_t sof_int_en = FRAME_INT & drv->regs->INTEN;

	if ( drv->ip_type == USB_HIGH_SPEED ) {
		/* IP3516 HS MEM */
		ep_nbyte_offset = HS_EP_NBYTE_OFFSET;
		addr_offset_mask = HS_ADDR_OFFSET_MASK;
	}
	else {
		/* IP3511 FS */
		ep_nbyte_offset = FS_EP_NBYTE_OFFSET;
		addr_offset_mask = FS_ADDR_OFFSET_MASK;
	}

	drv->BufferUsed = 0;

	drv->regs->EPLISTSTART = (uint32_t) drv->EPList_hw;
	drv->regs->DATABUFSTART = drv->ep_zero_base;
	EPList = drv->EPList;

	/* Zero out hw and sw EPlist */
	memset((void *) drv->EPList_hw, 0, 2 * USB_MAX_EP_NUM * EP_CMD_INFO_SIZE);
	
	/* CTRL, BULK or Interrupt IN/OUT EPs, max EP size is 64 */
	/* For EP0, double buffer doesn't apply to CTRL EPs, but, EP0OUTBuf0 is
	   for EP0OUT, EP0OUTBuf1 is for SETUP, EP0INBuf0 is for EP0IN, EP0INTBuf1 is
	   reserved. Also note: ACTIVE bit doesn't apply to SETUP and Reserved EP buffer. */
	addr = drv->EPList_hw;

	/* Setup Endpoint 0 and allocate buffer */
	for (i = 0; i < 4; i++) {
		/* Get rid of warning Pa082 from IAR EWB. */
		buf_length = EPList[i].buf_length;
		buf_ptr = EPList[i].buf_ptr;
		addr[i] = (buf_length << ep_nbyte_offset) |
				(addr_offset_mask & (buf_ptr >> 6));
	}

	/* Disable non zero endpoints. Buffer allocation is done in hwUSB_ConfigEP */
	for ( i = 4; i < (4 * USB_MAX_EP_NUM); i++ ) {
		addr[i] = EP_DISABLED;
	}

	drv->regs->EPINUSE = 0x0;
	drv->regs->EPSKIP = 0x0;

	/* Clear all EP interrupts, device status, and SOF interrupts. */
	drv->regs->INTSTAT = DEV_STAT_INT | FRAME_INT | MAX_PHY_EP_INTS;
	/* Enable all EPs interrupts including EP0, note: EP won't be
	   ready until it's configured/enabled when device sending SetEPStatus command
	   to the command engine. */
	drv->regs->INTEN  = DEV_STAT_INT | MAX_PHY_EP_INTS | sof_int_en;

	/* Enable USB */
	drv->regs->DEVCMDSTAT |= USB_EN;
}

/*
 *  USB Initialize Function
 *   Called by the User to initialize USB
 *    Return Value:    ErrorCode_t
 */
ErrorCode_t hwUSB_Init(USBD_HANDLE_T *phUsb, USB_CORE_DESCS_T *pDesc, USBD_API_INIT_PARAM_T *param)
{
	uint32_t *EPList_hw;
	EP_LIST *EPList;
	USBD_HW_DATA_T *drv;
	USB_CORE_CTRL_T *pCtrl;
	uint32_t temp_mem_base, mem_bytes;
	USB_ENDPOINT_DESCRIPTOR *pEpDesc;
	uint8_t  *pD;
	uint32_t new_addr, i, ep_next_buf, ep_sz;
	uint32_t ep_size[2 * USB_MAX_EP_NUM];

	/* check for memory alignment. For high speed USB assume user has passed USB_RAM base value. */
	/* check for length later when end point buffer sizes are calculated */
	if ( param->mem_base &  (256 - 1) ) {
		return ERR_USBD_BAD_MEM_BUF;
	}

	/* save initial value of mem base. */
	temp_mem_base = param->mem_base;

	/* Keep track of required memory */
	mem_bytes = 0;
	/* Allocate memory for Hardware EPlist which should be on 256 byte boundary */
	EPList_hw = (uint32_t *) param->mem_base;
	param->mem_base += (2 * USB_MAX_EP_NUM * EP_CMD_INFO_SIZE);
	memset((void *) EPList_hw, 0, 2 * USB_MAX_EP_NUM * EP_CMD_INFO_SIZE);

	/* Allocate memory for Software EPlist */
	EPList = (EP_LIST *) param->mem_base;
	param->mem_base += ((4 * (param->max_num_ep)) * sizeof(EP_LIST));
	memset((void *) &EPList[0], 0, 4 * param->max_num_ep * sizeof(EP_LIST));
	
	/* allocate memory for hardware driver data structure */
	drv = (USBD_HW_DATA_T *) param->mem_base;
	param->mem_base += sizeof(USBD_HW_DATA_T);
	/* align to 4 byte boundary */
	param->mem_base = (param->mem_base + 0x03) & ~0x03;
	/* allocate memory for USBD controller data structure */
	pCtrl = (USB_CORE_CTRL_T *) param->mem_base;
	param->mem_base += sizeof(USB_CORE_CTRL_T);

	/* now init USBD stack */
	mwUSB_InitCore(pCtrl, pDesc, param);
	/* first initialize data structures */
	memset((void *) drv, 0, sizeof(USBD_HW_DATA_T));
	/* set stack control and hw control pointer */
	drv->pCtrl = pCtrl;
	pCtrl->hw_data = (void *) drv;
	/* set up regs */
	drv->regs = (USB_REGS_T *) param->usb_reg_base;
	if (param->high_speed_capable) {
		drv->ip_type = USB_HIGH_SPEED;
	}
	else {
		drv->ip_type = USB_FULL_SPEED;
	}
	pCtrl->lpm_setting = param->lpm_setting;

	/* Also Endpoint buffers */
	drv->EPList_hw = EPList_hw;
	drv->EPList = EPList;

	/* Setup endpoints and allocate buffers */
	/* Endpoint buffers must be aligned to 64 byte boundary */
	param->mem_base += (64 - 1);
	param->mem_base &= ~(64 - 1);

	/* Store endpoint data buffer start address */
	drv->ep_zero_base = param->mem_base;
	/* Clear EP Size array to store MAXP set in all configurations */
	memset(&ep_size[0], 0, sizeof(ep_size));
	/* Calculate worst case buffer requirement for all endpoints in all device configurations 
	 by parsing the descriptors.  */
	pD = pDesc->full_speed_desc;
	i = 0;
	while (pD != NULL) {
		/* find MAXP for all EPs used */
		while (((USB_CONFIGURATION_DESCRIPTOR *) pD)->bLength != 0) {
			new_addr = (uint32_t) pD + ((USB_CONFIGURATION_DESCRIPTOR *) pD)->bLength;
			pEpDesc = (USB_ENDPOINT_DESCRIPTOR *) new_addr;
			/* Parse each endpoint descriptor for the particular configuration */
			while ((pEpDesc->bLength != 0 ) && 
				(pEpDesc->bDescriptorType != USB_CONFIGURATION_DESCRIPTOR_TYPE)) {
				new_addr = (uint32_t) pEpDesc + pEpDesc->bLength;

				/* parse endpoint descriptor */
				if (pEpDesc->bDescriptorType == USB_ENDPOINT_DESCRIPTOR_TYPE) {
					/* Endpoint address should not exceed max ep */
					/* Max ep is also indicated by IP3511 USB Config register but is not used */
					if ( (pEpDesc->bEndpointAddress & 0xF) > (param->max_num_ep - 1) ) {
						return ERR_USBD_BAD_EP_DESC;
					}
					/* Align EP Size to nearest 64 byte boundary */
					ep_sz = (pEpDesc->wMaxPacketSize + 0x3F) & ~0x3Fu;
					/* Update EP Size array only if the size is greater */
					if (ep_sz > ep_size[EPAdr(pEpDesc->bEndpointAddress)]) {
						ep_size[EPAdr(pEpDesc->bEndpointAddress)] = ep_sz;
					}
				}	/* pEpDesc->bDescriptorType == USB_ENDPOINT_DESCRIPTOR_TYPE */
				pEpDesc = (USB_ENDPOINT_DESCRIPTOR *) new_addr;
			}
			pD += ((USB_CONFIGURATION_DESCRIPTOR *) pD)->wTotalLength;
		}
		/* check other speed descriptor*/
		if ((pDesc->full_speed_desc != pDesc->high_speed_desc) && (i == 0))
			pD = pDesc->high_speed_desc;
		else
			pD = NULL;
		i++;
	}
	/* Allocate EP buffers as per MAXP set in configuration descriptors */
	ep_next_buf = drv->ep_zero_base;
	/* allocate for EP0_OUT */
	EPList[0].buf_ptr = ep_next_buf;
	EPList[0].buf_length = ((USB_DEVICE_DESCRIPTOR *) (pCtrl->device_desc))->bMaxPacketSize0;
	ep_next_buf += USB_MAX_PACKET0; /* increment by 64bytes for buffer alignment */
	/* allocate buffer for setup packets */
	EPList[1].buf_ptr = ep_next_buf;
	EPList[1].buf_length = sizeof(USB_SETUP_PACKET);
	ep_next_buf += USB_MAX_PACKET0; /* increment by 64bytes for buffer alignment */
	/* For EPO0_IN reuse the EP0_OUT buffer */
	EPList[2].buf_ptr = EPList[0].buf_ptr;
	EPList[2].buf_length = EPList[0].buf_length;
	EPList[3].buf_ptr = 0;
	EPList[3].buf_length = 0;

	for ( i = 2; i < 2 * param->max_num_ep; i++) {
		/* Remember EP buffer address and packet size */
		EPList[2*i].buf_ptr = ep_next_buf;
		EPList[2*i].buf_length = ep_size[i];
		ep_next_buf += ep_size[i];
		if (param->double_buffer) {
			EPList[2*i + 1].buf_ptr = ep_next_buf;
			EPList[2*i + 1].buf_length = ep_size[i];
			ep_next_buf += ep_size[i];
		}
	}
	/* Check total memory allocated is in limits*/
	mem_bytes = ep_next_buf - temp_mem_base;
	/* Check buffer length */
	if (param->mem_size < mem_bytes) {
		return ERR_USBD_BAD_MEM_BUF;
	}

	/* Update mem base pointer. */
	param->mem_base = temp_mem_base + mem_bytes;
	param->mem_size -= mem_bytes;

	/* Install default EP handlers so that ISR does not have to check if a
	   handler is present or not */
	for ( i = 0; i < 2 * USB_MAX_EP_NUM; i++) {
		if ( pCtrl->ep_event_hdlr[i] == 0 ) {
			pCtrl->ep_event_hdlr[i] = (USB_EP_HANDLER_T) def_ep_handler;
		}
	}
	//	drv->regs->DEVCMDSTAT = 0x0;
	/* SHOULD NOT reset to all zero. If connect and enable bits are set, clear them to disable and disconnect,
	   then do as BUS RESET arrives, and finally reconnect. */
	if ( (drv->regs->DEVCMDSTAT & (USB_DCON | USB_EN)) != 0x0 ) {
		drv->regs->DEVCMDSTAT &= ~(USB_DCON | USB_EN);
	}

	/* bit 31 of lpm_setting is LPM ENABLE/DISABLE bit, 1 is enabled, 0 is disabled. */
	if ( param->lpm_setting & (0x1UL << 31) ) {
		drv->regs->DEVCMDSTAT |= USB_LPM;
	}
	else {
		/* If bit 31 is not set, we update clear LPM bit in DEVCMDSTAT. */
		drv->regs->DEVCMDSTAT &= ~USB_LPM;
	}
	/* Shouldn't be a fixed value as indicated in earlier version.  */
	drv->regs->LPM = param->lpm_setting & 0x701F0;		/* update bit 4~8 and 16~18, the rest are Read-only. */
	if (param->double_buffer) {
		/* Enable double buffer except EP0_IN and EP0_OUT*/
		drv->regs->EPBUFCFG = MAX_PHY_EP_INTS & ~0x3ul;
	}
	/* initialize the HW */
	hwUSB_Reset(pCtrl);
	hwUSB_SetAddress(pCtrl, 0);

	/* return the handle */
	*phUsb = (USBD_HANDLE_T) pCtrl;

	return LPC_OK;
}

/*
 *  USB Interrupt Service Routine
 */
#if defined (__ICCARM__)
#pragma optimize=speed
#elif defined ( __GNUC__ )
__attribute__((optimize("Ofast")))
#elif defined ( __CC_ARM )
#pragma Otime
#endif
void hwUSB_ISR(USBD_HANDLE_T hUsb)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USBD_HW_DATA_T *drv = (USBD_HW_DATA_T *) pCtrl->hw_data;
	uint32_t disr, val, n, err;

	disr = drv->regs->INTSTAT;		/* Get Interrupt Status and clear immediately. */
	val = drv->regs->DEVCMDSTAT;	/* read device Status */
	drv->regs->INTSTAT = disr;
	disr &= drv->regs->INTEN;

	/* Save USB info register. Used later for NAK and Error handling */
	err = (drv->regs->INFO >> 11) & 0x0F;

	/* Device Status Interrupt (Reset, Connect change, Suspend/Resume) */
	if (disr & DEV_STAT_INT) {
		if (val & USB_DRESET_C) {		/* Reset */
			drv->regs->DEVCMDSTAT |= USB_DRESET_C;
			hwUSB_Reset(hUsb);
			mwUSB_ResetCore(pCtrl);
			/* Move to when USB BUS is received. */
			/* Update speed info. once BUS RESET is received. */
			/* It's very important to distinguish that:
			    ip_type is to show if the USB IP is capably of handling USB HS or not.
			    detected speed is the USB BUS speed negotiation after the CHIRP between the host and device. */
			if ( drv->ip_type == USB_HIGH_SPEED ) {
				if ((drv->regs->DEVCMDSTAT & (0x3 << 22)) == USB_CMD_STAT_SPEED_HIGH) {
					pCtrl->device_speed = USB_HIGH_SPEED;
				}
				else if ((drv->regs->DEVCMDSTAT & (0x3 << 22)) == USB_CMD_STAT_SPEED_FULL) {
					pCtrl->device_speed = USB_FULL_SPEED;
				}
			}
			else {
				pCtrl->device_speed = USB_FULL_SPEED;
			}
			if (pCtrl->USB_Reset_Event) {
				pCtrl->USB_Reset_Event(hUsb);
			}
			return;
		}
		if (val & USB_DCON_C) {			/* Connect change */
			drv->regs->DEVCMDSTAT |= USB_DCON_C;
			if (pCtrl->USB_Power_Event) {
				pCtrl->USB_Power_Event(hUsb, 0);
			}
		}
		if (val & USB_OTG_C) {			/* OTG Status change */
			drv->regs->DEVCMDSTAT |= USB_OTG_C;
		}
		if (val & USB_DSUS_C) {			/* Suspend/Resume */
			drv->regs->DEVCMDSTAT |= USB_DSUS_C;
			if ((val & USB_DSUS) || ((val & USB_LPM) && (val & USB_LPM_SUS))) {			/* Suspend */
				if (pCtrl->USB_Suspend_Event) {
					pCtrl->USB_Suspend_Event(hUsb);
				}
			}
			else {								/* Resume */
				if (pCtrl->USB_Resume_Event) {
					pCtrl->USB_Resume_Event(hUsb);
				}
			}
		}
	}	/* Device status Interrupt */

	/* Handle Endpoint Interrupts */

	if (disr & MAX_PHY_EP_INTS) {
		/* if any of the EP0 through EP15 is set, or bit 0 through 15 on disr */
		for (n = 0; n < (pCtrl->max_num_ep * 2); n++) {
			/* Check All Endpoints */
			if (disr & (1 << n)) {
				if ((n & 1) == 0) {
					/* OUT Endpoint */
					if ( val & USB_SETUP_RCVD ) {

						/* Setup packet is received. */
						*(drv->EPList_hw)   &= ~EP_STALL;	/* clear EP0_OUT stall */
						*(drv->EPList_hw + 2) &= ~(EP_STALL | BUF_ACTIVE);	/* clear EP0_IN stall */
						pCtrl->ep_event_hdlr[0](pCtrl, pCtrl->ep_hdlr_data[0], USB_EVT_SETUP);
						drv->regs->DEVCMDSTAT |= USB_SETUP_RCVD;
						continue;
					}
					/* Figure out if NAK caused the EP interrupt
					   IP3511 Does not identify NAK event on per EP basis. USB Info
					   register was saved when ISR was entered. Most likely it will reflect the
					   NAK status associated with the active EP interrupt. Interrupt on NAK
					   is enabled by calling hwUSB_EnableEvent */
					if ( (err == ERR_TX_RX_NAK) && ((drv->nak_on_ep & (1 << n)) != 0x0) ) {
						pCtrl->ep_event_hdlr[n](pCtrl, pCtrl->ep_hdlr_data[n], USB_EVT_OUT_NAK);
					}
					else {
						pCtrl->ep_event_hdlr[n](pCtrl, pCtrl->ep_hdlr_data[n], USB_EVT_OUT);
					}
				}	/* Out Endpoint */
				else {
					/* IN Endpoint */
					/* Figure out if NAK caused the EP interrupt
					   IP3511 Does not identify NAK event on per EP basis. USB Info
					   register was saved when ISR was entered. Most likely it will reflect the
					   NAK status associated with the active EP interrupt. Interrupt on NAK
					   is enabled by calling hwUSB_EnableEvent */
					if ( (err == ERR_TX_RX_NAK) && (drv->nak_on_ep & (1 << n)) ) {
						pCtrl->ep_event_hdlr[n](pCtrl, pCtrl->ep_hdlr_data[n], USB_EVT_IN_NAK);
					}
					else {
						pCtrl->ep_event_hdlr[n](pCtrl, pCtrl->ep_hdlr_data[n], USB_EVT_IN);
					}
				}	/* IN Endpoint */
			}	/* Active EP n Interrupt */
		}	/* For loop to process all endpoint interrupts */
			/* Endpoint Interrupts were cleared when ISR was entered */
	}	/* Active EP x interrupt */

	/* Start of Frame Interrupt */
	if (disr & FRAME_INT) {
		if (pCtrl->USB_SOF_Event) {
			pCtrl->USB_SOF_Event(hUsb);
		}
		/* SOF Interrupt was cleared when ISR was entered */
	}

	/* There is no interrupt for Error condition but error information was saved
	   when ISR was entered. Most likely it contains relevant information */
	if (pCtrl->USB_Error_Event) {
		switch ( err ) {
		case ERR_NOERROR:
		case ERR_TX_RX_NAK:
		case ERR_SENT_STALL:
			/* Ignore No Error, NAK and STALL conditions */
			break;

		default:
			/* Notify all other error conditions */
			pCtrl->USB_Error_Event(hUsb, err);
			/* Error conditions do not trigger Interrupt in IP3511 */
		}
	}

}
