/**
 * \file
 *
 * \brief USB Device wrapper layer for compliance with common driver UDD
 *
 * Copyright (c) 2014-2020 Microchip Technology Inc. and its subsidiaries.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip
 * software and any derivatives exclusively with Microchip products.
 * It is your responsibility to comply with third party license terms applicable
 * to your use of third party software (including open source software) that
 * may accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
 * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
 * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
 * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
 * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
 * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
 * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
 * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE FULLEST EXTENT
 * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
 * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
 * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="https://www.microchip.com/support/">Microchip Support</a>
 */
#include <string.h>
#include <stdlib.h>

#include "rtos.h"
#include "sam_usb.h"

// Get USB device configuration
#include "usb_config.h"
#include "udd.h"

/**
 * \ingroup usb_device_group
 * \defgroup usb_device_udd_group USB Device Driver Implement (UDD)
 * USB low-level driver for USB device mode
 * @{
 */
// Check USB device configuration
#ifdef USB_DEVICE_HS_SUPPORT
#  error The High speed mode is not supported on this part, please remove USB_DEVICE_HS_SUPPORT in conf_usb.h
#endif

//#if !(SAMD21) && !(SAMR21) && !(SAMD11) && !(SAML21) && !(SAML22) && !(SAMDA1) && !(SAMR30) && !(SAMR34) && !(WLR089)
//# error The current USB Device Driver supports only SAMD21/R21/D11/L21/L22/DA1/R34
//#endif

#ifndef UDC_REMOTEWAKEUP_LPM_ENABLE
#define UDC_REMOTEWAKEUP_LPM_ENABLE()
#endif
#ifndef UDC_REMOTEWAKEUP_LPM_DISABLE
#define UDC_REMOTEWAKEUP_LPM_DISABLE()
#endif
#ifndef UDC_SUSPEND_LPM_EVENT
#define UDC_SUSPEND_LPM_EVENT()
#endif

/* for debug text */
#ifdef USB_DEBUG
#   define dbg_print printf
#else
#   define dbg_print(...)
#endif

/** Maximum size of a transfer in multi-packet mode */
#define UDD_ENDPOINT_MAX_TRANS ((8*1024)-1)

/** USB software device instance structure */
struct usb_module usb_device;

/**
 * \name Clock management
 *
 * @{
 */
static inline void udd_wait_clock_ready(void)
{

#define DFLL_READY_FLAG (OSCCTRL_STATUS_DFLLRDY | \
		OSCCTRL_STATUS_DFLLLCKF | OSCCTRL_STATUS_DFLLLCKC)

	/* In USB recovery mode the status is not checked */
	if (!(OSCCTRL->DFLLCTRLB.reg & OSCCTRL_DFLLCTRLB_USBCRM)) {
		while((OSCCTRL->STATUS.reg & DFLL_READY_FLAG) != DFLL_READY_FLAG);
	} else {
		while((OSCCTRL->STATUS.reg & OSCCTRL_STATUS_DFLLRDY) != OSCCTRL_STATUS_DFLLRDY);
	}
}
/** @} */

/**
 * \name Power management
 *
 * @{
 */
#  define udd_sleep_mode(arg) do {/* Nothing */} while (/*CONSTCOND*/0)
/** @} */

/**
 * \name Control endpoint low level management routine.
 *
 * This function performs control endpoint management.
 * It handles the SETUP/DATA/HANDSHAKE phases of a control transaction.
 *
 * @{
 */

/**
 * \brief Buffer to store the data received on control endpoint (SETUP/OUT endpoint 0)
 *
 * Used to avoid a RAM buffer overflow in case of the payload buffer
 * is smaller than control endpoint size
 */
uint8_t udd_ctrl_buffer[USB_DEVICE_EP_CTRL_SIZE] ALIGNED(16);

/** Bit definitions about endpoint control state machine for udd_ep_control_state */
typedef enum {
	UDD_EPCTRL_SETUP                  = 0, //!< Wait a SETUP packet
	UDD_EPCTRL_DATA_OUT               = 1, //!< Wait a OUT data packet
	UDD_EPCTRL_DATA_IN                = 2, //!< Wait a IN data packet
	UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP  = 3, //!< Wait a IN ZLP packet
	UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP = 4, //!< Wait a OUT ZLP packet
	UDD_EPCTRL_STALL_REQ              = 5, //!< STALL enabled on IN & OUT packet
} udd_ctrl_ep_state_t;

/** Global variable to give and record information of the set up request management */
udd_ctrl_request_t udd_g_ctrlreq ALIGNED(16);

/** State of the endpoint control management */
static udd_ctrl_ep_state_t udd_ep_control_state;

/** Total number of data received/sent during data packet phase with previous payload buffers */
static uint16_t udd_ctrl_prev_payload_nb_trans;

/** Number of data received/sent to/from udd_g_ctrlreq.payload buffer */
static uint16_t udd_ctrl_payload_nb_trans;

/** @} */

/**
 * \name Management of bulk/interrupt/isochronous endpoints
 *
 * The UDD manages the data transfer on endpoints:
 * - Start data transfer on endpoint with USB Device DMA
 * - Send a ZLP packet if requested
 * - Call callback registered to signal end of transfer
 * The transfer abort and stall feature are supported.
 *
 * @{
 */

/**
 * \brief Buffer to store the data received on bulk/interrupt endpoints
 *
 * Used to avoid a RAM buffer overflow in case of the user buffer
 * is smaller than endpoint size
 *
 * \warning The protected interrupt endpoint size is 512 bytes maximum.
 * \warning The isochronous and endpoint is not protected by this system and
 *          the user must always use a buffer corresponding at endpoint size.
 */
