/*******************************************************************************
 Copyright (c) 2014, Infineon Technologies AG                                 **
 All rights reserved.                                                         **
 **
 Redistribution and use in source and binary forms, with or without           **
 modification,are permitted provided that the following conditions are met:   **
 **
 *Redistributions of source code must retain the above copyright notice,      **
 this list of conditions and the following disclaimer.                        **
 *Redistributions in binary form must reproduce the above copyright notice,   **
 this list of conditions and the following disclaimer in the documentation    **
 and/or other materials provided with the distribution.                       **
 *Neither the name of the copyright holders nor the names of its contributors **
 may be used to endorse or promote products derived from this software without**
 specific prior written permission.                                           **
 **
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  **
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE    **
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   **
 ARE  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE   **
 LIABLE  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR         **
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF         **
 SUBSTITUTE GOODS OR  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS    **
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN      **
 CONTRACT, STRICT LIABILITY,OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)       **
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE   **
 POSSIBILITY OF SUCH DAMAGE.                                                  **
 **
 To improve the quality of the software, users are encouraged to share        **
 modifications, enhancements or bug fixes with Infineon Technologies AG       **
 dave@infineon.com).                                                          **
 *******************************************************************************/


/**
 * @file WinUSB.c
 *
 * @brief winusb device framework
 */

#include "WinUSB.h"

/**
 * Buffer to hold transmit and receive data
 **/
uint8_t tx_buf[TX_BUF_SIZE];
uint8_t rx_buf[RX_BUF_SIZE];


/* USB runtime structure*/
XMC_USBD_t USB_runtime =
{
  .usbd = USB0,
  .usbd_max_num_eps = XMC_USBD_MAX_NUM_EPS_6,
  .usbd_transfer_mode = XMC_USBD_USE_DMA,
  .cb_xmc_device_event = USBD_SignalDeviceEventHandler,
  .cb_endpoint_event = USBD_SignalEndpointEvent_Handler

};


/*WinUSB endpoint info data structure*/
USBD_WINUSB_Info_t WinUSBInfo = {
  .Config = {
    .InEndpoint = {
		.Address = ENDPOINT_DIR_IN | WINUSB_TX_EPNUM,
		.Size = WINUSB_TXRX_EPSIZE,
		.Type = EP_TYPE_BULK
},
.OutEndpoint = {
		.Address = ENDPOINT_DIR_OUT | WINUSB_RX_EPNUM,
		.Size = WINUSB_TXRX_EPSIZE,
		.Type = EP_TYPE_BULK
}
}
};




/*******************************************************************************
**                     Public Function Definitions                            **
*******************************************************************************/

void USB0_0_IRQHandler(void)
{
  XMC_USBD_IRQHandler(&USB_runtime);
}

/*The function initializes the USB core layer and register call backs. */
void USB_Init(void)
{
  USBD_Initialize(&USB_runtime);

  /* Interrupts configuration*/
  NVIC_SetPriority(USB0_0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 63, 0));
  NVIC_ClearPendingIRQ(USB0_0_IRQn);
  NVIC_EnableIRQ(USB0_0_IRQn);

  /* USB Connection*/
  USB_Attach();

}



/*The WinUSB API to write data to USB host*/
USBD_WINUSB_Status_t USBD_WINUSB_WriteData(uint8_t ep, const void *Buffer, uint16_t Length)
{
USBD_WINUSB_Status_t Status = USBD_WINUSB_STATUS_SUCCESS;
uint8_t sts;

/*make sure it is IN endpoint*/
ep |= ENDPOINT_DIR_MASK;

if (Length > 0)
{
	do
	{
		/* leave if device is not configured */
		if (USB_DeviceState != DEVICE_STATE_Configured)
		{
			Status =  USBD_WINUSB_STATUS_NOT_READY;
			break;
		}

		/* Selecet winusb in endpoint */
		Endpoint_SelectEndpoint(ep);

		/* Leave if endpoint is not ready */
		if (!Endpoint_IsINReady())
		{
			Status = USBD_WINUSB_STATUS_NOT_READY;
			break;
		}

		/* write data to endpoint and then flush the data */
		sts = Endpoint_Write_Stream_LE(Buffer,Length,NULL);
		if (sts != ENDPOINT_RWSTREAM_NoError )
		{
			Status = USBD_WINUSB_STATUS_FAILURE;
			break;
		}
		Endpoint_ClearIN();
	}while(0);
}
else
{
	Status = USBD_WINUSB_STATUS_FAILURE;
}
return Status;
}

