/***********************************************************************
* $Id:: hw_usbd_ip3511.c 630 2012-12-06 19:17:47Z 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;
  volatile uint32_t ep_base; // Start address for non zero EP buffer 
  volatile uint32_t ep_cur;  // Current address for non zero EP buffer 
  // The ActiveFlag is to deal with STALL situation, if STALL happens during
  // WriteEP(), don't set the ACTIVE bit in EPList CommandStatus immediately but 
  // this flag only, ACTIVE bit will be set after ClearStallEP() that data is ready
  // to be sent. This is a bit mask flag for non-zero EPs.
  volatile uint32_t EPActiveFlag;
  // 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;

} USBD_HW_DATA_T;


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;

  /* calculate required length */
  req_len += ((2 * (param->max_num_ep)) * sizeof(EP_LIST));  /* 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 */
  req_len += sizeof(USB_CORE_CTRL_T);  /* memory for USBD controller structure */
  req_len += 63; /* for alignment overhead */
  req_len &= ~0x3f;
  /* EP 0 buffers */
  req_len += EP_ZERO_BUF_MAX_BYTES;
  /* Additional buffer requirements calculated in init function as NZ ep buffer 
     sizes are not known */

  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 (pCtrl->device_status & USB_GETSTATUS_REMOTE_WAKEUP)
  {
    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 */
	  // LPC_SYSCON->USBCLKCTRL = 1; Platform dependent !!!
  }
  else
  {
	  drv->regs->DEVCMDSTAT |= USB_PLL_ON;	/* NeedClk always "1" */
	  // LPC_SYSCON->USBCLKCTRL = 0; Platform dependent !!!
  }
}


/*
*  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;

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


/*
*  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)
{
  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 ( 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;
  }
  else
  {
	  /* TBD. 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; 
  }
}


/*
*  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_sz,j,i;
  uint32_t* addr;
  EP_LIST *EPList;

  EPList = drv->EPList;
  j = EPAdr(pEPD->bEndpointAddress) * 2;
  addr = drv->EPList_hw + j;

  /* For 64 byte alignment */
  ep_sz = pEPD->wMaxPacketSize;
  ep_sz += (64-1);
  ep_sz &= ~0x3F; 


  /* Each endpoint is Double buffered */
  for ( i = 0; i < 2; i++ )
  {
    if ( (pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_ISOCHRONOUS ) 
    {
      *addr = EP_DISABLED|EP_ISO_TYPE|(pEPD->wMaxPacketSize<<16)|(0xFFFF & (drv->ep_cur>>6));
    }
    else
    {
      *addr = EP_DISABLED|(pEPD->wMaxPacketSize<<16)|(0xFFFF & (drv->ep_cur>>6));
    }
 
    /* Remember EP buffer address and packet size */
    EPList[j].buf_ptr = drv->ep_cur;
    EPList[j].buf_length = pEPD->wMaxPacketSize;

    drv->ep_cur += ep_sz;
    addr++;
    j++;
  }
}