#if (defined USB_DEVICE_LOW_SPEED)
uint8_t udd_ep_out_cache_buffer[USB_DEVICE_MAX_EP][8] ALIGNED(16);
#else
uint8_t udd_ep_out_cache_buffer[USB_DEVICE_MAX_EP][64] ALIGNED(16);
#endif

/** Structure definition about job registered on an endpoint */
typedef struct {
	union {
		//! Callback to call at the end of transfer
		udd_callback_trans_t call_trans;
		//! Callback to call when the endpoint halt is cleared
		udd_callback_halt_cleared_t call_nohalt;
	};
	//! Buffer located in internal RAM to send or fill during job
	uint8_t *buf;
	//! Size of buffer to send or fill
	iram_size_t buf_size;
	//! Total number of data transferred on endpoint
	iram_size_t nb_trans;
	//! Endpoint size
	uint16_t ep_size;
	//! A job is registered on this endpoint
	uint8_t busy:1;
	//! A short packet is requested for this job on endpoint IN
	uint8_t b_shortpacket:1;
	//! The cache buffer is currently used on endpoint OUT
	uint8_t b_use_out_cache_buffer:1;
} udd_ep_job_t;

/** Array to register a job on bulk/interrupt/isochronous endpoint */
static udd_ep_job_t udd_ep_job[2 * USB_DEVICE_MAX_EP];

/** @} */

/**
 * \brief     Get the detailed job by endpoint number
 * \param[in] ep  Endpoint Address
 * \retval    pointer to an udd_ep_job_t structure instance
 */
static udd_ep_job_t* udd_ep_get_job(udd_ep_id_t ep)
{
	if ((ep == 0) || (ep == 0x80)) {
		return NULL;
	} else {
		return &udd_ep_job[(2 * (ep & USB_EP_ADDR_MASK) + ((ep & USB_EP_DIR_IN) ? 1 : 0)) - 2];
	}
}

/**
 * \brief     Endpoint IN process, continue to send packets or zero length packet
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void udd_ep_trans_in_next(void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;
	udd_ep_id_t ep = ep_callback_para->endpoint_address;
	uint16_t ep_size, nb_trans;
	uint16_t next_trans;
	udd_ep_id_t ep_num;
	udd_ep_job_t *ptr_job;

	ptr_job = udd_ep_get_job(ep);
	ep_num = ep & USB_EP_ADDR_MASK;

	ep_size = ptr_job->ep_size;
	/* Update number of data transferred */
	nb_trans = ep_callback_para->sent_bytes;
	ptr_job->nb_trans += nb_trans;

	/* Need to send other data */
	if (ptr_job->nb_trans != ptr_job->buf_size) {
		next_trans = ptr_job->buf_size - ptr_job->nb_trans;
		if (UDD_ENDPOINT_MAX_TRANS < next_trans) {
		/* The USB hardware support a maximum
		 * transfer size of UDD_ENDPOINT_MAX_TRANS Bytes */
			next_trans = UDD_ENDPOINT_MAX_TRANS -(UDD_ENDPOINT_MAX_TRANS % ep_size);
		}
		/* Need ZLP, if requested and last packet is not a short packet */
		ptr_job->b_shortpacket = ptr_job->b_shortpacket && (0 == (next_trans % ep_size));
		usb_device_endpoint_write_buffer_job(&usb_device,ep_num,&ptr_job->buf[ptr_job->nb_trans],next_trans);
		return;
	}

	/* Need to send a ZLP after all data transfer */
	if (ptr_job->b_shortpacket) {
		ptr_job->b_shortpacket = false;
		/* Start new transfer */
		usb_device_endpoint_write_buffer_job(&usb_device,ep_num,&ptr_job->buf[ptr_job->nb_trans],0);
		return;
	}

	/* Job complete then call callback */
	ptr_job->busy = false;
	if (NULL != ptr_job->call_trans) {
		ptr_job->call_trans(UDD_EP_TRANSFER_OK, ptr_job->nb_trans, ep);
	}
}

/**
 * \brief     Endpoint OUT process, continue to receive packets or zero length packet
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void udd_ep_trans_out_next(void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;
	udd_ep_id_t ep = ep_callback_para->endpoint_address;
	uint16_t ep_size, nb_trans;
	uint16_t next_trans;
	udd_ep_id_t ep_num;
	udd_ep_job_t *ptr_job;

	ptr_job = udd_ep_get_job(ep);
	ep_num = ep & USB_EP_ADDR_MASK;

	ep_size = ptr_job->ep_size;
	/* Update number of data transferred */
	nb_trans = ep_callback_para->received_bytes;

	/* Can be necessary to copy data receive from cache buffer to user buffer */
	if (ptr_job->b_use_out_cache_buffer) {
		memcpy(&ptr_job->buf[ptr_job->nb_trans], udd_ep_out_cache_buffer[ep_num - 1], ptr_job->buf_size % ep_size);
	}

	/* Update number of data transferred */
	ptr_job->nb_trans += nb_trans;
	if (ptr_job->nb_trans > ptr_job->buf_size) {
		ptr_job->nb_trans = ptr_job->buf_size;
	}

	/* If all previous data requested are received and user buffer not full
	 * then need to receive other data */
	if ((nb_trans == ep_callback_para->out_buffer_size) && (ptr_job->nb_trans != ptr_job->buf_size)) {
		next_trans = ptr_job->buf_size - ptr_job->nb_trans;
		if (UDD_ENDPOINT_MAX_TRANS < next_trans) {
		/* The USB hardware support a maximum transfer size
		 * of UDD_ENDPOINT_MAX_TRANS Bytes */
		next_trans = UDD_ENDPOINT_MAX_TRANS - (UDD_ENDPOINT_MAX_TRANS % ep_size);
		} else {
			next_trans -= next_trans % ep_size;
		}

		if (next_trans < ep_size) {
			/* Use the cache buffer for Bulk or Interrupt size endpoint */
			ptr_job->b_use_out_cache_buffer = true;
			usb_device_endpoint_read_buffer_job(&usb_device,ep_num,udd_ep_out_cache_buffer[ep_num - 1],ep_size);
		} else {
			usb_device_endpoint_read_buffer_job(&usb_device,ep_num,&ptr_job->buf[ptr_job->nb_trans],next_trans);
		}
		return;
	}

	/* Job complete then call callback */
	ptr_job->busy = false;
	if (NULL != ptr_job->call_trans) {
		ptr_job->call_trans(UDD_EP_TRANSFER_OK, ptr_job->nb_trans, ep);
	}
}

