/*
===============================================================================
 Name        : usb_test.c
 Author      : $(author)
 Version     :
 Copyright   : $(copyright)
 Description : main definition
===============================================================================
*/

#include "board.h"
#include "app_usbd_cfg.h"

#include <cr_section_macros.h>

// TODO: insert other include files here

// TODO: insert other definitions and declarations here

/*****************************************************************************
 * Private types/enumerations/variables
 ****************************************************************************/
static USBD_HANDLE_T g_hUsb;

/* Endpoint 0 patch that prevents nested NAK event processing */
static uint32_t g_ep0RxBusy = 0;/* flag indicating whether EP0 OUT/RX buffer is busy. */
static USB_EP_HANDLER_T g_Ep0BaseHdlr;	/* variable to store the pointer to base EP0 handler */

#define EP1 2
#define EP2 2
#define IN 1
#define OUT 0

/* dTD field and bit defines */
#define TD_NEXT_TERMINATE       _BIT(0)
#define TD_IOC                  _BIT(15)
#define USB_DEST_ADDR           0x20004000

uint8_t g_data[61440];
int g_num = 0x57;

/* dTD Transfer Description */
 typedef volatile struct {
     volatile uint32_t next_dTD;
     volatile uint32_t total_bytes;
     volatile uint32_t buffer0;
     volatile uint32_t buffer1;
     volatile uint32_t buffer2;
     volatile uint32_t buffer3;
     volatile uint32_t buffer4;
     volatile uint32_t reserved;
 }  DTD_T;

/* dQH  Queue Head */
typedef volatile struct {
	volatile uint32_t cap;
	volatile uint32_t curr_dTD;
	volatile uint32_t next_dTD;
	volatile uint32_t total_bytes;
	volatile uint32_t buffer0;
	volatile uint32_t buffer1;
	volatile uint32_t buffer2;
	volatile uint32_t buffer3;
	volatile uint32_t buffer4;
	volatile uint32_t reserved;
	volatile uint32_t setup[2];
	volatile uint32_t gap[4];
}  DQH_T;

/*****************************************************************************
 * Public types/enumerations/variables
 ****************************************************************************/
const USBD_API_T *g_pUsbApi;

DTD_T *g_dtdPool[2];

/*****************************************************************************
 * Private functions
 ****************************************************************************/

/* EP0_patch part of WORKAROUND for artf45032. */
static ErrorCode_t EP0_patch(USBD_HANDLE_T hUsb, void *data, uint32_t event)
{
	switch (event) {
	case USB_EVT_OUT_NAK:
		if (g_ep0RxBusy) {
			/* we already queued the buffer so ignore this NAK event. */
			return LPC_OK;
		}
		else {
			/* Mark EP0_RX buffer as busy and allow base handler to queue the buffer. */
			g_ep0RxBusy = 1;
		}
		break;

	case USB_EVT_SETUP:	/* reset the flag when new setup sequence starts */
	case USB_EVT_OUT:
		/* we received the packet so clear the flag. */
		g_ep0RxBusy = 0;
		break;
	}
	return g_Ep0BaseHdlr(hUsb, data, event);
}

/* If there is any error then stop here forever. */
static void vCatchErr(uint8_t u8Err)
{
	volatile uint8_t u8theErr = u8Err;

	while (1) {}
}

/* USB Configure Event Callback. Called automatically after active
 * configuration is selected.
 */
static ErrorCode_t bwtest_ConfigureEvent(USBD_HANDLE_T hUsb)
{
/* Bulk OUT Endpoint 1 Event Callback.*/
	//bwtest_USB_Prog_DTD(EP2, IN);		/* EP2_IN */
	int cnt;
	cnt = USBD_API->hw->WriteEP(hUsb, 0x82, g_data, 0x7FC0);
	//cnt = USBD_API->hw->WriteEP(hUsb, 0x82, g_data, 0x7000);
	//cnt = USBD_API->hw->WriteEP(hUsb, 0x82, g_data, 0x1000);
	DEBUGOUT("%x\r\n", cnt);
	//USBD_API->hw->WriteEP(hUsb, 0x82, (uint8_t *)&g_num, 4);
    return LPC_OK;
}

static ErrorCode_t bwtest_Ep1OutHandler(USBD_HANDLE_T hUsb, void *data, uint32_t event)
{
	switch (event) {
	case USB_EVT_OUT:
        DEBUGOUT("EP OUT\n");
        break;
	case USB_EVT_OUT_NAK:
        DEBUGOUT("OUT NAK\n");
        break;
    }
    return LPC_OK;
}
/* Bulk IN Endpoint 2 Event Callback.*/
static ErrorCode_t bwtest_Ep2InHandler(USBD_HANDLE_T hUsb, void *data, uint32_t event)
{
	static uint32_t pkt_cnt = 0;
    USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
    if(pkt_cnt == 0) {
    	USBD_API->hw->WriteEP(hUsb, 0x82, g_data+0x7FC0, 0x7040);
    	pkt_cnt++;
    } else if(pkt_cnt == 1) {
    	USBD_API->hw->WriteEP(hUsb, 0x82, g_data, 0x0);
    	pkt_cnt++;
    } else {
    	USBD_API->hw->WriteEP(hUsb, 0x82, g_data, 0x7FC0);
    	pkt_cnt = 0;
    }
    //g_num++;
    //USBD_API->hw->WriteEP(hUsb, 0x82, (uint8_t *)&g_num, 4);
    return LPC_OK;
}
static ErrorCode_t bwtest_ep0_hdlr(USBD_HANDLE_T hUsb, void *data, uint32_t event)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	ErrorCode_t ret = ERR_USBD_UNHANDLED;

    return ret;
}

