// Copyright (c) 2010, Peter Barrett
/*
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/

#include "Arduino.h"
#include "USBAPI.h"
#include "Reset.h"
#include <stdio.h>
#include "PluggableUSB.h"
#include <stdint.h>

//#define TRACE_CORE(x)	x
#define TRACE_CORE(x)

uint32_t EndPoints[] =
{
	EP_TYPE_CONTROL,

#ifdef CDC_ENABLED
	EP_TYPE_INTERRUPT_IN,           // CDC_ENDPOINT_ACM
	EP_TYPE_BULK_OUT,               // CDC_ENDPOINT_OUT
	EP_TYPE_BULK_IN,                // CDC_ENDPOINT_IN
#endif

#ifdef PLUGGABLE_USB_ENABLED
	//allocate 6 endpoints and remove const so they can be changed by the user
	0,
	0,
	0,
	0,
	0,
	0,
#endif
};

/** Pulse generation counters to keep track of the number of milliseconds remaining for each pulse type */
#define TX_RX_LED_PULSE_MS 100
volatile uint8_t TxLEDPulse; /**< Milliseconds remaining for data Tx LED pulse */
volatile uint8_t RxLEDPulse; /**< Milliseconds remaining for data Rx LED pulse */
static char isRemoteWakeUpEnabled = 0;
static char isEndpointHalt = 0;
//==================================================================
//==================================================================

extern const uint16_t STRING_LANGUAGE[];
extern const uint8_t STRING_PRODUCT[];
extern const uint8_t STRING_MANUFACTURER[];
extern const DeviceDescriptor USB_DeviceDescriptor;
extern const DeviceDescriptor USB_DeviceDescriptorA;

const uint16_t STRING_LANGUAGE[2] = {
	(3<<8) | (2+2),
	0x0409	// English
};

#ifndef USB_PRODUCT
#define USB_PRODUCT "Arduino Due"
#endif

const uint8_t STRING_PRODUCT[] = USB_PRODUCT;

#ifndef USB_MANUFACTURER
#define USB_MANUFACTURER "Arduino LLC"
#endif

const uint8_t STRING_MANUFACTURER[] = USB_MANUFACTURER;

#ifdef CDC_ENABLED
#define DEVICE_CLASS 0x02
#else
#define DEVICE_CLASS 0x00
#endif

#ifdef COMPOSITE_DEVICE
#define DEFAULT_DEV_CLASS 0x00
#else
#define DEFAULT_DEV_CLASS 0x02
#endif

//	DEVICE DESCRIPTOR
const DeviceDescriptor USB_DeviceDescriptor =
	D_DEVICE(DEFAULT_DEV_CLASS,0x00,0x00,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);

const DeviceDescriptor USB_DeviceDescriptorA =
	D_DEVICE(0xEF,0x02,0x01,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);

const QualifierDescriptor USB_DeviceQualifier =
	D_QUALIFIER(0x00,0x00,0x00,64,1);

//! 7.1.20 Test Mode Support
static const unsigned char test_packet_buffer[] = {
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,                // JKJKJKJK * 9
    0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,                     // JJKKJJKK * 8
    0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,                     // JJJJKKKK * 8
    0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // JJJJJJJKKKKKKK * 8
    0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,                          // JJJJJJJK * 8
    0xFC,0x7E,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0x7E                 // {JKKKKKKK * 10}, JK
};

//==================================================================
//==================================================================

volatile uint32_t _usbConfiguration = 0;
volatile uint32_t _usbInitialized = 0;
uint32_t _usbSetInterface = 0;
uint32_t _cdcComposite = 0;

//==================================================================
//==================================================================

#define USB_RECV_TIMEOUT
class LockEP
{
	irqflags_t flags;
public:
	LockEP(uint32_t ep __attribute__ ((unused))) : flags(cpu_irq_save())
	{
	}
	~LockEP()
	{
		cpu_irq_restore(flags);
	}
};