/**
 * \brief     Endpoint Transfer Complete callback function, to do the next transfer depends on the direction(IN or OUT)
 * \param[in] module_inst Pointer to USB module instance
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void udd_ep_transfer_process(struct usb_module *module_inst, void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;
	udd_ep_id_t ep = ep_callback_para->endpoint_address;

	if (ep & USB_EP_DIR_IN) {
		udd_ep_trans_in_next(pointer);
	} else {
		udd_ep_trans_out_next(pointer);
	}

	(void) module_inst;
}

void udd_ep_abort(udd_ep_id_t ep)
{
	udd_ep_job_t *ptr_job;

	usb_device_endpoint_abort_job(&usb_device, ep);

	/* Job complete then call callback */
	ptr_job = udd_ep_get_job(ep);
	if (!ptr_job->busy) {
		return;
	}
	ptr_job->busy = false;
	if (NULL != ptr_job->call_trans) {
		/* It can be a Transfer or stall callback */
		ptr_job->call_trans(UDD_EP_TRANSFER_ABORT, ptr_job->nb_trans, ep);
	}
}

bool udd_is_high_speed(void)
{
	return false;
}

uint16_t udd_get_frame_number(void)
{
	return usb_device_get_frame_number(&usb_device);
}

uint16_t udd_get_micro_frame_number(void)
{
	return usb_device_get_micro_frame_number(&usb_device);
}

void udd_ep_free(udd_ep_id_t ep)
{
	struct usb_device_endpoint_config config_ep;
	usb_device_endpoint_get_config_defaults(&config_ep);

	uint8_t ep_num = ep & USB_EP_ADDR_MASK;
	udd_ep_abort(ep);

	config_ep.ep_address = ep;
	config_ep.ep_type = USB_DEVICE_ENDPOINT_TYPE_DISABLE;
	usb_device_endpoint_set_config(&usb_device, &config_ep);
	usb_device_endpoint_unregister_callback(&usb_device,ep_num,USB_DEVICE_ENDPOINT_CALLBACK_TRCPT);
	usb_device_endpoint_disable_callback(&usb_device,ep,USB_DEVICE_ENDPOINT_CALLBACK_TRCPT);
}

bool udd_ep_alloc(udd_ep_id_t ep, uint8_t bmAttributes, uint16_t MaxEndpointSize)
{
	struct usb_device_endpoint_config config_ep;
	usb_device_endpoint_get_config_defaults(&config_ep);

	config_ep.ep_address = ep;

	if(MaxEndpointSize <= 8) {
		config_ep.ep_size = USB_ENDPOINT_8_BYTE;
	} else if(MaxEndpointSize <= 16) {
		config_ep.ep_size = USB_ENDPOINT_16_BYTE;
	} else if(MaxEndpointSize <= 32) {
		config_ep.ep_size = USB_ENDPOINT_32_BYTE;
	} else if(MaxEndpointSize <= 64) {
		config_ep.ep_size = USB_ENDPOINT_64_BYTE;
	} else if(MaxEndpointSize <= 128) {
		config_ep.ep_size = USB_ENDPOINT_128_BYTE;
	} else if(MaxEndpointSize <= 256) {
		config_ep.ep_size = USB_ENDPOINT_256_BYTE;
	} else if(MaxEndpointSize <= 512) {
		config_ep.ep_size = USB_ENDPOINT_512_BYTE;
	} else if(MaxEndpointSize <= 1023) {
		config_ep.ep_size = USB_ENDPOINT_1023_BYTE;
	} else {
		return false;
	}
	udd_ep_job_t *ptr_job = udd_ep_get_job(ep);
	ptr_job->ep_size = MaxEndpointSize;

	bmAttributes = bmAttributes & USB_EP_TYPE_MASK;

	/* Check endpoint type */
	if(USB_EP_TYPE_ISOCHRONOUS == bmAttributes) {
		config_ep.ep_type = USB_DEVICE_ENDPOINT_TYPE_ISOCHRONOUS;
	} else if (USB_EP_TYPE_BULK == bmAttributes) {
		config_ep.ep_type = USB_DEVICE_ENDPOINT_TYPE_BULK;
	} else if (USB_EP_TYPE_INTERRUPT == bmAttributes) {
		config_ep.ep_type = USB_DEVICE_ENDPOINT_TYPE_INTERRUPT;
	} else {
		return false;
	}

	uint8_t ep_num = ep & USB_EP_ADDR_MASK;

	if (STATUS_OK != usb_device_endpoint_set_config(&usb_device, &config_ep)) {
		return false;
	}
	usb_device_endpoint_register_callback(&usb_device,ep_num,USB_DEVICE_ENDPOINT_CALLBACK_TRCPT,udd_ep_transfer_process);
	usb_device_endpoint_enable_callback(&usb_device,ep,USB_DEVICE_ENDPOINT_CALLBACK_TRCPT);
//	usb_device_endpoint_enable_callback(&usb_device,ep,USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL);

	return true;
}