/*****************************************************************************
 * Public functions
 ****************************************************************************/

/* Handle interrupt from USB */
void USB1_IRQHandler(void)
{
	USBD_API->hw->ISR(g_hUsb);
}


int main(void) {

	USBD_API_INIT_PARAM_T usb_param;
	USB_CORE_DESCS_T desc;
	ErrorCode_t ret = LPC_OK;
	USB_CORE_CTRL_T *pCtrl;

    // Read clock settings and update SystemCoreClock variable
    SystemCoreClockUpdate();
    // Set up and initialize all required blocks and
    // functions related to the board hardware
    Board_Init();
    // Set the LED to the state of "On"
    Board_LED_Set(0, true);

    Chip_USB1_Init();

	/* Init USB API structure */
	g_pUsbApi = (const USBD_API_T *) LPC_ROM_API->usbdApiBase;

	/* initialize call back structures */
	memset((void *) &usb_param, 0, sizeof(USBD_API_INIT_PARAM_T));
	usb_param.usb_reg_base = LPC_USB1_BASE;
	usb_param.max_num_ep = USB_MAX_EP_NUM;
	usb_param.mem_base = USB_STACK_MEM_BASE;
	usb_param.mem_size = USB_STACK_MEM_SIZE;

	usb_param.USB_Configure_Event = bwtest_ConfigureEvent;

	/* Set the USB descriptors */
	desc.device_desc = (uint8_t *) USB_DeviceDescriptor;
	desc.string_desc = (uint8_t *) USB_StringDescriptor;
	/* Note, to pass USBCV test full-speed only devices should have both
	 * descriptor arrays point to same location and device_qualifier set
	 * to 0.
	 */
	desc.high_speed_desc = (uint8_t *) USB_FsConfigDescriptor;
	desc.full_speed_desc = (uint8_t *) USB_FsConfigDescriptor;
	desc.device_qualifier = 0;

	/* USB Initialization */
	ret = USBD_API->hw->Init(&g_hUsb, &desc, &usb_param);
	if (ret != LPC_OK) {
		vCatchErr(1);
	}

	/*	WORKAROUND for artf45032 ROM driver BUG:
	    Due to a race condition there is the chance that a second NAK event will
	    occur before the default endpoint0 handler has completed its preparation
	    of the DMA engine for the first NAK event. This can cause certain fields
	    in the DMA descriptors to be in an invalid state when the USB controller
	    reads them, thereby causing a hang.
	 */
	pCtrl = (USB_CORE_CTRL_T *) g_hUsb;	/* convert the handle to control structure */
	g_Ep0BaseHdlr = pCtrl->ep_event_hdlr[0];/* retrieve the default EP0_OUT handler */
	pCtrl->ep_event_hdlr[0] = EP0_patch;/* set our patch routine as EP0_OUT handler */

	/* allocate TDs for bandwidth test in USB accessable memory*/
	while (usb_param.mem_base & 0x1F) {
		usb_param.mem_base++; usb_param.mem_size--;
	}
	/* allocate DTDs for EP1_OUT */
	g_dtdPool[0] = (DTD_T *) usb_param.mem_base;
	usb_param.mem_base += (8 * sizeof(DTD_T));
	/* allocate DTDs for EP1_IN */
	g_dtdPool[1] = (DTD_T *) usb_param.mem_base;
	usb_param.mem_base += (8 * sizeof(DTD_T));

	/* update size for all four EPs */
	usb_param.mem_size -= (2 * 8 * sizeof(DTD_T));

	/* register EP1 handler */
	ret = USBD_API->core->RegisterEpHandler(g_hUsb, 2*EP1+OUT, bwtest_Ep1OutHandler, 0);
	if (ret != LPC_OK) {
		vCatchErr(2);
	}
	ret = USBD_API->core->RegisterEpHandler(g_hUsb, 2*EP2+IN, bwtest_Ep2InHandler, 0);
	if (ret != LPC_OK) {
		vCatchErr(3);
	}

	/* register WCID handler */
	ret = USBD_API->core->RegisterClassHandler(g_hUsb, bwtest_ep0_hdlr, 0);
	if (ret != LPC_OK) {
		vCatchErr(6);
	}
	/* enable USB0 interrrupts */
	NVIC_EnableIRQ(USB1_IRQn);

	/* now connect */
	USBD_API->hw->Connect(g_hUsb, 1);

	while (1) {
		__WFI();
	}

}