//	Number of bytes, assumes a rx endpoint
uint32_t USBD_Available(uint32_t ep)
{
	LockEP lock(ep);
	return UDD_FifoByteCount(ep & 0xF);
}

//	Non Blocking receive
//	Return number of bytes read
uint32_t USBD_Recv(uint32_t ep, void* d, uint32_t len)
{
	if (!_usbConfiguration)
		return -1;

	LockEP lock(ep);
	uint32_t n = UDD_FifoByteCount(ep & 0xF);
	len = min(n,len);
	n = len;
	uint8_t* dst = (uint8_t*)d;
	while (n--)
		*dst++ = UDD_Recv8(ep & 0xF);
	if (len && !UDD_FifoByteCount(ep & 0xF)) // release empty buffer
		UDD_ReleaseRX(ep & 0xF);

	return len;
}

//	Recv 1 byte if ready
uint32_t USBD_Recv(uint32_t ep)
{
	uint8_t c;
	if (USBD_Recv(ep & 0xF, &c, 1) != 1)
		return -1;
	else
		return c;
}

//	Space in send EP
//uint32_t USBD_SendSpace(uint32_t ep)
//{
	//LockEP lock(ep);
////	if (!UDD_ReadWriteAllowed(ep & 0xF))
    ////{
        ////printf("pb "); // UOTGHS->UOTGHS_DEVEPTISR[%d]=0x%X\n\r", ep, UOTGHS->UOTGHS_DEVEPTISR[ep]);
		////return 0;
    ////}

    //if(ep==0) return 64 - UDD_FifoByteCount(ep & 0xF);  // EP0_SIZE  jcb
    //else return 512 - UDD_FifoByteCount(ep & 0xF);  // EPX_SIZE  jcb
//}

//	Blocking Send of data to an endpoint
uint32_t USBD_Send(uint32_t ep, const void* d, uint32_t len)
{
    uint32_t n;
	int r = len;
	const uint8_t* data = (const uint8_t*)d;

    if (!_usbConfiguration)
    {
    	TRACE_CORE(printf("pb conf\n\r");)
		return -1;
    }

	while (len)
	{
        if(ep==0) n = EP0_SIZE;
        else n =  EPX_SIZE;
		if (n > len)
			n = len;
		len -= n;

		int count=0;
		while( UOTGHS_DEVEPTISR_TXINI != (UOTGHS->UOTGHS_DEVEPTISR[ep & 0xF] & UOTGHS_DEVEPTISR_TXINI ))
		{
			count++;
			if (count>10000) return len;
		}

		UDD_Send(ep & 0xF, data, n);
		data += n;
    }
	//TXLED1;					// light the TX LED
	//TxLEDPulse = TX_RX_LED_PULSE_MS;
	return r;
}

uint16_t _cmark;
uint16_t _cend;

void USBD_InitControl(int end)
{
	_cmark = 0;
	_cend = end;
}

//	Clipped by _cmark/_cend
int USBD_SendControl(uint8_t flags __attribute__ ((unused)), const void* d, uint32_t len)
{
	const uint8_t* data = (const uint8_t*)d;
	uint32_t length = len;
	uint32_t sent = 0;
	uint32_t pos = 0;

	TRACE_CORE(printf("=> USBD_SendControl TOTAL len=%lu\r\n", len);)

	if (_cmark < _cend)
	{
		while (len > 0)
		{
			sent = UDD_Send(EP0, data + pos, len);
			TRACE_CORE(printf("=> USBD_SendControl sent=%lu\r\n", sent);)
			pos += sent;
			len -= sent;
		}
	}

	_cmark += length;

	return length;
}