bool udd_ep_is_halted(udd_ep_id_t ep)
{
	return usb_device_endpoint_is_halted(&usb_device, ep);
}

bool udd_ep_set_halt(udd_ep_id_t ep)
{
	uint8_t ep_num = ep & USB_EP_ADDR_MASK;

	if (USB_DEVICE_MAX_EP < ep_num) {
		return false;
	}

	usb_device_endpoint_set_halt(&usb_device, ep);

	udd_ep_abort(ep);
	return true;
}

bool udd_ep_clear_halt(udd_ep_id_t ep)
{
	udd_ep_job_t *ptr_job;
	uint8_t ep_num = ep & USB_EP_ADDR_MASK;

	if (USB_DEVICE_MAX_EP < ep_num) {
		return false;
	}
	ptr_job = udd_ep_get_job(ep);

	usb_device_endpoint_clear_halt(&usb_device, ep);

	/* If a job is register on clear halt action then execute callback */
	if (ptr_job->busy == true) {
		ptr_job->busy = false;
		ptr_job->call_nohalt();
	}

	return true;
}

bool udd_ep_wait_stall_clear(udd_ep_id_t ep, udd_callback_halt_cleared_t callback)
{
	udd_ep_id_t ep_num;
	udd_ep_job_t *ptr_job;

	ep_num = ep & USB_EP_ADDR_MASK;
	if (USB_DEVICE_MAX_EP < ep_num) {
		return false;
	}

	ptr_job = udd_ep_get_job(ep);
	if (ptr_job->busy == true) {
		return false; /* Job already on going */
	}

	/* Wait clear halt endpoint */
	if (usb_device_endpoint_is_halted(&usb_device, ep)) {
		/* Endpoint halted then registers the callback */
		ptr_job->busy = true;
		ptr_job->call_nohalt = callback;
		return true;
	} else if (usb_device_endpoint_is_configured(&usb_device, ep)) {
		callback(); /* Endpoint not halted then call directly callback */
		return true;
	} else {
		return false;
	}
}

/**
 * \brief Control Endpoint stall sending data
 */
static void udd_ctrl_stall_data(void)
{
	udd_ep_control_state = UDD_EPCTRL_STALL_REQ;

	usb_device_endpoint_set_halt(&usb_device, USB_EP_DIR_IN);
	usb_device_endpoint_clear_halt(&usb_device, USB_EP_DIR_OUT);
}

bool udd_ep_run(udd_ep_id_t ep, bool b_shortpacket, uint8_t * buf, iram_size_t buf_size, udd_callback_trans_t callback)
{
	udd_ep_id_t ep_num;
	udd_ep_job_t *ptr_job;
	rtos_saved_ipl_t ipl;

	ep_num = ep & USB_EP_ADDR_MASK;

	if ((USB_DEVICE_MAX_EP < ep_num) || (udd_ep_is_halted(ep))) {
		return false;
	}

	ptr_job = udd_ep_get_job(ep);

	ipl = rtos_ipl_raise(HW_IPL_USB);
	if (ptr_job->busy == true) {
		rtos_ipl_restore(ipl);
		return false; /* Job already on going */
	}
	ptr_job->busy = true;
	rtos_ipl_restore(ipl);

	/* No job running, set up a new one */
	ptr_job->buf = buf;
	ptr_job->buf_size = buf_size;
	ptr_job->nb_trans = 0;
	ptr_job->call_trans = callback;
	ptr_job->b_shortpacket = b_shortpacket;
	ptr_job->b_use_out_cache_buffer = false;

	/* Initialize value to simulate a empty transfer */
	uint16_t next_trans;

	if (ep & USB_EP_DIR_IN) {
		if (0 != ptr_job->buf_size) {
			next_trans = ptr_job->buf_size;
			if (UDD_ENDPOINT_MAX_TRANS < next_trans) {
				next_trans = UDD_ENDPOINT_MAX_TRANS -
						(UDD_ENDPOINT_MAX_TRANS % ptr_job->ep_size);
			}
			ptr_job->b_shortpacket = ptr_job->b_shortpacket &&
					(0 == (next_trans % ptr_job->ep_size));
		} else if (true == ptr_job->b_shortpacket) {
			ptr_job->b_shortpacket = false; /* avoid to send zero length packet again */
			next_trans = 0;
		} else {
			ptr_job->busy = false;
			if (NULL != ptr_job->call_trans) {
				ptr_job->call_trans(UDD_EP_TRANSFER_OK, 0, ep);
			}
			return true;
		}
		return (STATUS_OK ==
				usb_device_endpoint_write_buffer_job(&usb_device,
						ep_num,&ptr_job->buf[0],next_trans));
	} else {
		if (0 != ptr_job->buf_size) {
			next_trans = ptr_job->buf_size;
			if (UDD_ENDPOINT_MAX_TRANS < next_trans) {
				/* The USB hardware support a maximum transfer size
				 * of UDD_ENDPOINT_MAX_TRANS Bytes */
				next_trans = UDD_ENDPOINT_MAX_TRANS -
						(UDD_ENDPOINT_MAX_TRANS % ptr_job->ep_size);
			} else {
				next_trans -= next_trans % ptr_job->ep_size;
			}
			if (next_trans < ptr_job->ep_size) {
				ptr_job->b_use_out_cache_buffer = true;
				return (STATUS_OK ==
						usb_device_endpoint_read_buffer_job(&usb_device, ep_num,
								udd_ep_out_cache_buffer[ep_num - 1],
								ptr_job->ep_size));
			} else {
				return (STATUS_OK ==
						usb_device_endpoint_read_buffer_job(&usb_device, ep_num,
								&ptr_job->buf[0],next_trans));
			}
		} else {
			ptr_job->busy = false;
			if (NULL != ptr_job->call_trans) {
				ptr_job->call_trans(UDD_EP_TRANSFER_OK, 0, ep);
			}
			return true;
		}
	}
}