/*
*  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 &= ~EP_DISABLED;

  if ( EPNum & 0x0F )	/* Non-zero EPs */ 	
  {
	  if ( EPNum & 0x80 )		/* For non-zero IN */	
	  {
		  /* For non-zero double buffer EPs, clear EP_DISABLED to both buffer. */
		  addr++;
		  *addr &= ~EP_DISABLED;
	  }
	  else
	  {
      /* For non-zero EP OUT, in addition to clear EP_DISABLED bits,
      set the ACTIVE bit indicating that EP is ready to read. For
      double buffered EPs, set ACTIVE bit and clear EP_DISABLED bit
      for both buffer0 and 1. */
      *addr |= BUF_ACTIVE;
      /* For double buffer. */
      addr++;
      *addr &= ~EP_DISABLED;
      *addr |= 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;

  /* Release non zero endpoint buffer memory on any EP disable. Host will 
     call disable on all EPs in a particualr configuration. Thanks Durgesh */
  if ( EPNum & 0x0F )	/* Non-zero EPs only, EP0 should never be disabled. */
  {
    drv->ep_cur = drv->ep_base;
    addr = drv->EPList_hw + (EPAdr(EPNum) * 2);
    *addr |= EP_DISABLED;
    /* For double buffer */
    addr++;
    *addr |= 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;

  addr = drv->EPList_hw + (EPAdr(EPNum) * 2);
  /* 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 &= ~EP_STALL;
  *(addr+2) &= ~EP_STALL;
  if ( drv->regs->EPINUSE & (0x1<<EPAdr(EPNum)) )
  {
	  addr++;
  }
  *addr |= 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); // stall ep3_IN.
 * @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;

  addr = drv->EPList_hw + (EPAdr(EPNum) * 2);
  if ( (EPNum & 0x0F) == 0 )
  {
	  /* For EP0 IN or OUT, simply clear the ACTIVE and set the STALL bit. */
	  /* STALL bit can't be set until ACTIVE bit is gone. */
	  if ( *addr & BUF_ACTIVE )
	  {
	    USB_DeactivateEP (hUsb, EPNum);
	  }
	  *addr |= EP_STALL;
  }
  else
  {
	  /* For non-zero EPs, deactivate the EP first, make buffer inactive
	  before setting the STALL bit. It applies to both buffer 0 and 1 if
	  double buffer is enabled. */
	  if ( *addr & BUF_ACTIVE )
	  {
	    USB_DeactivateEP (hUsb, EPNum);
	  }
	  *addr |= EP_STALL;		/* STALL on buffer 0. */		
	  if ( drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum)) )
	  {
	    /* If double buffer is enabled, STALL on buffer 1. */ 
	    addr++;
	    if ( *addr & BUF_ACTIVE )
	    {
		    USB_DeactivateEP (hUsb, EPNum);
	    }
	  *addr |= 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;
  EP_LIST *EPList;

  EPList = drv->EPList;
  j = EPAdr(EPNum) * 2;
  addr = drv->EPList_hw + j;
  if ( (EPNum & 0x0F) == 0 )
  {
	  /* For EP0 IN and OUT, simply clear the STALL bit. */
	  *addr &= ~EP_STALL;
  }
  else
  {
	  /* For non-zero EPs if double buffer, both STALL bits should be cleared. */
	  *addr &= ~EP_STALL;
	  *(addr+1) &= ~EP_STALL;	

	  /* Based on EPInUse register to decide which buffer needs to toggle reset. */
	  if ( drv->regs->EPINUSE & (0x1<<EPAdr(EPNum)) )
	  {
	    /* Buffer 1 is in use. Toggle Reset Buffer 1, otherwise, toggle Reset
	    buffer 0. */
	    addr++;
	  }
	  *addr |= EP_RESET;
	  if ( !(EPNum & 0x80) )
	  {
	    /* For non-zero EP OUT, ACTIVE bit and length field need to 
	    be set again after clearing STALL. */
	    *addr &= ~(PKT_LNGTH_MASK<<16);
	    *addr |= ((EPList[j].buf_length<<16)| BUF_ACTIVE);
	  }
	  else
	  {
	    /* For non-zero EP IN, the EPActiveFlag will be set when WriteEP() happens 
	    while STALL is set. If so, when ClearSTALL happens, set the ACTIVE bit that
	    data buffer is ready to write data to the EPs. */
	    if ( drv->EPActiveFlag & (0x1U << EPAdr(EPNum)) )
	    {
		    *addr |= BUF_ACTIVE;
		    drv->EPActiveFlag &= ~(0x1U << EPAdr(EPNum));
	    }
	  }
  }

}


/*
*  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
*/
#pragma Otime
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;
  uint32_t *dataptr;
  EP_LIST *EPList;

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

  /* Check/Clear STALL on both EP0 IN and OUT when SETUP is received. */
  if ( (*addr & EP_STALL) || ((*addr+2) & EP_STALL) )
  {
	*addr &= ~EP_STALL;
	*(addr+2) &= ~EP_STALL;
  }

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

  addr++;		/* 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. */
  *addr &= ~0x3FFFFFF;		
  *addr |= (0xFFFF & (EPList[1].buf_ptr)>>6);
  return (cnt);
}