// Send a USB descriptor string. The string is stored as a
// plain ASCII string but is sent out as UTF-16 with the
// correct 2-byte prefix
static bool USB_SendStringDescriptor(const uint8_t *string, int wLength) {
	if (wLength < 2)
		return false;

	uint8_t buffer[wLength];
	buffer[0] = strlen((const char*)string) * 2 + 2;
	buffer[1] = 0x03;

	uint8_t i;
	for (i = 2; i < wLength && *string; i++) {
		buffer[i++] = *string++;
		if (i == wLength) break;
		buffer[i] = 0;
	}

	return USBD_SendControl(0, (uint8_t*)buffer, i);
}

//	Does not timeout or cross fifo boundaries
//	Will only work for transfers <= 64 bytes
//	TODO
int USBD_RecvControl(void* d, uint32_t len)
{
	UDD_WaitOUT();
	UDD_Recv(EP0, (uint8_t*)d, len);
	UDD_ClearOUT();

	return len;
}

//	Handle CLASS_INTERFACE requests
bool USBD_ClassInterfaceRequest(USBSetup& setup)
{
	uint8_t i = setup.wIndex;

	TRACE_CORE(printf("=> USBD_ClassInterfaceRequest\r\n");)

#ifdef CDC_ENABLED
	if (CDC_ACM_INTERFACE == i)
	{
		return CDC_Setup(setup);
	}
#endif

#ifdef PLUGGABLE_USB_ENABLED
	return PluggableUSB().setup(setup);
#endif

	return false;
}

uint8_t USBD_SendInterfaces(void)
{
	uint8_t interfaces = 0;

#ifdef CDC_ENABLED
	CDC_GetInterface(&interfaces);
#endif

#ifdef PLUGGABLE_USB_ENABLED
	PluggableUSB().getInterface(&interfaces);
#endif

	TRACE_CORE(printf("=> USBD_SendInterfaces, interfaces=%d\r\n", interfaces);)
	return interfaces;
}

uint8_t USBD_SendOtherInterfaces(void)
{
	uint8_t interfaces = 0;

#ifdef CDC_ENABLED
	CDC_GetOtherInterface(&interfaces);
#endif

#ifdef PLUGGABLE_USB_ENABLED
	PluggableUSB().getInterface(&interfaces);
#endif

	TRACE_CORE(printf("=> USBD_SendInterfaces, interfaces=%d\r\n", interfaces);)
	return interfaces;
}

//	Construct a dynamic configuration descriptor
//	This really needs dynamic endpoint allocation etc
//	TODO
static bool USBD_SendConfiguration(int maxlen)
{
	//	Count and measure interfaces
	USBD_InitControl(0);
	//TRACE_CORE(printf("=> USBD_SendConfiguration _cmark1=%d\r\n", _cmark);)
	uint8_t interfaces = USBD_SendInterfaces();
	//TRACE_CORE(printf("=> USBD_SendConfiguration _cmark2=%d\r\n", _cmark);)
	//TRACE_CORE(printf("=> USBD_SendConfiguration sizeof=%d\r\n", sizeof(ConfigDescriptor));)

_Pragma("pack(1)")
	ConfigDescriptor config = D_CONFIG(_cmark + sizeof(ConfigDescriptor),interfaces);
_Pragma("pack()")
	//TRACE_CORE(printf("=> USBD_SendConfiguration clen=%d\r\n", config.clen);)

	//TRACE_CORE(printf("=> USBD_SendConfiguration maxlen=%d\r\n", maxlen);)

	//	Now send them
	USBD_InitControl(maxlen);
	USBD_SendControl(0,&config,sizeof(ConfigDescriptor));
	USBD_SendInterfaces();
	return true;
}