void udd_set_address(uint8_t address)
{
	usb_device_set_address(&usb_device,address);
}

uint8_t udd_getaddress(void)
{
	return usb_device_get_address(&usb_device);
}

void udd_send_remotewakeup(void)
{
	uint32_t try = 5;
	udd_wait_clock_ready();
	udd_sleep_mode(UDD_STATE_IDLE);
	while(2 != usb_get_state_machine_status(&usb_device) && try --) {
		usb_device_send_remote_wake_up(&usb_device);
	}
}

void udd_set_setup_payload( uint8_t *payload, uint16_t payload_size )
{
	udd_g_ctrlreq.payload = payload;
	udd_g_ctrlreq.payload_size = payload_size;
}

/**
 * \brief Control Endpoint translate the data in buffer into Device Request Struct
 */
static void udd_ctrl_fetch_ram(void)
{
	udd_g_ctrlreq.req.bmRequestType = udd_ctrl_buffer[0];
	udd_g_ctrlreq.req.bRequest = udd_ctrl_buffer[1];
	udd_g_ctrlreq.req.wValue = ((uint16_t)(udd_ctrl_buffer[3]) << 8) + udd_ctrl_buffer[2];
	udd_g_ctrlreq.req.wIndex = ((uint16_t)(udd_ctrl_buffer[5]) << 8) + udd_ctrl_buffer[4];
	udd_g_ctrlreq.req.wLength = ((uint16_t)(udd_ctrl_buffer[7]) << 8) + udd_ctrl_buffer[6];
}

/**
 * \brief Control Endpoint send out zero length packet
 */
static void udd_ctrl_send_zlp_in(void)
{
	udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP;
	usb_device_endpoint_setup_buffer_job(&usb_device,udd_ctrl_buffer);
	usb_device_endpoint_write_buffer_job(&usb_device,0,udd_g_ctrlreq.payload,0);
}

/**
 * \brief Process control endpoint IN transaction
 */
static void udd_ctrl_in_sent(void)
{
	static bool b_shortpacket = false;
	uint16_t nb_remain;

	nb_remain = udd_g_ctrlreq.payload_size - udd_ctrl_payload_nb_trans;

	if (0 == nb_remain) {
		/* All content of current buffer payload are sent Update number of total data sending by previous payload buffer */
		udd_ctrl_prev_payload_nb_trans += udd_ctrl_payload_nb_trans;
		if ((udd_g_ctrlreq.req.wLength == udd_ctrl_prev_payload_nb_trans) || b_shortpacket) {
			/* All data requested are transferred or a short packet has been sent, then it is the end of data phase.
			 * Generate an OUT ZLP for handshake phase */
			udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP;
			usb_device_endpoint_setup_buffer_job(&usb_device,udd_ctrl_buffer);
			return;
		}
		/* Need of new buffer because the data phase is not complete */
		if ((!udd_g_ctrlreq.over_under_run) || (!udd_g_ctrlreq.over_under_run())) {
			/* Under run then send zlp on IN
			 * Here nb_remain=0, this allows to send a IN ZLP */
		} else {
			/* A new payload buffer is given */
			udd_ctrl_payload_nb_trans = 0;
			nb_remain = udd_g_ctrlreq.payload_size;
		}
	}

	/* Continue transfer and send next data */
	if (nb_remain >= USB_DEVICE_EP_CTRL_SIZE) {
		nb_remain = USB_DEVICE_EP_CTRL_SIZE;
		b_shortpacket = false;
	} else {
		b_shortpacket = true;
	}

	/* Link payload buffer directly on USB hardware */
	usb_device_endpoint_write_buffer_job(&usb_device,0,udd_g_ctrlreq.payload + udd_ctrl_payload_nb_trans,nb_remain);

	udd_ctrl_payload_nb_trans += nb_remain;
}