/*The WinUSB API to read data from USB host*/
USBD_WINUSB_Status_t USBD_WINUSB_ReadData(uint8_t ep,void *Buffer,uint16_t Length)
{
	USBD_WINUSB_Status_t Status = USBD_WINUSB_STATUS_SUCCESS;
	uint8_t sts;

	if (Length > 0)
	{
		do
		{
			/* Leave if device is not configured */
			if (USB_DeviceState != DEVICE_STATE_Configured)
			{
				Status = (USBD_WINUSB_Status_t)ENDPOINT_RWSTREAM_DeviceDisconnected;
				break;
			}

			/* Select the winusb out endpoint */
			Endpoint_SelectEndpoint(ep);

			/* leave if no out is received */
			if (true != Endpoint_IsOUTReceived())
			{
				Status = USBD_WINUSB_STATUS_NOT_READY;
				break;
			}

			/* read data from endpoint and start new read operation */
			sts = Endpoint_Read_Stream_LE(Buffer,Length,NULL);
			if (sts != ENDPOINT_RWSTREAM_NoError) 
			{
				Status = USBD_WINUSB_STATUS_FAILURE;
				break;
			}
			Endpoint_ClearOUT();
		}while(0);
	}
	else
	{
		Status = USBD_WINUSB_STATUS_FAILURE;
	}
	return Status;
}

/*The WinUSB API to check the data reception from USB host*/
uint16_t USBD_WINUSB_BytesReceived(uint8_t ep)
{
	uint16_t bytes = 0;
	/* Leave if device is not configured */
	if (USB_DeviceState == DEVICE_STATE_Configured)
	{
		/* Select the winusb out endpoint */
		Endpoint_SelectEndpoint(ep);

		/* check if we have out received and if we have data. if is empty out
		 * packet, flush */
		if (true == Endpoint_IsOUTReceived())
		{
			if (0 == Endpoint_BytesInEndpoint())
			{
				Endpoint_ClearOUT();
				bytes = 0;
			}
			else
			{
				bytes = Endpoint_BytesInEndpoint();
			}
		}

	}

	return bytes;
}

/*The API to configure the USB end points*/
void USBD_WINUSB_ConfigureEndpoints(USBD_WINUSB_Info_t* const WinUSBInfo)
{
	/* configure in endpoint */
	Endpoint_ConfigureEndpointTable(&WinUSBInfo->Config.InEndpoint,1);
	/* configure out endpoint */
	Endpoint_ConfigureEndpointTable(&WinUSBInfo->Config.OutEndpoint,1);
}

/*The USB device control request call back.
 * This can be used by the customers to handle the class/vendor specific
 * USB requests
 * */
void EVENT_USB_Device_ControlRequest()
{
	uint16_t Length = 0;
	uint16_t index = USB_ControlRequest.wIndex;

	/* Serve Microsoft vendor control request */
	if (MS_GET_DESCRIPTOR == (USB_ControlRequest.bmRequestType & REQTYPE_VENDOR && USB_ControlRequest.bRequest))
	{
		/* Send comapt_ID descriptor for automatic winusb detection */
		if (Extended_Compat_ID == index)
		{
			Endpoint_ClearSETUP();

			if (USB_ControlRequest.wLength < Compat_ID.dwLength)
			{
				Length = USB_ControlRequest.wLength;
			}
			else
			{
				Length = Compat_ID.dwLength;
			}

			Endpoint_Write_Control_Stream_LE(&Compat_ID,Length);
		}
		/* Send Extended Properties to get allways the same device GUID */
		if ((USB_ControlRequest.bmRequestType & REQREC_INTERFACE) && (Extended_Properties == index))
		{
			Endpoint_ClearSETUP();

			if (USB_ControlRequest.wLength < Properties.dwLength)
			{
				Length = USB_ControlRequest.wLength;
			}
			else
			{
				Length = Properties.dwLength;
			}
			Endpoint_Write_Control_Stream_LE(&Properties,Length);
		}
	}
}

/*
 * USB device configuration changed event call back.
 * Here we reconfigure the USB end points.
 * */
void EVENT_USB_Device_ConfigurationChanged()
{
	uint8_t i;
	/* Clear configured flag */
	for (i = 1;i < USB_runtime.usbd_max_num_eps;i++)
	{
		device.Endpoints[i].IsConfigured = 0;
	}
	USBD_SetEndpointBuffer(WinUSBInfo.Config.InEndpoint.Address,tx_buf,TX_BUF_SIZE);
	USBD_SetEndpointBuffer(WinUSBInfo.Config.OutEndpoint.Address,rx_buf,RX_BUF_SIZE);
	USBD_WINUSB_ConfigureEndpoints(&WinUSBInfo);

	/* Unconfigure all enabled but not used endpoints */
	for (i = 1;i < USB_runtime.usbd_max_num_eps;i++)
	{
		USBD_Endpoint_t *ep = &device.Endpoints[i];
		if ((0U == ep->IsConfigured) && (1U == ep->IsEnabled))
		{
			device.Driver->EndpointUnconfigure(ep->Number);
		}
	}
	device.IsConfigured = 1;
	USB_DeviceState = DEVICE_STATE_Configured;
}

/** Event handler for the library USB Disconnection event. */
void EVENT_USB_Device_Reset(void)
{
  if(device.IsConfigured)
  {
    USB_Init();
    device.IsConfigured=0;
  }
}