static bool USBD_SendOtherConfiguration(int maxlen)
{
	//	Count and measure interfaces
	USBD_InitControl(0);
	//TRACE_CORE(printf("=> USBD_SendConfiguration _cmark1=%d\r\n", _cmark);)
	uint8_t interfaces = USBD_SendOtherInterfaces();
	//TRACE_CORE(printf("=> USBD_SendConfiguration _cmark2=%d\r\n", _cmark);)
	//TRACE_CORE(printf("=> USBD_SendConfiguration sizeof=%d\r\n", sizeof(ConfigDescriptor));)

_Pragma("pack(1)")
	ConfigDescriptor config = D_OTHERCONFIG(_cmark + sizeof(ConfigDescriptor),interfaces);
_Pragma("pack()")
	//TRACE_CORE(printf("=> USBD_SendConfiguration clen=%d\r\n", config.clen);)

	//TRACE_CORE(printf("=> USBD_SendConfiguration maxlen=%d\r\n", maxlen);)

	//	Now send them
	USBD_InitControl(maxlen);
	USBD_SendControl(0,&config,sizeof(ConfigDescriptor));
	USBD_SendOtherInterfaces();
	return true;
}

static bool USBD_SendDescriptor(USBSetup& setup)
{
	uint8_t t = setup.wValueH;
	uint8_t desc_length = 0;
	int ret = 0;
	const uint8_t* desc_addr = 0;

	if (USB_CONFIGURATION_DESCRIPTOR_TYPE == t)
	{
		TRACE_CORE(printf("=> USBD_SendDescriptor : USB_CONFIGURATION_DESCRIPTOR_TYPE length=%d\r\n", setup.wLength);)
		return USBD_SendConfiguration(setup.wLength);
	}

	USBD_InitControl(setup.wLength);

#ifdef PLUGGABLE_USB_ENABLED
	ret = PluggableUSB().getDescriptor(setup);
	if (ret != 0) {
		return (ret > 0 ? true : false);
	}
#endif

	if (USB_DEVICE_DESCRIPTOR_TYPE == t)
	{
		TRACE_CORE(puts("=> USBD_SendDescriptor : USB_DEVICE_DESCRIPTOR_TYPE\r\n");)
		if (setup.wLength == 8)
		{
			_cdcComposite = 1;
		}
		desc_addr = _cdcComposite ?  (const uint8_t*)&USB_DeviceDescriptorA : (const uint8_t*)&USB_DeviceDescriptor;
        if( *desc_addr > setup.wLength ) {
            desc_length = setup.wLength;
        }
	}
	else if (USB_STRING_DESCRIPTOR_TYPE == t)
	{
		TRACE_CORE(puts("=> USBD_SendDescriptor : USB_STRING_DESCRIPTOR_TYPE\r\n");)
		if (setup.wValueL == 0) {
			desc_addr = (const uint8_t*)&STRING_LANGUAGE;
		}
		else if (setup.wValueL == IPRODUCT) {
			return USB_SendStringDescriptor(STRING_PRODUCT, setup.wLength);
		}
		else if (setup.wValueL == IMANUFACTURER) {
			return USB_SendStringDescriptor(STRING_MANUFACTURER, setup.wLength);
		}
		else if (setup.wValueL == ISERIAL) {
#ifdef PLUGGABLE_USB_ENABLED
			char name[ISERIAL_MAX_LEN];
			PluggableUSB().getShortName(name);
			return USB_SendStringDescriptor((uint8_t*)name, setup.wLength);
#endif
		}
		else {
			return false;
		}
		if( *desc_addr > setup.wLength ) {
			desc_length = setup.wLength;
		}
	}
	else if (USB_DEVICE_QUALIFIER == t)
	{
		// Device qualifier descriptor requested
		desc_addr = (const uint8_t*)&USB_DeviceQualifier;
        if( *desc_addr > setup.wLength ) {
            desc_length = setup.wLength;
        }
    }
    else if (USB_OTHER_SPEED_CONFIGURATION == t)
    {
		// Other configuration descriptor requested
		return USBD_SendOtherConfiguration(setup.wLength);
    }
    else
    {
        //printf("Device ERROR");
    }

	if (desc_addr == 0)
	{
		return false;
	}

	if (desc_length == 0)
	{
		desc_length = *desc_addr;
	}

	TRACE_CORE(printf("=> USBD_SendDescriptor : desc_addr=%p desc_length=%d\r\n", desc_addr, desc_length);)
	USBD_SendControl(0, desc_addr, desc_length);

	return true;
}