/**
 * \brief Process control endpoint OUT transaction
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void udd_ctrl_out_received(void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;

	uint16_t nb_data;
	nb_data = ep_callback_para->received_bytes; /* Read data received during OUT phase */

	if (udd_g_ctrlreq.payload_size < (udd_ctrl_payload_nb_trans + nb_data)) {
		/* Payload buffer too small */
		nb_data = udd_g_ctrlreq.payload_size - udd_ctrl_payload_nb_trans;
	}

	memcpy((uint8_t *) (udd_g_ctrlreq.payload + udd_ctrl_payload_nb_trans), udd_ctrl_buffer, nb_data);
	udd_ctrl_payload_nb_trans += nb_data;

	if ((USB_DEVICE_EP_CTRL_SIZE != nb_data) || \
	(udd_g_ctrlreq.req.wLength <= (udd_ctrl_prev_payload_nb_trans + udd_ctrl_payload_nb_trans))) {
		/* End of reception because it is a short packet
		 * or all data are transferred */

		/* Before send ZLP, call intermediate callback
		 * in case of data receive generate a stall */
		udd_g_ctrlreq.payload_size = udd_ctrl_payload_nb_trans;
		if (NULL != udd_g_ctrlreq.over_under_run) {
			if (!udd_g_ctrlreq.over_under_run()) {
				/* Stall ZLP */
				udd_ep_control_state = UDD_EPCTRL_STALL_REQ;
				/* Stall all packets on IN & OUT control endpoint */
				udd_ep_set_halt(0);
				/* Ack reception of OUT to replace NAK by a STALL */
				return;
			}
		}
		/* Send IN ZLP to ACK setup request */
		udd_ctrl_send_zlp_in();
		return;
	}

	if (udd_g_ctrlreq.payload_size == udd_ctrl_payload_nb_trans) {
		/* Overrun then request a new payload buffer */
		if (!udd_g_ctrlreq.over_under_run) {
			/* No callback available to request a new payload buffer
			 * Stall ZLP */
			udd_ep_control_state = UDD_EPCTRL_STALL_REQ;
			/* Stall all packets on IN & OUT control endpoint */
			udd_ep_set_halt(0);
			return;
		}
		if (!udd_g_ctrlreq.over_under_run()) {
			/* No new payload buffer delivered
			 * Stall ZLP */
			udd_ep_control_state = UDD_EPCTRL_STALL_REQ;
			/* Stall all packets on IN & OUT control endpoint */
			udd_ep_set_halt(0);
			return;
		}
		/* New payload buffer available
		 * Update number of total data received */
		udd_ctrl_prev_payload_nb_trans += udd_ctrl_payload_nb_trans;

		/* Reinitialize reception on payload buffer */
		udd_ctrl_payload_nb_trans = 0;
	}
	usb_device_endpoint_read_buffer_job(&usb_device,0,udd_ctrl_buffer,USB_DEVICE_EP_CTRL_SIZE);
}

/**
 * \internal
 * \brief     Endpoint 0 (control) SETUP received callback
 * \param[in] module_inst pointer to USB module instance
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void _usb_ep0_on_setup(struct usb_module *module_inst, void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;

	if (UDD_EPCTRL_SETUP != udd_ep_control_state) {
		if (NULL != udd_g_ctrlreq.callback) {
			udd_g_ctrlreq.callback();
		}
		udd_ep_control_state = UDD_EPCTRL_SETUP;
	}
	if ( 8 != ep_callback_para->received_bytes) {
		udd_ctrl_stall_data();
		return;
	} else {
		udd_ctrl_fetch_ram();
		if (false == udc_process_setup()) {
			udd_ctrl_stall_data();
			return;
		} else if (Udd_setup_is_in()) {
			udd_ctrl_prev_payload_nb_trans = 0;
			udd_ctrl_payload_nb_trans = 0;
			udd_ep_control_state = UDD_EPCTRL_DATA_IN;
			usb_device_endpoint_read_buffer_job(&usb_device,0,udd_ctrl_buffer,USB_DEVICE_EP_CTRL_SIZE);
			udd_ctrl_in_sent();
		} else {
			if(0 == udd_g_ctrlreq.req.wLength) {
				udd_ctrl_send_zlp_in();
				return;
			} else {
				udd_ctrl_prev_payload_nb_trans = 0;
				udd_ctrl_payload_nb_trans = 0;
				udd_ep_control_state = UDD_EPCTRL_DATA_OUT;
				/* Initialize buffer size and enable OUT bank */
				usb_device_endpoint_read_buffer_job(&usb_device,0,udd_ctrl_buffer,USB_DEVICE_EP_CTRL_SIZE);
			}
		}
	}

	(void) module_inst;
}

/**
 * \brief Control Endpoint Process when underflow condition has occurred
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void udd_ctrl_underflow(void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;

	if (UDD_EPCTRL_DATA_OUT == udd_ep_control_state) {
		/* Host want to stop OUT transaction
		 * then stop to wait OUT data phase and wait IN ZLP handshake */
		udd_ctrl_send_zlp_in();
	} else if (UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP == udd_ep_control_state) {
		/* A OUT handshake is waiting by device,
		 * but host want extra IN data then stall extra IN data */
		usb_device_endpoint_set_halt(&usb_device, ep_callback_para->endpoint_address);
	}
}

/**
 * \brief Control Endpoint Process when overflow condition has occurred
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void udd_ctrl_overflow(void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;

	if (UDD_EPCTRL_DATA_IN == udd_ep_control_state) {
		/* Host want to stop IN transaction
		 * then stop to wait IN data phase and wait OUT ZLP handshake */
		udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP;
	} else if (UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP == udd_ep_control_state) {
		/* A IN handshake is waiting by device,
		 * but host want extra OUT data then stall extra OUT data and following status stage */
		usb_device_endpoint_set_halt(&usb_device, ep_callback_para->endpoint_address);
	}
}

/**
 * \internal
 * \brief Control endpoint transfer fail callback function
 * \param[in] module_inst Pointer to USB module instance
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void _usb_ep0_on_tansfer_fail(struct usb_module *module_inst, void* pointer)
{
	struct usb_endpoint_callback_parameter *ep_callback_para = (struct usb_endpoint_callback_parameter*)pointer;

	if(ep_callback_para->endpoint_address & USB_EP_DIR_IN) {
		udd_ctrl_underflow(pointer);
	} else {
		udd_ctrl_overflow(pointer);
	}

	(void) module_inst;
}

/**
 * \internal
 * \brief Control endpoint transfer complete callback function
 * \param[in] module_inst Pointer to USB module instance
 * \param[in] pointer Pointer to the endpoint transfer status parameter struct from driver layer.
 */