uint32_t hwUSB_ReadReqEP(USBD_HANDLE_T hUsb, uint32_t EPNum, uint8_t *pData, uint32_t len) 
{
  /* do nothing */
  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_flag;
  uint32_t *dataptr;
  uint32_t index;
  EP_LIST *EPList;

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

  if ( EPNum & 0x0F )
  {
    /* For EP0 IN/OUT, there is no double buffer. For non-zero
	     EP, double buffer is considered. If BufferUsed bit mask is set,
	     buffer0 is used, switch to buffer 1, index needs to be changed
	     accordingly too. */
	  buf_flag = drv->BufferUsed & (0x1U << EPAdr(EPNum));
	  if ( buf_flag != 0 )
	  {
	    addr++;		/* Use buffer 1 */
	    index++;
	  }
  }
  	
  cnt = (*addr >> 16) & PKT_LNGTH_MASK;
  /* The NBytes field is decremented by H/W with the packet byte each time. */
  cnt = EPList[index].buf_length - cnt;
  dataptr = (uint32_t *)EPList[index].buf_ptr;
  memcpy(pData,dataptr,cnt);

  /* Clear buffer after EP read, reset EP length and buffer pointer field */
  *addr &= ~0x3FFFFFF;
  if ( (EPNum & 0x0F) == 0 )
  {
	  /* EP0 is single buffer only. */		
	  *addr |= ((EPList[index].buf_length<<16) 
		| (0xFFFF & (EPList[index].buf_ptr)>>6) | BUF_ACTIVE);
  }
  else
  {
	  /* Toggle buffer if double buffer is used for non-zero EPs.  */
	  if ( drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum)) )
	  {
	    drv->BufferUsed = drv->regs->EPINUSE;
	    if ( drv->BufferUsed & (0x1U << EPAdr(EPNum)) )
	    {
		    addr++;		/* Set buffer 1 ACTIVE */
		    index++;
	    }
	    else
	    {
		    addr--;		/* Set buffer 0 ACTIVE */
		    index--;
	    }
	  }
	  *addr |= ((EPList[index].buf_length<<16) 
		| (0xFFFF & (EPList[index].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;

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

  if ( !(EPNum & 0x0F) )
  {
	  /* When EP0 IN is received, set ACTIVE bit on both EP0 IN
	  and OUT. */
	  *(addr-2) |= BUF_ACTIVE;	/* Set ACTIVE bit on EP0 OUT */
  }
  else
  {
    /* For non-zero EPs, if double buffer is used and EPInUse is set, buffer0 
    is used, otherwise, buffer1 is used. */
    if ( drv->regs->EPBUFCFG & (0x1U << EPAdr(EPNum)) )
    {
      if ( drv->regs->EPINUSE & (0x1U << EPAdr(EPNum)) )
      {
      addr++;		/* move to buffer1 address in EP command/status list. */
      index++;
      }
    }
  }

  /* Get EP command/status List, update the length field and data pointer. */
  *addr &= ~0x3FFFFFF;
  cnt &= PKT_LNGTH_MASK;
  *addr |= (cnt<<16)|( 0xFFFF & (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);

  if ( (*addr & EP_STALL) && (EPNum & 0x0F) )
  {
	  /* This is for MSC class when STALL occurs and non-zero EPs,
	  set the ACTIVE flag, but don't do anything until ClearFeature
	  to clear STALL, then tranfer the data. */
	  drv->EPActiveFlag |= (0x1U << EPAdr(EPNum));
	  return (cnt);
  }
  *addr |= BUF_ACTIVE;
  return (cnt);
}


/*
*  USB Reset Function
*   Called automatically on USB Reset
*    Return Value:    None
*/
#pragma Ospace
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,j;
  uint32_t *addr;
  EP_LIST *EPList;


  USB_SetSpeedMode(pCtrl, USB_FULL_SPEED);

  drv->BufferUsed = 0;
  drv->EPActiveFlag = 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 * pCtrl->max_num_ep * sizeof(EP_LIST));
  memset((void *)drv->EPList, 0, 2 * pCtrl->max_num_ep * sizeof(EP_LIST));

  /* 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 */
  j = 0;
  for (i = 0; i < 2; i++)
  {
    *addr = ((((USB_DEVICE_DESCRIPTOR*)(pCtrl->device_desc))->bMaxPacketSize0)<<16) | ( 0xFFFF & ((drv->ep_zero_base + (i * USB_MAX_PACKET0))>>6));
    EPList[j].buf_ptr = (drv->ep_zero_base + (i * USB_MAX_PACKET0));
    if ( i == 1 )
    {
      EPList[j].buf_length = sizeof(USB_SETUP_PACKET);
    }
    else
    {
      EPList[j].buf_length = ((USB_DEVICE_DESCRIPTOR*)(pCtrl->device_desc))->bMaxPacketSize0;
    }
    addr++;
    j++;
  }
  /* Reuse EP0 outbuf for EP0 IN as they are not used at the same time */
  *addr = ((((USB_DEVICE_DESCRIPTOR*)(pCtrl->device_desc))->bMaxPacketSize0)<<16) | ( 0xFFFF & ((drv->ep_zero_base)>>6));
  EPList[j].buf_ptr = drv->ep_zero_base;
    
  /* EP0 IN buffer 1 is reserved 
  *addr = 0;
  EPList[j].buf_ptr = 0;
  EPList[j].buf_length = 0; EPLists are already zeroed out */
  addr++;
  /* j++; No need to increment j as it is not used any more */
  
  /* Disable non zero endpoints. Buffer allocation is done in hwUSB_ConfigEP */
  for ( i = 4; i < (4 * pCtrl->max_num_ep); i++ )
  {
    *addr = EP_DISABLED;
    addr++;
  }
  /* Release NZ EP buffers */
  drv->ep_cur = drv->ep_base;

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

  /* Enable double buffer */
  drv->regs->EPBUFCFG = MAX_PHY_EP_INTS;

  /* Clear all EP interrupts, device status, and SOF interrupts. */
  drv->regs->INTSTAT = DEV_STAT_INT | FRAME_INT | MAX_PHY_EP_INTS;
  /* Enable all ten(10) 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;
  /* 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;
  uint32_t cfg_mem_bytes = 0, ep_sz, buff_sz = 0;

  /* check for memory alignment */
  /* 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 * (param->max_num_ep)) * sizeof(EP_LIST));
  mem_bytes += ((2 * (param->max_num_ep)) * sizeof(EP_LIST));

  /* Allocate memory for Software EPlist */
  EPList = (EP_LIST*)param->mem_base;
  param->mem_base += ((4 * (param->max_num_ep)) * sizeof(EP_LIST));
  mem_bytes += ((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);
  mem_bytes += sizeof(USBD_HW_DATA_T);
  /* align to 4 byte boundary */
  while (param->mem_base & 0x03) 
    {
      param->mem_base++;
      mem_bytes++;
    }
  /* allocate memory for USBD controller data structure */
  pCtrl = (USB_CORE_CTRL_T*)param->mem_base;
  param->mem_base += sizeof(USB_CORE_CTRL_T);
  mem_bytes += 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;

  /* !!!TODO vitual to physical translation */
  /* 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 */
  while (param->mem_base & (64 - 1)) 
    {
      param->mem_base++;
      mem_bytes++;
    }

  /* Endpoint zero requires 2 x 64 bytes buffers Due to IP3511 design */
  /* Hard coded entry */  
  drv->ep_zero_base = param->mem_base;
  param->mem_base += EP_ZERO_BUF_MAX_BYTES;
  mem_bytes+= EP_ZERO_BUF_MAX_BYTES;
  /* Initialize Non Zero EP data buffer pointers */
  drv->ep_base = param->mem_base;
// done in hwUSB_Reset  drv->ep_cur = param->mem_base;

  /* Calculate worst case buffer requirement for all non zero endpoints in all device configurations */
  /* Parse the descriptors. Thanks Durgesh */
  pD = pDesc->full_speed_desc; 
  while (((USB_CONFIGURATION_DESCRIPTOR *)pD)->bLength != 0) 
  {
    new_addr = (uint32_t)pD;
    pEpDesc = (USB_ENDPOINT_DESCRIPTOR*)new_addr;
    while(pEpDesc->bLength != 0 )
    {
      pEpDesc = (USB_ENDPOINT_DESCRIPTOR*)new_addr;
      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);
        }
        ep_sz = pEpDesc->wMaxPacketSize;
        ep_sz += 63;
        ep_sz &= ~0x3F; 
        cfg_mem_bytes += ep_sz;
      } /* pEpDesc->bDescriptorType == USB_ENDPOINT_DESCRIPTOR_TYPE */
   }
   if (buff_sz < cfg_mem_bytes)
   buff_sz = cfg_mem_bytes;
   /* Calculate non zero EP buffer requirement for next configuration */
   cfg_mem_bytes = 0;
   pD += ((USB_CONFIGURATION_DESCRIPTOR *)pD)->wTotalLength;
  }

  mem_bytes+= (2 * buff_sz);
  /* 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;
  hwUSB_Reset(pCtrl);
  hwUSB_SetAddress(pCtrl, 0);

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

  return LPC_OK;
}


/*
*  USB Interrupt Service Routine
*/
#pragma Otime
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;
    volatile unsigned x;


  disr = drv->regs->INTSTAT;         /* Get Interrupt Status and clear immediately. */
  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) 
  {
    val = drv->regs->DEVCMDSTAT;       /* Device Status */
    if (val & USB_DRESET_C)            /* Reset */
    {               
      drv->regs->DEVCMDSTAT |= USB_DRESET_C;
      hwUSB_Reset(hUsb);
      mwUSB_ResetCore(pCtrl);
      if (pCtrl->USB_Reset_Event)
      {
        pCtrl->USB_Reset_Event(hUsb);
      }
      goto isr_end;
    }
    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_DSUS_C)              /* Suspend/Resume */
    {
      drv->regs->DEVCMDSTAT |= USB_DSUS_C;
      if (val & USB_DSUS)                 /* 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 EP9 is set, or bit 0 through 9 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 ( drv->regs->DEVCMDSTAT & USB_SETUP_RCVD ) 
          { 											
            /* Setup packet is received. */
            *(drv->EPList_hw)   &= ~EP_STALL;  /* clear EP0_OUT stall */
            *(drv->EPList_hw+2) &= ~EP_STALL;  /* 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 */
    }
  }

isr_end:
  return;
}