static void USB_SendZlp( void )
{
    while( UOTGHS_DEVEPTISR_TXINI != (UOTGHS->UOTGHS_DEVEPTISR[0] & UOTGHS_DEVEPTISR_TXINI ) )
    {
        if((UOTGHS->UOTGHS_DEVISR & UOTGHS_DEVISR_SUSP) == UOTGHS_DEVISR_SUSP)
        {
            return;
        }
    }
    UOTGHS->UOTGHS_DEVEPTICR[0] = UOTGHS_DEVEPTICR_TXINIC;
}


static void Test_Mode_Support( uint8_t wIndex )
{
    uint8_t i;
	uint8_t *ptr_dest = (uint8_t *) &udd_get_endpoint_fifo_access8(2);

	switch( wIndex )
	{
		case 4:
			//Test mode Test_Packet:
			//Upon command, a port must repetitively transmit the following test packet until
			//the exit action is taken. This enables the testing of rise and fall times, eye
			//patterns, jitter, and any other dynamic waveform specifications.
			//The test packet is made up by concatenating the following strings.
			//(Note: For J/K NRZI data, and for NRZ data, the bit on the left is the first one
			//transmitted. "S" indicates that a bit stuff occurs, which inserts an "extra" NRZI data bit.
			//"* N" is used to indicate N occurrences of a string of bits or symbols.)
			//A port in Test_Packet mode must send this packet repetitively. The inter-packet timing
			//must be no less than the minimum allowable inter-packet gap as defined in Section 7.1.18 and
			//no greater than 125 us.

			// Send ZLP
			USB_SendZlp();

			UOTGHS->UOTGHS_DEVDMA[0].UOTGHS_DEVDMACONTROL = 0; // raz
			UOTGHS->UOTGHS_DEVDMA[1].UOTGHS_DEVDMACONTROL = 0; // raz

			// Configure endpoint 2, 64 bytes, direction IN, type BULK, 1 bank
			UOTGHS->UOTGHS_DEVEPTCFG[2] = UOTGHS_DEVEPTCFG_EPSIZE_64_BYTE
												 | UOTGHS_DEVEPTCFG_EPDIR_IN
												 | UOTGHS_DEVEPTCFG_EPTYPE_BLK
												 | UOTGHS_DEVEPTCFG_EPBK_1_BANK;
			// Check if the configuration is ok
			UOTGHS->UOTGHS_DEVEPTCFG[2] |= UOTGHS_DEVEPTCFG_ALLOC;
			while((UOTGHS->UOTGHS_DEVEPTISR[2]&UOTGHS_DEVEPTISR_CFGOK)==0) {}
			UOTGHS->UOTGHS_DEVEPT |= UOTGHS_DEVEPT_EPEN2;
			// Write FIFO
			for( i=0; i<sizeof(test_packet_buffer); i++)
			{
				ptr_dest[i] = test_packet_buffer[i];;
			}
			// Tst PACKET
			UOTGHS->UOTGHS_DEVCTRL |= UOTGHS_DEVCTRL_TSTPCKT;
			// Send packet
			UOTGHS->UOTGHS_DEVEPTICR[2] = UOTGHS_DEVEPTICR_TXINIC;
			UOTGHS->UOTGHS_DEVEPTIDR[2] = UOTGHS_DEVEPTIDR_FIFOCONC;
			for(;;);
//      break;

		case 1:
			//Test mode Test_J:
			//Upon command, a port's transceiver must enter the high-speed J state and remain in that
			//state until the exit action is taken. This enables the testing of the high output drive
			//level on the D+ line.
			// Send a ZLP
			USB_SendZlp();
			UOTGHS->UOTGHS_DEVCTRL |= UOTGHS_DEVCTRL_TSTJ;
			for(;;);
//      break;

		case 2:
			//Test mode Test_K:
			//Upon command, a port's transceiver must enter the high-speed K state and remain in
			//that state until the exit action is taken. This enables the testing of the high output drive
			//level on the D- line.
			// Send a ZLP
			USB_SendZlp();
			UOTGHS->UOTGHS_DEVCTRL |= UOTGHS_DEVCTRL_TSTK;
			for(;;);
//		break;

		case 3:
			//Test mode Test_SE0_NAK:
			//Upon command, a port's transceiver must enter the high-speed receive mode
			//and remain in that mode until the exit action is taken. This enables the testing
			//of output impedance, low level output voltage, and loading characteristics.
			//In addition, while in this mode, upstream facing ports (and only upstream facing ports)
			//must respond to any IN token packet with a NAK handshake (only if the packet CRC is
			//determined to be correct) within the normal allowed device response time. This enables testing of
			//the device squelch level circuitry and, additionally, provides a general purpose stimulus/response
			//test for basic functional testing.

			// Send a ZLP
			USB_SendZlp();
			UOTGHS->UOTGHS_DEVIDR = UOTGHS_DEVIDR_SUSPEC
							   | UOTGHS_DEVIDR_MSOFEC
							   | UOTGHS_DEVIDR_SOFEC
							   | UOTGHS_DEVIDR_EORSTEC
							   | UOTGHS_DEVIDR_WAKEUPEC
							   | UOTGHS_DEVIDR_EORSMEC
							   | UOTGHS_DEVIDR_UPRSMEC
							   | UOTGHS_DEVIDR_PEP_0
							   | UOTGHS_DEVIDR_PEP_1
							   | UOTGHS_DEVIDR_PEP_2
							   | UOTGHS_DEVIDR_PEP_3
							   | UOTGHS_DEVIDR_PEP_4
							   | UOTGHS_DEVIDR_PEP_5
							   | UOTGHS_DEVIDR_PEP_6
							   | UOTGHS_DEVIDR_DMA_1
							   | UOTGHS_DEVIDR_DMA_2
							   | UOTGHS_DEVIDR_DMA_3
							   | UOTGHS_DEVIDR_DMA_4
							   | UOTGHS_DEVIDR_DMA_5
							   | UOTGHS_DEVIDR_DMA_6;
			for(;;);
//		break;
	}
}