static void _usb_ep0_on_tansfer_ok(struct usb_module *module_inst, void * pointer)
{
		if (UDD_EPCTRL_DATA_OUT  == udd_ep_control_state) { /* handshake Out for status stage */
			udd_ctrl_out_received(pointer);
		} else if (UDD_EPCTRL_DATA_IN == udd_ep_control_state) { /* handshake In for status stage */
			udd_ctrl_in_sent();
		} else {
			if (NULL != udd_g_ctrlreq.callback) {
				udd_g_ctrlreq.callback();
			}
			udd_ep_control_state = UDD_EPCTRL_SETUP;
		}

	(void) module_inst;
}

/**
 * \brief Enable Control Endpoint
 * \param[in] module_inst Pointer to USB module instance
 */
static void udd_ctrl_ep_enable(struct usb_module *module_inst)
{
	/* USB Device Endpoint0 Configuration */
	 struct usb_device_endpoint_config config_ep0;

	 usb_device_endpoint_get_config_defaults(&config_ep0);
	 config_ep0.ep_size = (enum usb_endpoint_size)(32 - clz(((uint32_t)Min(Max(USB_DEVICE_EP_CTRL_SIZE, 8), 1024) << 1) - 1) - 1 - 3);
	 usb_device_endpoint_set_config(module_inst,&config_ep0);

	 usb_device_endpoint_setup_buffer_job(module_inst,udd_ctrl_buffer);

	 usb_device_endpoint_register_callback(module_inst,0,USB_DEVICE_ENDPOINT_CALLBACK_RXSTP, _usb_ep0_on_setup );
	 usb_device_endpoint_register_callback(module_inst,0,USB_DEVICE_ENDPOINT_CALLBACK_TRCPT,_usb_ep0_on_tansfer_ok );
	 usb_device_endpoint_register_callback(module_inst,0,USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL,_usb_ep0_on_tansfer_fail );
	 usb_device_endpoint_enable_callback(module_inst,0,USB_DEVICE_ENDPOINT_CALLBACK_RXSTP);
	 usb_device_endpoint_enable_callback(module_inst,0,USB_DEVICE_ENDPOINT_CALLBACK_TRCPT);
	 usb_device_endpoint_enable_callback(module_inst,0,USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL);

#ifdef  USB_DEVICE_LPM_SUPPORT
	 // Enable LPM feature
	 usb_device_set_lpm_mode(module_inst, USB_DEVICE_LPM_ACK);
#endif

	 udd_ep_control_state = UDD_EPCTRL_SETUP;
}

/**
 * \internal
 * \brief Control endpoint Suspend callback function
 * \param[in] module_inst Pointer to USB module instance
 * \param[in] pointer Pointer to the callback parameter from driver layer.
 */
static void _usb_on_suspend(struct usb_module *module_inst, void *pointer)
{
	usb_device_disable_callback(&usb_device, USB_DEVICE_CALLBACK_SUSPEND);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_WAKEUP);
	udd_sleep_mode(UDD_STATE_SUSPEND);
#ifdef UDC_SUSPEND_EVENT
	UDC_SUSPEND_EVENT();
#endif

	(void) module_inst;
	(void) pointer;
}

#ifdef  USB_DEVICE_LPM_SUPPORT
static void _usb_device_lpm_suspend(struct usb_module *module_inst, void *pointer)
{
	dbg_print("LPM_SUSP\n");

	uint32_t *lpm_wakeup_enable;
	lpm_wakeup_enable = (uint32_t *)pointer;

	usb_device_disable_callback(&usb_device, USB_DEVICE_CALLBACK_LPMSUSP);
	usb_device_disable_callback(&usb_device, USB_DEVICE_CALLBACK_SUSPEND);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_WAKEUP);

//#warning Here the sleep mode must be choose to have a DFLL startup time < bmAttribut.HIRD
	udd_sleep_mode(UDD_STATE_SUSPEND_LPM);  // Enter in LPM SUSPEND mode
	if ((*lpm_wakeup_enable)) {
		UDC_REMOTEWAKEUP_LPM_ENABLE();
	}
	if (!(*lpm_wakeup_enable)) {
		UDC_REMOTEWAKEUP_LPM_DISABLE();
	}
	UDC_SUSPEND_LPM_EVENT();
}
#endif

/**
 * \internal
 * \brief Control endpoint SOF callback function
 * \param[in] module_inst Pointer to USB module instance
 * \param[in] pointer Pointer to the callback parameter from driver layer.
 */
static void _usb_on_sof_notify(struct usb_module *module_inst, void *pointer)
{
	udc_sof_notify();
#ifdef UDC_SOF_EVENT
	UDC_SOF_EVENT();
#endif

	(void) module_inst;
	(void) pointer;
}

/**
 * \internal
 * \brief Control endpoint Reset callback function
 * \param[in] module_inst Pointer to USB module instance
 * \param[in] pointer Pointer to the callback parameter from driver layer.
 */
static void _usb_on_bus_reset(struct usb_module *module_inst, void *pointer)
{
	// Reset USB Device Stack Core
	udc_reset();
	usb_device_set_address(module_inst,0);
	udd_ctrl_ep_enable(module_inst);

	(void) pointer;
}

/**
 * \internal
 * \brief Control endpoint Wakeup callback function
 * \param[in] module_inst Pointer to USB module instance
 * \param[in] pointer Pointer to the callback parameter from driver layer.
 */
static void _usb_on_wakeup(struct usb_module *module_inst, void *pointer)
{
	udd_wait_clock_ready();

	usb_device_disable_callback(&usb_device, USB_DEVICE_CALLBACK_WAKEUP);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_SUSPEND);