//unsigned int iii=0;
//	Endpoint 0 interrupt
static void USB_ISR(void)
{
//    printf("ISR=0x%X\n\r", UOTGHS->UOTGHS_DEVISR); // jcb
//    if( iii++ > 1500 ) while(1); // jcb
    // End of bus reset
    if (Is_udd_reset())
    {
		TRACE_CORE(printf(">>> End of Reset\r\n");)

		// Reset USB address to 0
		udd_configure_address(0);
		udd_enable_address();

		// Configure EP 0
        UDD_InitEP(0, EP_TYPE_CONTROL);
		udd_enable_setup_received_interrupt(0);
		udd_enable_endpoint_interrupt(0);

        _usbConfiguration = 0;
		udd_ack_reset();
    }

#ifdef CDC_ENABLED
  	if (Is_udd_endpoint_interrupt(CDC_RX))
	{
		udd_ack_out_received(CDC_RX);

		// Handle received bytes
		if (USBD_Available(CDC_RX))
			SerialUSB.accept();
	}

	if (Is_udd_sof())
	{
		udd_ack_sof();
	//	USBD_Flush(CDC_TX); // jcb
	}
#endif

	// EP 0 Interrupt
	if (Is_udd_endpoint_interrupt(0) )
	{
		if (!UDD_ReceivedSetupInt())
		{
			return;
		}

		USBSetup setup;
		UDD_Recv(EP0, (uint8_t*)&setup, 8);
		UDD_ClearSetupInt();

		uint8_t requestType = setup.bmRequestType;
		if (requestType & REQUEST_DEVICETOHOST)
		{
			TRACE_CORE(puts(">>> EP0 Int: IN Request\r\n");)
			UDD_WaitIN();
		}
		else
		{
			TRACE_CORE(puts(">>> EP0 Int: OUT Request\r\n");)
			UDD_ClearIN();
		}

		bool ok = true;
		if (REQUEST_STANDARD == (requestType & REQUEST_TYPE))
		{
			// Standard Requests
			uint8_t r = setup.bRequest;
			if (GET_STATUS == r)
			{
                if( setup.bmRequestType == 0 )  // device
                {
                    // Send the device status
     				TRACE_CORE(puts(">>> EP0 Int: GET_STATUS\r\n");)
                    // Check current configuration for power mode (if device is configured)
                    // TODO
                    // Check if remote wake-up is enabled
                    // TODO
                    UDD_Send8(EP0, 0); // TODO
	    			UDD_Send8(EP0, 0);
                }
                // if( setup.bmRequestType == 2 ) // Endpoint:
                else
                {
                    // Send the endpoint status
                    // Check if the endpoint if currently halted
                    if( isEndpointHalt == 1 )
    				UDD_Send8(EP0, 1); // TODO
                    else
    				UDD_Send8(EP0, 0); // TODO
	    			UDD_Send8(EP0, 0);
                }
			}
			else if (CLEAR_FEATURE == r)
			{
               // Check which is the selected feature
                if( setup.wValueL == 1) // DEVICEREMOTEWAKEUP
                {
                    // Enable remote wake-up and send a ZLP
                    if( isRemoteWakeUpEnabled == 1 )
	    			UDD_Send8(EP0, 1);
                    else
	    			UDD_Send8(EP0, 0);
                    UDD_Send8(EP0, 0);
                }
                else // if( setup.wValueL == 0) // ENDPOINTHALT
                {
                    isEndpointHalt = 0;  // TODO
    				UDD_Send8(EP0, 0);
	    			UDD_Send8(EP0, 0);
                }

 			}
			else if (SET_FEATURE == r)
			{
                // Check which is the selected feature
                if( setup.wValueL == 1) // DEVICEREMOTEWAKEUP
                {
                    // Enable remote wake-up and send a ZLP
                    isRemoteWakeUpEnabled = 1;
	    			UDD_Send8(EP0, 0);
                }
                if( setup.wValueL == 0) // ENDPOINTHALT
                {
                    // Halt endpoint
                    isEndpointHalt = 1;
                    //USBD_Halt(USBGenericRequest_GetEndpointNumber(pRequest));
	    			UDD_Send8(EP0, 0);
                }
                if( setup.wValueL == 2) // TEST_MODE
                {
                    // 7.1.20 Test Mode Support, 9.4.9 SetFeature
                    if( (setup.bmRequestType == 0 /*USBGenericRequest_DEVICE*/) &&
                        ((setup.wIndex & 0x000F) == 0) )
                    {
                        // the lower byte of wIndex must be zero
                        // the most significant byte of wIndex is used to specify the specific test mode

                        UOTGHS->UOTGHS_DEVIDR &= ~UOTGHS_DEVIDR_SUSPEC;
                        UOTGHS->UOTGHS_DEVCTRL |= UOTGHS_DEVCTRL_SPDCONF_HIGH_SPEED; // remove suspend ?

                        Test_Mode_Support( (setup.wIndex & 0xFF00)>>8 );
                    }
                }
			}
			else if (SET_ADDRESS == r)
			{
				TRACE_CORE(puts(">>> EP0 Int: SET_ADDRESS\r\n");)
				UDD_WaitIN();
				UDD_SetAddress(setup.wValueL);
			}
			else if (GET_DESCRIPTOR == r)
			{
				TRACE_CORE(puts(">>> EP0 Int: GET_DESCRIPTOR\r\n");)
				ok = USBD_SendDescriptor(setup);
			}
			else if (SET_DESCRIPTOR == r)
			{
				TRACE_CORE(puts(">>> EP0 Int: SET_DESCRIPTOR\r\n");)
				ok = false;
			}
			else if (GET_CONFIGURATION == r)
			{
				TRACE_CORE(puts(">>> EP0 Int: GET_CONFIGURATION\r\n");)
				UDD_Send8(EP0, _usbConfiguration);
			}
			else if (SET_CONFIGURATION == r)
			{
				if (REQUEST_DEVICE == (requestType & REQUEST_RECIPIENT))
				{
					TRACE_CORE(printf(">>> EP0 Int: SET_CONFIGURATION REQUEST_DEVICE %d\r\n", setup.wValueL);)

					uint32_t num_endpoints = 0;
					while (EndPoints[num_endpoints] != 0) {
						num_endpoints++;
					}
					UDD_InitEndpoints(EndPoints, num_endpoints);
					_usbConfiguration = setup.wValueL;

#ifdef CDC_ENABLED
					// Enable interrupt for CDC reception from host (OUT packet)
					udd_enable_out_received_interrupt(CDC_RX);
					udd_enable_endpoint_interrupt(CDC_RX);
#endif
				}
				else
				{
					TRACE_CORE(puts(">>> EP0 Int: SET_CONFIGURATION failed!\r\n");)
					ok = false;
				}
			}
			else if (GET_INTERFACE == r)
			{
				TRACE_CORE(puts(">>> EP0 Int: GET_INTERFACE\r\n");)
				UDD_Send8(EP0, _usbSetInterface);
			}
			else if (SET_INTERFACE == r)
			{
                _usbSetInterface = setup.wValueL;
				TRACE_CORE(puts(">>> EP0 Int: SET_INTERFACE\r\n");)
			}
		}
		else
		{
			TRACE_CORE(puts(">>> EP0 Int: ClassInterfaceRequest\r\n");)

			UDD_WaitIN(); // Workaround: need tempo here, else CDC serial won't open correctly

			USBD_InitControl(setup.wLength); // Max length of transfer
			ok = USBD_ClassInterfaceRequest(setup);
		}

		if (ok)
		{
			TRACE_CORE(puts(">>> EP0 Int: Send packet\r\n");)
			UDD_ClearIN();
		}
		else
		{
			TRACE_CORE(puts(">>> EP0 Int: Stall\r\n");)
			UDD_Stall();
		}
	}
}

void USBD_Flush(uint32_t ep)
{
	if (UDD_FifoByteCount(ep))
		UDD_ReleaseTX(ep);
}

//	VBUS or counting frames
//	Any frame counting?
uint32_t USBD_Connected(void)
{
	uint8_t f = UDD_GetFrameNumber();

	delay(3);

	return f != UDD_GetFrameNumber();
}


//=======================================================================
//=======================================================================

USBDevice_ USBDevice;

USBDevice_::USBDevice_()
{
	UDD_SetStack(&USB_ISR);

	if (UDD_Init() == 0UL)
	{
		_usbInitialized=1UL;
	}
}

bool USBDevice_::attach(void)
{
  if (_usbInitialized != 0UL)
  {
    UDD_Attach();
	_usbConfiguration = 0;
	return true;
  }
  else
  {
    return false;
  }
}

bool USBDevice_::detach(void)
{
	if (_usbInitialized != 0UL)
	{
		UDD_Detach();
		return true;
	}
	else
	{
		return false;
	}
}

//	Check for interrupts
//	TODO: VBUS detection
bool USBDevice_::configured()
{
	return _usbConfiguration;
}

void USBDevice_::poll()
{
}