#ifdef  USB_DEVICE_LPM_SUPPORT
	usb_device_register_callback(&usb_device, USB_DEVICE_CALLBACK_LPMSUSP, _usb_device_lpm_suspend);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_LPMSUSP);
#endif
	udd_sleep_mode(UDD_STATE_IDLE);
#ifdef UDC_RESUME_EVENT
	UDC_RESUME_EVENT();
#endif
	(void) module_inst;
	(void) pointer;
}

void udd_detach(void)
{
	usb_device_detach(&usb_device);
	udd_sleep_mode(UDD_STATE_SUSPEND);
}

void udd_attach(void)
{
	udd_sleep_mode(UDD_STATE_IDLE);
	usb_device_attach(&usb_device);

	usb_device_register_callback(&usb_device, USB_DEVICE_CALLBACK_SUSPEND, _usb_on_suspend);
	usb_device_register_callback(&usb_device, USB_DEVICE_CALLBACK_SOF, _usb_on_sof_notify);
	usb_device_register_callback(&usb_device, USB_DEVICE_CALLBACK_RESET, _usb_on_bus_reset);
	usb_device_register_callback(&usb_device, USB_DEVICE_CALLBACK_WAKEUP, _usb_on_wakeup);

	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_SUSPEND);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_SOF);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_RESET);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_WAKEUP);
#ifdef  USB_DEVICE_LPM_SUPPORT
	usb_device_register_callback(&usb_device, USB_DEVICE_CALLBACK_LPMSUSP, _usb_device_lpm_suspend);
	usb_device_enable_callback(&usb_device, USB_DEVICE_CALLBACK_LPMSUSP);
#endif
}

#if USB_VBUS_EIC
/**
 * \name USB VBUS PAD management
 *
 * @{
 */
#error "This ain't ready for prime time."

 /** Check if USB VBus is available */
# define is_usb_vbus_high()           port_pin_get_input_level(USB_VBUS_PIN)

/**
 * \internal
 * \brief USB VBUS pin change handler
 */
static void _uhd_vbus_handler(void)
{
	extint_chan_disable_callback(USB_VBUS_EIC_LINE,
			EXTINT_CALLBACK_TYPE_DETECT);
# ifndef USB_DEVICE_ATTACH_AUTO_DISABLE
	if (is_usb_vbus_high()) {
		udd_attach();
	} else {
		udd_detach();
	}
# endif
# ifdef UDC_VBUS_EVENT
	UDC_VBUS_EVENT(is_usb_vbus_high());
# endif
	extint_chan_enable_callback(USB_VBUS_EIC_LINE,
			EXTINT_CALLBACK_TYPE_DETECT);
}

/**
 * \internal
 * \brief USB VBUS pin configuration
 */
static void _usb_vbus_config(void)
{

	/* Initialize EIC for vbus checking */
	struct extint_chan_conf eint_chan_conf;
	extint_chan_get_config_defaults(&eint_chan_conf);

	eint_chan_conf.gpio_pin           = USB_VBUS_PIN;
	eint_chan_conf.gpio_pin_mux       = USB_VBUS_EIC_MUX;
	eint_chan_conf.gpio_pin_pull      = EXTINT_PULL_NONE;
	eint_chan_conf.detection_criteria = EXTINT_DETECT_BOTH;
	eint_chan_conf.filter_input_signal = true;

	extint_chan_disable_callback(USB_VBUS_EIC_LINE,
			EXTINT_CALLBACK_TYPE_DETECT);
	extint_chan_set_config(USB_VBUS_EIC_LINE, &eint_chan_conf);
	extint_register_callback(_uhd_vbus_handler,
			USB_VBUS_EIC_LINE,
			EXTINT_CALLBACK_TYPE_DETECT);
	extint_chan_enable_callback(USB_VBUS_EIC_LINE,
			EXTINT_CALLBACK_TYPE_DETECT);
}
/** @} */
#endif

bool udd_include_vbus_monitoring(void)
{
#if USB_VBUS_EIC
	return true;
#else
	return false;
#endif
}

void udd_enable(void)
{
	rtos_saved_ipl_t ipl;

	/* To avoid USB interrupt before end of initialization */
	ipl = rtos_ipl_raise(HW_IPL_USB);

#if USB_ID_EIC
	if (usb_dual_enable()) {
		/* The current mode has been started by otg_dual_enable() */
		rtos_ipl_restore(ipl);
		return;
	}
#endif
	struct usb_config config_usb;

	/* USB Module configuration */
	sam_usb_get_config_defaults(&config_usb);
	sam_usb_init(&usb_device, USB, &config_usb);

	/* USB Module Enable */
	usb_enable(&usb_device);

	/* Check clock after enable module, request the clock */
	udd_wait_clock_ready();

	udd_sleep_mode(UDD_STATE_SUSPEND);

#if USB_VBUS_EIC
	_usb_vbus_config();
	if (is_usb_vbus_high()) {
		/* USB Attach */
		_uhd_vbus_handler();
	}
#else
	// No VBus detect, assume always high
# ifndef USB_DEVICE_ATTACH_AUTO_DISABLE
	udd_attach();
# endif
#endif

	rtos_ipl_restore(ipl);
}

void udd_disable(void)
{
	rtos_saved_ipl_t ipl;

	udd_detach();

	udd_sleep_mode(UDD_STATE_OFF);

	ipl = rtos_ipl_raise(HW_IPL_USB);
	sam_usb_deinit(&usb_device);
	rtos_ipl_restore(ipl);
}
/** @} */
