/**
 * \file
 *
 * \brief SAM USB Driver.
 *
 * Copyright (c) 2014-2018 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 "usb.h"

#ifndef UHD_BULK_INTERVAL_MIN
/** Minimal bulk interval value */
#  define UHD_BULK_INTERVAL_MIN 1
#endif

/** Fields definition from a LPM TOKEN  */
#define  USB_LPM_ATTRIBUT_BLINKSTATE_MASK      (0xF << 0)
#define  USB_LPM_ATTRIBUT_HIRD_MASK            (0xF << 4)
#define  USB_LPM_ATTRIBUT_REMOTEWAKE_MASK      (1 << 8)
#define  USB_LPM_ATTRIBUT_BLINKSTATE(value)    ((value & 0xF) << 0)
#define  USB_LPM_ATTRIBUT_HIRD(value)          ((value & 0xF) << 4)
#define  USB_LPM_ATTRIBUT_REMOTEWAKE(value)    ((value & 1) << 8)
#define  USB_LPM_ATTRIBUT_BLINKSTATE_L1        USB_LPM_ATTRIBUT_BLINKSTATE(1)

/**
 * \brief Mask selecting the index part of an endpoint address
 */
#define  USB_EP_ADDR_MASK     0x0f

/**
 * \brief Endpoint transfer direction is IN
 */
#define  USB_EP_DIR_IN        0x80

/**
 * \brief Endpoint transfer direction is OUT
 */
#define  USB_EP_DIR_OUT       0x00

/**
 * \name USB SRAM data containing pipe descriptor table
 * The content of the USB SRAM can be :
 * - modified by USB hardware interface to update pipe status.
 *   Thereby, it is read by software.
 * - modified by USB software to control pipe.
 *   Thereby, it is read by hardware.
 * This data section is volatile.
 *
 * @{
 */
COMPILER_PACK_SET(1)
COMPILER_WORD_ALIGNED
union {
	UsbDeviceDescriptor usb_endpoint_table[USB_EPT_NUM];
#if !SAMD11
	UsbHostDescriptor usb_pipe_table[USB_PIPE_NUM];
#endif
} usb_descriptor_table;
COMPILER_PACK_RESET()
/** @} */

/**
 * \brief Local USB module instance
 */
static struct usb_module *_usb_instances;

#if !SAMD11
/**
 * \brief Host pipe callback structure variable
 */
static struct usb_pipe_callback_parameter pipe_callback_para;
#endif

/* Device LPM callback variable */
static uint32_t device_callback_lpm_wakeup_enable;

/**
 * \brief Device endpoint callback parameter variable, used to transfer info to UDD wrapper layer
 */
static struct usb_endpoint_callback_parameter ep_callback_para;

/**
 * \internal USB Device IRQ Mask Bits Map
 */
static const uint16_t _usb_device_irq_bits[USB_DEVICE_CALLBACK_N] = {
	USB_DEVICE_INTFLAG_SOF,
	USB_DEVICE_INTFLAG_EORST,
	USB_DEVICE_INTFLAG_WAKEUP | USB_DEVICE_INTFLAG_EORSM | USB_DEVICE_INTFLAG_UPRSM,
	USB_DEVICE_INTFLAG_RAMACER,
	USB_DEVICE_INTFLAG_SUSPEND,
	USB_DEVICE_INTFLAG_LPMNYET,
	USB_DEVICE_INTFLAG_LPMSUSP,
};

/**
 * \internal USB Device IRQ Mask Bits Map
 */
static const uint8_t _usb_endpoint_irq_bits[USB_DEVICE_EP_CALLBACK_N] = {
	USB_DEVICE_EPINTFLAG_TRCPT_Msk,
	USB_DEVICE_EPINTFLAG_TRFAIL_Msk,
	USB_DEVICE_EPINTFLAG_RXSTP,
	USB_DEVICE_EPINTFLAG_STALL_Msk
};

#if !SAMD11
/**
 * \brief Bit mask for pipe job busy status
 */
uint32_t host_pipe_job_busy_status = 0;

/**
 * \brief Registers a USB host callback
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref usb_host_enable_callback,
 * in order for the interrupt handler to call it when the conditions for the
 * callback type is met.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 * \param[in]     callback_func Pointer to callback function
 *
 * \return Status of the registration operation.
 * \retval STATUS_OK    The callback was registered successfully.
 */
enum status_code usb_host_register_callback(struct usb_module *module_inst,
		enum usb_host_callback callback_type,
		usb_host_callback_t callback_func)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(callback_func);

	/* Register callback function */
	module_inst->host_callback[callback_type] = callback_func;

	/* Set the bit corresponding to the callback_type */
	module_inst->host_registered_callback_mask |= (1 << callback_type);

	return STATUS_OK;
}

/**
 * \brief Unregisters a USB host callback
 *
 * Unregisters an asynchronous callback implemented by the user. Removing it
 * from the internal callback registration table.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the de-registration operation.
 * \retval STATUS_OK    The callback was unregistered successfully.
 */
enum status_code usb_host_unregister_callback(struct usb_module *module_inst,
		enum usb_host_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);

	/* Unregister callback function */
	module_inst->host_callback[callback_type] = NULL;

	/* Clear the bit corresponding to the callback_type */
	module_inst->host_registered_callback_mask &= ~(1 << callback_type);

	return STATUS_OK;
}

/**
 * \brief Enables USB host callback generation for a given type.
 *
 * Enables asynchronous callbacks for a given logical type.
 * This must be called before USB host generate callback events.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback enable operation.
 * \retval STATUS_OK    The callback was enabled successfully.
 */
enum status_code usb_host_enable_callback(struct usb_module *module_inst,
		enum usb_host_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	/* Enable callback */
	module_inst->host_enabled_callback_mask |= (1 << callback_type);

	if (callback_type == USB_HOST_CALLBACK_SOF) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_HSOF;
	}
	if (callback_type == USB_HOST_CALLBACK_RESET) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_RST;
	}
	if (callback_type == USB_HOST_CALLBACK_WAKEUP) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_WAKEUP;
	}
	if (callback_type == USB_HOST_CALLBACK_DNRSM) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_DNRSM;
	}
	if (callback_type == USB_HOST_CALLBACK_UPRSM) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_UPRSM;
	}
	if (callback_type == USB_HOST_CALLBACK_RAMACER) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_RAMACER;
	}
	if (callback_type == USB_HOST_CALLBACK_CONNECT) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_DCONN;
	}
	if (callback_type == USB_HOST_CALLBACK_DISCONNECT) {
		module_inst->hw->HOST.INTENSET.reg = USB_HOST_INTENSET_DDISC;
	}

	return STATUS_OK;
}

/**
 * \brief Disables USB host callback generation for a given type.
 *
 * Disables asynchronous callbacks for a given logical type.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback disable operation.
 * \retval STATUS_OK    The callback was disabled successfully.
 */
enum status_code usb_host_disable_callback(struct usb_module *module_inst,
		enum usb_host_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	/* Disable callback */
	module_inst->host_enabled_callback_mask &= ~(1 << callback_type);

	if (callback_type == USB_HOST_CALLBACK_SOF) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_HSOF;
	}
	if (callback_type == USB_HOST_CALLBACK_RESET) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_RST;
	}
	if (callback_type == USB_HOST_CALLBACK_WAKEUP) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_WAKEUP;
	}
	if (callback_type == USB_HOST_CALLBACK_DNRSM) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_DNRSM;
	}
	if (callback_type == USB_HOST_CALLBACK_UPRSM) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_UPRSM;
	}
	if (callback_type == USB_HOST_CALLBACK_RAMACER) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_RAMACER;
	}
	if (callback_type == USB_HOST_CALLBACK_CONNECT) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_DCONN;
	}
	if (callback_type == USB_HOST_CALLBACK_DISCONNECT) {
		module_inst->hw->HOST.INTENCLR.reg = USB_HOST_INTENCLR_DDISC;
	}

	return STATUS_OK;
}

/**
 * \brief Initializes an USB host pipe configuration structure to defaults.
 *
 * Initializes a given USB host pipe configuration structure to a
 * set of known default values. This function should be called on all new
 * instances of these configuration structures before being modified by the
 * user application.
 *
 * The default configuration is as follows:
 * \li device address is 0
 * \li endpoint address is 0
 * \li pipe type is control
 * \li interval is 0
 * \li pipe size is 8
 *
 * \param[out] ep_config  Configuration structure to initialize to default values
 */
void usb_host_pipe_get_config_defaults(struct usb_host_pipe_config *ep_config)
{
	/* Sanity check arguments */
	Assert(ep_config);
	/* Write default config to config struct */
	ep_config->device_address = 0;
	ep_config->endpoint_address = 0;
	ep_config->pipe_type = USB_HOST_PIPE_TYPE_CONTROL;
	ep_config->binterval = 0;
	ep_config->size = 8;
}

/**
 * \brief Writes an USB host pipe configuration to the hardware module.
 *
 * Writes out a given configuration of an USB host pipe
 * configuration to the hardware module. If the pipe is already configured,
 * the new configuration will replace the existing one.
 *
 * \param[in] module_inst    Pointer to USB software instance struct
 * \param[in] pipe_num       Pipe to configure
 * \param[in] ep_config      Configuration settings for the pipe
 *
 * \return Status of the host pipe configuration operation.
 * \retval STATUS_OK    The host pipe was configured successfully.
 */
enum status_code usb_host_pipe_set_config(struct usb_module *module_inst, uint8_t pipe_num,
		struct usb_host_pipe_config *ep_config)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);
	Assert(ep_config);

	/* set pipe config */
	module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.BK = 0;
	module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE = ep_config->pipe_type;
#if UHD_BULK_INTERVAL_MIN
	if (ep_config->pipe_type == USB_HOST_PIPE_TYPE_BULK &&
		!(ep_config->endpoint_address & 0x80)) {
		module_inst->hw->HOST.HostPipe[pipe_num].BINTERVAL.reg =
			max(ep_config->binterval, UHD_BULK_INTERVAL_MIN);
	} else if (ep_config->pipe_type == USB_HOST_PIPE_TYPE_BULK) {
		module_inst->hw->HOST.HostPipe[pipe_num].BINTERVAL.reg = 0;
	} else {
		module_inst->hw->HOST.HostPipe[pipe_num].BINTERVAL.reg =
			ep_config->binterval;
	}
#else
	module_inst->hw->HOST.HostPipe[pipe_num].BINTERVAL.reg =
			ep_config->binterval;
#endif
	if (ep_config->endpoint_address == 0) {
		module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTOKEN =
				USB_HOST_PIPE_TOKEN_SETUP;
	} else if (ep_config->endpoint_address & USB_EP_DIR_IN) {
		module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTOKEN =
				USB_HOST_PIPE_TOKEN_IN;
		module_inst->hw->HOST.HostPipe[pipe_num].PSTATUSSET.reg =
				USB_HOST_PSTATUSSET_BK0RDY;
	} else {
		module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTOKEN =
				USB_HOST_PIPE_TOKEN_OUT;
		module_inst->hw->HOST.HostPipe[pipe_num].PSTATUSCLR.reg =
				USB_HOST_PSTATUSCLR_BK0RDY;
	}

	memset((uint8_t *)&usb_descriptor_table.usb_pipe_table[pipe_num], 0,
			sizeof(usb_descriptor_table.usb_pipe_table[0]));
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].CTRL_PIPE.bit.PDADDR =
			ep_config->device_address;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].CTRL_PIPE.bit.PEPNUM =
			ep_config->endpoint_address & USB_EP_ADDR_MASK;
	if (ep_config->size == 1023) {
		usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.SIZE = 0x07;
	} else {
		usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.SIZE =
				(32 - clz(((uint32_t)min(max(ep_config->size, 8), 1024) << 1) - 1) - 1 - 3);
	}

	/* Clear busy status */
	host_pipe_job_busy_status &= ~(1 << pipe_num);

	return STATUS_OK;
}

/**
 * \brief Gets an USB host pipe configuration.
 *
 * Gets out the configuration of an USB host pipe from the hardware module.
 *
 * \param[in] module_inst    Pointer to USB software instance struct
 * \param[in] pipe_num       Pipe to configure
 * \param[out] ep_config     Configuration settings for the pipe
 *
 * \return Status of the get host pipe configuration operation.
 * \retval STATUS_OK    The host pipe configuration was read successfully.
 */
enum status_code usb_host_pipe_get_config(struct usb_module *module_inst, uint8_t pipe_num,
		struct usb_host_pipe_config *ep_config)
{
	uint32_t size;

	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);
	Assert(ep_config);
	/* get pipe config from setting register */
	ep_config->device_address =
			usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].CTRL_PIPE.bit.PDADDR;
	ep_config->endpoint_address =
			usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].CTRL_PIPE.bit.PEPNUM;

	if (module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTOKEN ==
				USB_HOST_PIPE_TOKEN_IN) {
		ep_config->endpoint_address |= USB_EP_DIR_IN;
	}

	ep_config->pipe_type = (enum usb_host_pipe_type)module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE;
	ep_config->binterval =
			module_inst->hw->HOST.HostPipe[pipe_num].BINTERVAL.reg;
	size = usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.SIZE;
	if (size == 0x07) {
		ep_config->size = 1023;
	} else {
		ep_config->size = (8 << size);
	}

	return STATUS_OK;
}

/**
 * \brief Registers a USB host pipe callback
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref usb_host_pipe_enable_callback,
 * in order for the interrupt handler to call it when the conditions for the
 * callback type is met.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     callback_type Callback type given by an enum
 * \param[in]     callback_func Pointer to callback function
 *
 * \return Status of the registration operation.
 * \retval STATUS_OK    The callback was registered successfully.
 */
enum status_code usb_host_pipe_register_callback(
		struct usb_module *module_inst, uint8_t pipe_num,
		enum usb_host_pipe_callback callback_type,
		usb_host_pipe_callback_t callback_func)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(pipe_num < USB_PIPE_NUM);
	Assert(callback_func);

	/* Register callback function */
	module_inst->host_pipe_callback[pipe_num][callback_type] = callback_func;

	/* Set the bit corresponding to the callback_type */
	module_inst->host_pipe_registered_callback_mask[pipe_num] |= (1 << callback_type);

	return STATUS_OK;
}

/**
 * \brief Unregisters a USB host pipe callback
 *
 * Unregisters an asynchronous callback implemented by the user. Removing it
 * from the internal callback registration table.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the de-registration operation.
 * \retval STATUS_OK    The callback was unregistered successfully.
 */
enum status_code usb_host_pipe_unregister_callback(
		struct usb_module *module_inst, uint8_t pipe_num,
		enum usb_host_pipe_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(pipe_num < USB_PIPE_NUM);

	/* Unregister callback function */
	module_inst->host_pipe_callback[pipe_num][callback_type] = NULL;

	/* Clear the bit corresponding to the callback_type */
	module_inst->host_pipe_registered_callback_mask[pipe_num] &= ~(1 << callback_type);

	return STATUS_OK;
}

/**
 * \brief Enables USB host pipe callback generation for a given type.
 *
 * Enables asynchronous callbacks for a given logical type.
 * This must be called before USB host pipe generate callback events.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback enable operation.
 * \retval STATUS_OK    The callback was enabled successfully.
 */
enum status_code usb_host_pipe_enable_callback(
		struct usb_module *module_inst, uint8_t pipe_num,
		enum usb_host_pipe_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);

	/* Enable callback */
	module_inst->host_pipe_enabled_callback_mask[pipe_num] |= (1 << callback_type);

	if (callback_type == USB_HOST_PIPE_CALLBACK_TRANSFER_COMPLETE) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENSET.reg = USB_HOST_PINTENSET_TRCPT_Msk;
	}
	if (callback_type == USB_HOST_PIPE_CALLBACK_ERROR) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENSET.reg =
				USB_HOST_PINTENSET_TRFAIL | USB_HOST_PINTENSET_PERR;
	}
	if (callback_type == USB_HOST_PIPE_CALLBACK_SETUP) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENSET.reg = USB_HOST_PINTENSET_TXSTP;
	}
	if (callback_type == USB_HOST_PIPE_CALLBACK_STALL) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENSET.reg = USB_HOST_PINTENSET_STALL;
	}

	return STATUS_OK;
}

/**
 * \brief Disables USB host callback generation for a given type.
 *
 * Disables asynchronous callbacks for a given logical type.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback disable operation.
 * \retval STATUS_OK    The callback was disabled successfully.
 */
enum status_code usb_host_pipe_disable_callback(
		struct usb_module *module_inst, uint8_t pipe_num,
		enum usb_host_pipe_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);

	/* Enable callback */
	module_inst->host_pipe_enabled_callback_mask[pipe_num] &= ~(1 << callback_type);

	if (callback_type == USB_HOST_PIPE_CALLBACK_TRANSFER_COMPLETE) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENCLR.reg = USB_HOST_PINTENCLR_TRCPT_Msk;
	}
	if (callback_type == USB_HOST_PIPE_CALLBACK_ERROR) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENCLR.reg =
				USB_HOST_PINTENCLR_TRFAIL| USB_HOST_PINTENCLR_PERR;
	}
	if (callback_type == USB_HOST_PIPE_CALLBACK_SETUP) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENCLR.reg = USB_HOST_PINTENCLR_TXSTP;
	}
	if (callback_type == USB_HOST_PIPE_CALLBACK_STALL) {
		module_inst->hw->HOST.HostPipe[pipe_num].PINTENCLR.reg = USB_HOST_PINTENCLR_STALL;
	}

	return STATUS_OK;
}

/**
 * \brief Sends the setup package.
 *
 * Sends the setup package.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     buf           Pointer to data buffer
 *
 * \return Status of the setup operation.
 * \retval STATUS_OK    The setup job was set successfully.
 * \retval STATUS_BUSY    The pipe is busy.
 * \retval STATUS_ERR_NOT_INITIALIZED    The pipe has not been configured.
 */
enum status_code usb_host_pipe_setup_job(struct usb_module *module_inst,
		uint8_t pipe_num, uint8_t *buf)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);

	if (host_pipe_job_busy_status & (1 << pipe_num)) {
		return STATUS_BUSY;
	}

	/* Set busy status */
	host_pipe_job_busy_status |= 1 << pipe_num;

	if (module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE ==
			USB_HOST_PIPE_TYPE_DISABLE) {
		return STATUS_ERR_NOT_INITIALIZED;
	}

	/* get pipe config from setting register */
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].ADDR.reg = (uint32_t)buf;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT =
			8;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE =
			0;
	module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTOKEN =
			USB_HOST_PIPE_TOKEN_SETUP;

	module_inst->hw->HOST.HostPipe[pipe_num].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY;
	usb_host_pipe_unfreeze(module_inst, pipe_num);

	return STATUS_OK;
}

/**
 * \brief USB host pipe read job.
 *
 * USB host pipe read job by set and start an in transaction transfer.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     buf           Pointer to data buffer
 * \param[in]     buf_size      Data buffer size
 * \note The buffer length should not larger than 0x3FFF
 *
 * \return Status of the setting operation.
 * \retval STATUS_OK    The read job was set successfully.
 * \retval STATUS_BUSY    The pipe is busy.
 * \retval STATUS_ERR_NOT_INITIALIZED    The pipe has not been configured.
 */
enum status_code usb_host_pipe_read_job(struct usb_module *module_inst,
		uint8_t pipe_num, uint8_t *buf, uint32_t buf_size)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);

	if (module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE ==
			USB_HOST_PIPE_TYPE_DISABLE) {
		return STATUS_ERR_NOT_INITIALIZED;
	}

	if (host_pipe_job_busy_status & (1 << pipe_num)) {
		return STATUS_BUSY;
	}

	/* Set busy status */
	host_pipe_job_busy_status |= 1 << pipe_num;

	/* get pipe config from setting register */
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].ADDR.reg = (uint32_t)buf;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT =
			0;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE =
			buf_size;
	module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTOKEN =
			USB_HOST_PIPE_TOKEN_IN;

	/* Start transfer */
	module_inst->hw->HOST.HostPipe[pipe_num].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY;
	usb_host_pipe_unfreeze(module_inst, pipe_num);

	return STATUS_OK;
}

/**
 * \brief USB host pipe write job.
 *
 * USB host pipe write job by set and start an out transaction transfer.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     buf           Pointer to data buffer
 * \param[in]     buf_size      Data buffer size
 * \note The buffer length should not larger than 0x3FFF
 *
 * \return Status of the setting operation.
 * \retval STATUS_OK    The write job was set successfully.
 * \retval STATUS_BUSY    The pipe is busy.
 * \retval STATUS_ERR_NOT_INITIALIZED    The pipe has not been configured.
 */
enum status_code usb_host_pipe_write_job(struct usb_module *module_inst,
		uint8_t pipe_num, uint8_t *buf, uint32_t buf_size)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);

	if (host_pipe_job_busy_status & (1 << pipe_num)) {
		return STATUS_BUSY;
	}

	/* Set busy status */
	host_pipe_job_busy_status |= 1 << pipe_num;

	if (module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE ==
			USB_HOST_PIPE_TYPE_DISABLE) {
		return STATUS_ERR_NOT_INITIALIZED;
	}

	/* get pipe config from setting register */
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].ADDR.reg = (uint32_t)buf;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT =
			buf_size;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE =
			0;
	module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTOKEN =
			USB_HOST_PIPE_TOKEN_OUT;

	/* Start transfer */
	module_inst->hw->HOST.HostPipe[pipe_num].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY;
	usb_host_pipe_unfreeze(module_inst, pipe_num);

	return STATUS_OK;
}

/**
 * \brief USB host abort a pipe job.
 *
 * USB host pipe abort job by freeze the pipe.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 *
 * \return Status of the setting operation.
 * \retval STATUS_OK    The abort job was set successfully.
 * \retval STATUS_ERR_NOT_INITIALIZED    The pipe has not been configured.
 */
enum status_code usb_host_pipe_abort_job(struct usb_module *module_inst, uint8_t pipe_num)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);

	if (module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE ==
			USB_HOST_PIPE_TYPE_DISABLE) {
		return STATUS_ERR_NOT_INITIALIZED;
	}

	module_inst->hw->HOST.HostPipe[pipe_num].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE;

	/* Clear busy status */
	host_pipe_job_busy_status &= ~(1 << pipe_num);

	return STATUS_OK;
}

/**
 * \brief Sends the LPM package.
 *
 * Sends the LPM package.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     b_remotewakeup  Remote wake up flag
 * \param[in]     hird  Host Initiated Resume Duration
 *
 * \return Status of the setup operation.
 * \retval STATUS_OK    The setup job was set successfully.
 * \retval STATUS_BUSY    The pipe is busy.
 * \retval STATUS_ERR_NOT_INITIALIZED    The pipe has not been configured.
 */
enum status_code usb_host_pipe_lpm_job(struct usb_module *module_inst,
		uint8_t pipe_num, bool b_remotewakeup, uint8_t hird)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(pipe_num < USB_PIPE_NUM);

	if (host_pipe_job_busy_status & (1 << pipe_num)) {
		return STATUS_BUSY;
	}

	/* Set busy status */
	host_pipe_job_busy_status |= 1 << pipe_num;

	if (module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE ==
			USB_HOST_PIPE_TYPE_DISABLE) {
		return STATUS_ERR_NOT_INITIALIZED;
	}

	module_inst->hw->HOST.HostPipe[pipe_num].PCFG.bit.PTYPE =
				USB_HOST_PIPE_TYPE_EXTENDED;

	/* get pipe config from setting register */
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].EXTREG.bit.SUBPID = 0x3;
	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].EXTREG.bit.VARIABLE =
			USB_LPM_ATTRIBUT_REMOTEWAKE(b_remotewakeup) |
			USB_LPM_ATTRIBUT_HIRD(hird) |
			USB_LPM_ATTRIBUT_BLINKSTATE_L1;

	module_inst->hw->HOST.HostPipe[pipe_num].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY;
	usb_host_pipe_unfreeze(module_inst, pipe_num);

	return STATUS_OK;
}

/**
 * \internal
 * \brief Function called by USB interrupt to manage USB host interrupts
 *
 * USB host interrupt events are split into four sections:
 * - USB line events
 *   (Device dis/connection, SOF, reset, resume, wakeup, error)
 * - Pipe events
 *   (End of data transfer, setup, stall, error)
 */
static void _usb_host_interrupt_handler(void)
{
	uint32_t pipe_int;
	uint32_t flags;

	/* Manage pipe interrupts */
	pipe_int = ctz(_usb_instances->hw->HOST.PINTSMRY.reg);
	if (pipe_int < 32) {
		/* pipe interrupts */

		/* get interrupt flags */
		flags = _usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg;

		/* host pipe transfer complete interrupt */
		if (flags & USB_HOST_PINTFLAG_TRCPT_Msk) {
			/* Clear busy status */
			host_pipe_job_busy_status &= ~(1 << pipe_int);
			/* clear the flag */
			_usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg =
					USB_HOST_PINTFLAG_TRCPT_Msk;
			if(_usb_instances->host_pipe_enabled_callback_mask[pipe_int] &
					(1 << USB_HOST_PIPE_CALLBACK_TRANSFER_COMPLETE)) {
				pipe_callback_para.pipe_num = pipe_int;
				if (_usb_instances->hw->HOST.HostPipe[pipe_int].PCFG.bit.PTOKEN ==
							USB_HOST_PIPE_TOKEN_IN) {
					/* in  */
					pipe_callback_para.transfered_size = usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT;
					pipe_callback_para.required_size = usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE;
					usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = 0;
				} else {
					/* out */
					pipe_callback_para.transfered_size = usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE;
					pipe_callback_para.required_size = usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT;
					usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = 0;
					if (0 == pipe_callback_para.transfered_size) {
						pipe_callback_para.transfered_size = usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT;
					}
				}
				(_usb_instances->host_pipe_callback[pipe_int]
						[USB_HOST_PIPE_CALLBACK_TRANSFER_COMPLETE])(_usb_instances, &pipe_callback_para);
			}
		}

		/* host pipe transfer fail interrupt */
		if (flags & USB_HOST_PINTFLAG_TRFAIL) {
			/* For ISO IN, check CRC error */
			if (_usb_instances->hw->HOST.HostPipe[pipe_int].PCFG.bit.PTYPE == USB_HOST_PIPE_TYPE_ISO &&
					_usb_instances->hw->HOST.HostPipe[pipe_int].PCFG.bit.PTOKEN == USB_HOST_PIPE_TOKEN_IN &&
					usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].STATUS_BK.bit.CRCERR) {
				/* Clear busy status */
				host_pipe_job_busy_status &= ~(1 << pipe_int);
				/* clear the flag */
				usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].STATUS_BK.reg = 0;
				_usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg =
						USB_HOST_PINTFLAG_TRFAIL;
				if(_usb_instances->host_pipe_enabled_callback_mask[pipe_int] &
						(1 << USB_HOST_PIPE_CALLBACK_ERROR)) {
					pipe_callback_para.pipe_num = pipe_int;
					#define USB_STATUS_PIPE_CRC16ER   (1 << 4)
					pipe_callback_para.pipe_error_status = USB_STATUS_PIPE_CRC16ER;
					(_usb_instances->host_pipe_callback[pipe_int]
							[USB_HOST_PIPE_CALLBACK_ERROR])(_usb_instances, &pipe_callback_para);
				}
			}
#if UHD_BULK_INTERVAL_MIN
			/* For Bulk IN, check flow error */
			else if (_usb_instances->hw->HOST.HostPipe[pipe_int].PCFG.bit.PTYPE == USB_HOST_PIPE_TYPE_BULK &&
					_usb_instances->hw->HOST.HostPipe[pipe_int].PCFG.bit.PTOKEN == USB_HOST_PIPE_TOKEN_IN) {
				/* clear the flag */
				usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].STATUS_BK.reg = 0;
				_usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg =
						USB_HOST_PINTFLAG_TRFAIL;
				/* Freeze until next SOF */
				_usb_instances->hw->HOST.HostPipe[pipe_int].PSTATUSSET.reg = USB_HOST_PSTATUS_PFREEZE;
			}
#endif
			/* Clear flag anyway */
			else {
				/* clear the flag */
				usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].STATUS_BK.reg = 0;
				_usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg =
						USB_HOST_PINTFLAG_TRFAIL;
			}
		}

		/* host pipe error interrupt */
		if (flags & USB_HOST_PINTFLAG_PERR) {
			/* Clear busy status */
			host_pipe_job_busy_status &= ~(1 << pipe_int);
			/* clear the flag */
			_usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg =
					USB_HOST_PINTFLAG_PERR;
			if(_usb_instances->host_pipe_enabled_callback_mask[pipe_int] &
					(1 << USB_HOST_PIPE_CALLBACK_ERROR)) {
				pipe_callback_para.pipe_num = pipe_int;
				pipe_callback_para.pipe_error_status =
						usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].STATUS_PIPE.reg & 0x1F;
				(_usb_instances->host_pipe_callback[pipe_int]
						[USB_HOST_PIPE_CALLBACK_ERROR])(_usb_instances, &pipe_callback_para);
			}
		}

		/* host pipe transmitted setup interrupt */
		if (flags & USB_HOST_PINTFLAG_TXSTP) {
			/* Clear busy status */
			host_pipe_job_busy_status &= ~(1 << pipe_int);
			/* clear the flag */
			_usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg =
					USB_HOST_PINTFLAG_TXSTP;
			if(_usb_instances->host_pipe_enabled_callback_mask[pipe_int] &
					(1 << USB_HOST_PIPE_CALLBACK_SETUP)) {
				pipe_callback_para.pipe_num = pipe_int;
				pipe_callback_para.transfered_size = usb_descriptor_table.usb_pipe_table[pipe_int].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE;
				(_usb_instances->host_pipe_callback[pipe_int]
						[USB_HOST_PIPE_CALLBACK_SETUP])(_usb_instances, NULL);
			}
		}

		/* host pipe stall interrupt */
		if (flags & USB_HOST_PINTFLAG_STALL) {
			/* Clear busy status */
			host_pipe_job_busy_status &= ~(1 << pipe_int);
			/* clear the flag */
			_usb_instances->hw->HOST.HostPipe[pipe_int].PINTFLAG.reg =
					USB_HOST_PINTFLAG_STALL;
			if(_usb_instances->host_pipe_enabled_callback_mask[pipe_int] &
					(1 << USB_HOST_PIPE_CALLBACK_STALL)) {
				pipe_callback_para.pipe_num = pipe_int;
				(_usb_instances->host_pipe_callback[pipe_int]
						[USB_HOST_PIPE_CALLBACK_STALL])(_usb_instances, &pipe_callback_para);
			}
		}

	} else {
		/* host interrupts */

		/* get interrupt flags */
		flags = _usb_instances->hw->HOST.INTFLAG.reg;

		/* host SOF interrupt */
		if (flags & USB_HOST_INTFLAG_HSOF) {
			/* clear the flag */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_HSOF;
#if UHD_BULK_INTERVAL_MIN
			/* Start Bulk IN */
			for (pipe_int = 1; pipe_int < USB_PIPE_NUM; pipe_int ++) {
				if (!(host_pipe_job_busy_status & (1 << pipe_int))) {
					continue;
				}
				if (_usb_instances->hw->HOST.HostPipe[pipe_int].PCFG.bit.PTYPE != USB_HOST_PIPE_TYPE_BULK ||
					_usb_instances->hw->HOST.HostPipe[pipe_int].PCFG.bit.PTOKEN != USB_HOST_PIPE_TOKEN_IN) {
					continue;
				}
				/* Continue */
				_usb_instances->hw->HOST.HostPipe[pipe_int].PSTATUSCLR.reg = USB_HOST_PSTATUS_PFREEZE;
			}
#endif
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_SOF)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_SOF])(_usb_instances);
			}
		}

		/* host reset interrupt */
		if (flags & USB_HOST_INTFLAG_RST) {
			/* Clear busy status */
			host_pipe_job_busy_status = 0;
			/* clear the flag */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RST;
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_RESET)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_RESET])(_usb_instances);
			}
		}

		/* host upstream resume interrupts */
		if (flags & USB_HOST_INTFLAG_UPRSM) {
			/* clear the flags */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_UPRSM;
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_UPRSM)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_UPRSM])(_usb_instances);
			}
		}

		/* host downstream resume interrupts */
		if (flags & USB_HOST_INTFLAG_DNRSM) {
			/* clear the flags */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DNRSM;
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_DNRSM)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_DNRSM])(_usb_instances);
			}
		}

		/* host wakeup interrupts */
		if (flags & USB_HOST_INTFLAG_WAKEUP) {
			/* clear the flags */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_WAKEUP;
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_WAKEUP)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_WAKEUP])(_usb_instances);
			}
		}

		/* host ram access interrupt  */
		if (flags & USB_HOST_INTFLAG_RAMACER) {
			/* Clear busy status */
			host_pipe_job_busy_status = 0;
			/* clear the flag */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RAMACER;
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_RAMACER)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_RAMACER])(_usb_instances);
			}
		}

		/* host connect interrupt */
		if (flags & USB_HOST_INTFLAG_DCONN) {
			/* Clear busy status */
			host_pipe_job_busy_status = 0;
			/* clear the flag */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DCONN;
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_CONNECT)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_CONNECT])(_usb_instances);
			}
		}

		/* host disconnect interrupt 	*/
		if (flags & USB_HOST_INTFLAG_DDISC) {
			/* Clear busy status */
			host_pipe_job_busy_status = 0;
			/* clear the flag */
			_usb_instances->hw->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DDISC;
			if(_usb_instances->host_enabled_callback_mask & (1 << USB_HOST_CALLBACK_DISCONNECT)) {
				(_usb_instances->host_callback[USB_HOST_CALLBACK_DISCONNECT])(_usb_instances);
			}
		}

	}
}

/**
 * \brief Sets USB host pipe auto ZLP setting value
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     pipe_num      Pipe to configure
 * \param[in]     value         Auto ZLP setting value, \c true to enable
 *
 */
void usb_host_pipe_set_auto_zlp(struct usb_module *module_inst, uint8_t pipe_num, bool value)
{
	Assert(module_inst);

	usb_descriptor_table.usb_pipe_table[pipe_num].HostDescBank[0].PCKSIZE.bit.AUTO_ZLP = value;
}
#endif

/**
 * \brief Registers a USB device callback
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref usb_device_enable_callback,
 * in order for the interrupt handler to call it when the conditions for the
 * callback type is met.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 * \param[in]     callback_func Pointer to callback function
 *
 * \return Status of the registration operation.
 * \retval STATUS_OK    The callback was registered successfully.
 */
enum status_code usb_device_register_callback(struct usb_module *module_inst,
		enum usb_device_callback callback_type,
		usb_device_callback_t callback_func)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(callback_func);

	/* Register callback function */
	module_inst->device_callback[callback_type] = callback_func;

	/* Set the bit corresponding to the callback_type */
	module_inst->device_registered_callback_mask |= _usb_device_irq_bits[callback_type];

	return STATUS_OK;
}

/**
 * \brief Unregisters a USB device callback
 *
 * Unregisters an asynchronous callback implemented by the user. Removing it
 * from the internal callback registration table.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the de-registration operation.
 * \retval STATUS_OK    The callback was unregistered successfully.
 */
enum status_code usb_device_unregister_callback(struct usb_module *module_inst,
		enum usb_device_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);

	/* Unregister callback function */
	module_inst->device_callback[callback_type] = NULL;

	/* Clear the bit corresponding to the callback_type */
	module_inst->device_registered_callback_mask &= ~_usb_device_irq_bits[callback_type];

	return STATUS_OK;
}

/**
 * \brief Enables USB device callback generation for a given type.
 *
 * Enables asynchronous callbacks for a given logical type.
 * This must be called before USB device generate callback events.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback enable operation.
 * \retval STATUS_OK    The callback was enabled successfully.
 */
enum status_code usb_device_enable_callback(struct usb_module *module_inst,
		enum usb_device_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	/* clear related flag */
	module_inst->hw->DEVICE.INTFLAG.reg = _usb_device_irq_bits[callback_type];

	/* Enable callback */
	module_inst->device_enabled_callback_mask |= _usb_device_irq_bits[callback_type];

	module_inst->hw->DEVICE.INTENSET.reg = _usb_device_irq_bits[callback_type];

	return STATUS_OK;
}

/**
 * \brief Disables USB device callback generation for a given type.
 *
 * Disables asynchronous callbacks for a given logical type.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback disable operation.
 * \retval STATUS_OK    The callback was disabled successfully.
 */
enum status_code usb_device_disable_callback(struct usb_module *module_inst,
		enum usb_device_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	/* Disable callback */
	module_inst->device_enabled_callback_mask &= ~_usb_device_irq_bits[callback_type];

	module_inst->hw->DEVICE.INTENCLR.reg = _usb_device_irq_bits[callback_type];

	return STATUS_OK;
}

/**
 * \brief Registers a USB device endpoint callback
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref usb_device_endpoint_enable_callback,
 * in order for the interrupt handler to call it when the conditions for the
 * callback type is met.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep_num        Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 * \param[in]     callback_func Pointer to callback function
 *
 * \return Status of the registration operation.
 * \retval STATUS_OK    The callback was registered successfully.
 */
enum status_code usb_device_endpoint_register_callback(
		struct usb_module *module_inst, uint8_t ep_num,
		enum usb_device_endpoint_callback callback_type,
		usb_device_endpoint_callback_t callback_func)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(ep_num < USB_EPT_NUM);
	Assert(callback_func);

	/* Register callback function */
	module_inst->device_endpoint_callback[ep_num][callback_type] = callback_func;

	/* Set the bit corresponding to the callback_type */
	module_inst->device_endpoint_registered_callback_mask[ep_num] |= _usb_endpoint_irq_bits[callback_type];

	return STATUS_OK;
}

/**
 * \brief Unregisters a USB device endpoint callback
 *
 * Unregisters an callback implemented by the user. Removing it
 * from the internal callback registration table.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep_num        Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the de-registration operation.
 * \retval STATUS_OK    The callback was unregistered successfully.
 */
enum status_code usb_device_endpoint_unregister_callback(
		struct usb_module *module_inst, uint8_t ep_num,
		enum usb_device_endpoint_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(ep_num < USB_EPT_NUM);

	/* Unregister callback function */
	module_inst->device_endpoint_callback[ep_num][callback_type] = NULL;

	/* Clear the bit corresponding to the callback_type */
	module_inst->device_endpoint_registered_callback_mask[ep_num] &= ~_usb_endpoint_irq_bits[callback_type];

	return STATUS_OK;
}

/**
 * \brief Enables USB device endpoint callback generation for a given type.
 *
 * Enables callbacks for a given logical type.
 * This must be called before USB device pipe generate callback events.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep            Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback enable operation.
 * \retval STATUS_OK    The callback was enabled successfully.
 */
enum status_code usb_device_endpoint_enable_callback(
		struct usb_module *module_inst, uint8_t ep,
		enum usb_device_endpoint_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	uint8_t ep_num = ep & USB_EP_ADDR_MASK;
	Assert(ep_num < USB_EPT_NUM);

	/* Enable callback */
	module_inst->device_endpoint_enabled_callback_mask[ep_num] |= _usb_endpoint_irq_bits[callback_type];

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRCPT) {
		if (ep_num == 0) { // control endpoint
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1;
		} else if (ep & USB_EP_DIR_IN) {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT1;
		} else {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0;
		}
	}

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL) {
		if (ep_num == 0) { // control endpoint
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRFAIL0 | USB_DEVICE_EPINTENSET_TRFAIL1;
		} else if (ep & USB_EP_DIR_IN) {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRFAIL1;
		} else {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRFAIL0;
		}
	}

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_RXSTP) {
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_RXSTP;
	}

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_STALL) {
		if (ep & USB_EP_DIR_IN) {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_STALL1;
		} else {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_STALL0;
		}
	}

	return STATUS_OK;
}

/**
 * \brief Disables USB device endpoint callback generation for a given type.
 *
 * Disables callbacks for a given logical type.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep            Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback disable operation.
 * \retval STATUS_OK    The callback was disabled successfully.
 */
enum status_code usb_device_endpoint_disable_callback(
		struct usb_module *module_inst, uint8_t ep,
		enum usb_device_endpoint_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	uint8_t ep_num = ep & USB_EP_ADDR_MASK;
	Assert(ep_num < USB_EPT_NUM);

	/* Enable callback */
	module_inst->device_endpoint_enabled_callback_mask[ep_num] &= ~_usb_endpoint_irq_bits[callback_type];

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRCPT) {
		if (ep_num == 0) { // control endpoint
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg =  USB_DEVICE_EPINTENCLR_TRCPT0 | USB_DEVICE_EPINTENCLR_TRCPT1;
		} else if (ep & USB_EP_DIR_IN) {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg =  USB_DEVICE_EPINTENCLR_TRCPT1;
		} else {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg =  USB_DEVICE_EPINTENCLR_TRCPT0;
		}
	}

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL) {
		if (ep_num == 0) { // control endpoint
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRFAIL0 | USB_DEVICE_EPINTENCLR_TRFAIL1;
		} else if (ep & USB_EP_DIR_IN) {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRFAIL1;
		} else {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRFAIL0;
		}
	}

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_RXSTP) {
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_RXSTP;
	}

	if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_STALL) {
		if (ep & USB_EP_DIR_IN) {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_STALL1;
		} else {
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_STALL0;
		}
	}

	return STATUS_OK;
}

/**
 * \brief Initializes an USB device endpoint configuration structure to defaults.
 *
 * Initializes a given USB device endpoint configuration structure to a
 * set of known default values. This function should be called on all new
 * instances of these configuration structures before being modified by the
 * user application.
 *
 * The default configuration is as follows:
 * \li endpoint address is 0
 * \li endpoint size is 8 bytes
 * \li auto_zlp is false
 * \li endpoint type is control
 *
 * \param[out] ep_config  Configuration structure to initialize to default values
 */
void usb_device_endpoint_get_config_defaults(struct usb_device_endpoint_config *ep_config)
{
	/* Sanity check arguments */
	Assert(ep_config);

	/* Write default config to config struct */
	ep_config->ep_address = 0;
	ep_config->ep_size = USB_ENDPOINT_8_BYTE;
	ep_config->auto_zlp = false;
	ep_config->ep_type = USB_DEVICE_ENDPOINT_TYPE_CONTROL;
}

/**
 * \brief Writes an USB device endpoint configuration to the hardware module.
 *
 * Writes out a given configuration of an USB device endpoint
 * configuration to the hardware module. If the pipe is already configured,
 * the new configuration will replace the existing one.
 *
 * \param[in] module_inst    Pointer to USB software instance struct
 * \param[in] ep_config      Configuration settings for the endpoint
 *
 * \return Status of the device endpoint configuration operation
 * \retval STATUS_OK         The device endpoint was configured successfully
 * \retval STATUS_ERR_DENIED The endpoint address is already configured
 */
enum status_code usb_device_endpoint_set_config(struct usb_module *module_inst,
		struct usb_device_endpoint_config *ep_config)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(ep_config);

	uint8_t ep_num = ep_config->ep_address & USB_EP_ADDR_MASK;
	uint8_t ep_bank = (ep_config->ep_address & USB_EP_DIR_IN) ? 1 : 0;

	switch (ep_config->ep_type) {
		case USB_DEVICE_ENDPOINT_TYPE_DISABLE:
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(0) |  USB_DEVICE_EPCFG_EPTYPE1(0);
			return STATUS_OK;

		case USB_DEVICE_ENDPOINT_TYPE_CONTROL:
			if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0 && \
				(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0) {
				module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(1) | USB_DEVICE_EPCFG_EPTYPE1(1);
				module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
				module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
			} else {
				return STATUS_ERR_DENIED;
			}
			if (true == ep_config->auto_zlp) {
				usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.reg |= USB_DEVICE_PCKSIZE_AUTO_ZLP;
				usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.reg |= USB_DEVICE_PCKSIZE_AUTO_ZLP;
			} else {
				usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.reg &= ~USB_DEVICE_PCKSIZE_AUTO_ZLP;
				usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.reg &= ~USB_DEVICE_PCKSIZE_AUTO_ZLP;
			}
			usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.bit.SIZE = ep_config->ep_size;
			usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.bit.SIZE = ep_config->ep_size;
			return STATUS_OK;

		case USB_DEVICE_ENDPOINT_TYPE_ISOCHRONOUS:
			if (ep_bank) {
				if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0){
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE1(2);
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
				} else {
					return STATUS_ERR_DENIED;
				}
			} else {
				if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0){
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE0(2);
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
				} else {
					return STATUS_ERR_DENIED;
				}
			}
			break;

		case USB_DEVICE_ENDPOINT_TYPE_BULK:
			if (ep_bank) {
				if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0){
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE1(3);
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
				} else {
					return STATUS_ERR_DENIED;
				}
			} else {
				if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0){
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE0(3);
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
				} else {
					return STATUS_ERR_DENIED;
				}
			}
			break;

		case USB_DEVICE_ENDPOINT_TYPE_INTERRUPT:
			if (ep_bank) {
				if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0){
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE1(4);
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
				} else {
					return STATUS_ERR_DENIED;
				}
			} else {
				if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0){
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE0(4);
					module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
				} else {
					return STATUS_ERR_DENIED;
				}
			}
			break;

		default:
			break;
	}

	usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[ep_bank].PCKSIZE.bit.SIZE = ep_config->ep_size;

	if (true == ep_config->auto_zlp) {
		usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[ep_bank].PCKSIZE.reg |= USB_DEVICE_PCKSIZE_AUTO_ZLP;
		} else {
		usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[ep_bank].PCKSIZE.reg &= ~USB_DEVICE_PCKSIZE_AUTO_ZLP;
	}

	return STATUS_OK;
}

/**
 * \brief Check if current endpoint is configured
 *
 * \param module_inst   Pointer to USB software instance struct
 * \param ep            Endpoint address (direction & number)
 *
 * \return \c true if endpoint is configured and ready to use
 */
bool usb_device_endpoint_is_configured(struct usb_module *module_inst, uint8_t ep)
{
	uint8_t ep_num = ep & USB_EP_ADDR_MASK;
	uint8_t flag;

	if (ep & USB_EP_DIR_IN) {
		flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE1);
	} else {
		flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE0);
	}
	return ((enum usb_device_endpoint_type)(flag) != USB_DEVICE_ENDPOINT_TYPE_DISABLE);
}


/**
 * \brief Abort ongoing job on the endpoint
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 */
void usb_device_endpoint_abort_job(struct usb_module *module_inst, uint8_t ep)
{
	uint8_t ep_num;
	ep_num = ep & USB_EP_ADDR_MASK;

	// Stop transfer
	if (ep & USB_EP_DIR_IN) {
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
		// Eventually ack a transfer occur during abort
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1;
	} else {
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
		// Eventually ack a transfer occur during abort
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0;
	}
}

/**
 * \brief Check if endpoint is halted
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 *
 * \return \c true if the endpoint is halted
 */
bool usb_device_endpoint_is_halted(struct usb_module *module_inst, uint8_t ep)
{
	uint8_t ep_num = ep & USB_EP_ADDR_MASK;

	if (ep & USB_EP_DIR_IN) {
		return (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ1);
	} else {
		return (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ0);
	}
}

/**
 * \brief Halt the endpoint (send STALL)
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 */
void usb_device_endpoint_set_halt(struct usb_module *module_inst, uint8_t ep)
{
	uint8_t ep_num = ep & USB_EP_ADDR_MASK;

	// Stall endpoint
	if (ep & USB_EP_DIR_IN) {
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ1;
	} else {
		module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ0;
	}
}

/**
 * \brief Clear endpoint halt state
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 */
void usb_device_endpoint_clear_halt(struct usb_module *module_inst, uint8_t ep)
{
	uint8_t ep_num = ep & USB_EP_ADDR_MASK;

	if (ep & USB_EP_DIR_IN) {
		if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ1) {
			// Remove stall request
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1;
			if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL1) {
				module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL1;
				// The Stall has occurred, then reset data toggle
				module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSSET_DTGLIN;
			}
		}
	} else {
		if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ0) {
			// Remove stall request
			module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0;
			if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL0) {
				module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL0;
				// The Stall has occurred, then reset data toggle
				module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSSET_DTGLOUT;
			}
		}
	}
}

/**
 * \brief Start write buffer job on a endpoint
 *
 * \param module_inst Pointer to USB module instance
 * \param ep_num      Endpoint number
 * \param pbuf        Pointer to buffer
 * \param buf_size    Size of buffer
 *
 * \return Status of procedure
 * \retval STATUS_OK Job started successfully
 * \retval STATUS_ERR_DENIED Endpoint is not ready
 */
enum status_code usb_device_endpoint_write_buffer_job(struct usb_module *module_inst,uint8_t ep_num,
		uint8_t* pbuf, uint32_t buf_size)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(ep_num < USB_EPT_NUM);

	uint8_t flag;
	flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE1);
	if ((enum usb_device_endpoint_type)(flag) == USB_DEVICE_ENDPOINT_TYPE_DISABLE) {
		return STATUS_ERR_DENIED;
	};

	/* get endpoint configuration from setting register */
	usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].ADDR.reg = (uint32_t)pbuf;
	usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.bit.MULTI_PACKET_SIZE = 0;
	usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.bit.BYTE_COUNT = buf_size;
	module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK1RDY;

	return STATUS_OK;
}

/**
 * \brief Start read buffer job on a endpoint
 *
 * \param module_inst Pointer to USB module instance
 * \param ep_num      Endpoint number
 * \param pbuf        Pointer to buffer
 * \param buf_size    Size of buffer
 *
 * \return Status of procedure
 * \retval STATUS_OK Job started successfully
 * \retval STATUS_ERR_DENIED Endpoint is not ready
 */
enum status_code usb_device_endpoint_read_buffer_job(struct usb_module *module_inst,uint8_t ep_num,
		uint8_t* pbuf, uint32_t buf_size)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);
	Assert(ep_num < USB_EPT_NUM);

	uint8_t flag;
	flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE0);
	if ((enum usb_device_endpoint_type)(flag) == USB_DEVICE_ENDPOINT_TYPE_DISABLE) {
		return STATUS_ERR_DENIED;
	};

	/* get endpoint configuration from setting register */
	usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].ADDR.reg = (uint32_t)pbuf;
	usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = buf_size;
	usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT = 0;
	module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK0RDY;

	return STATUS_OK;
}

/**
 * \brief Start setup packet read job on a endpoint
 *
 * \param module_inst Pointer to USB device module instance
 * \param pbuf        Pointer to buffer
 *
 * \return Status of procedure
 * \retval STATUS_OK Job started successfully
 * \retval STATUS_ERR_DENIED Endpoint is not ready
 */
enum status_code usb_device_endpoint_setup_buffer_job(struct usb_module *module_inst,
		uint8_t* pbuf)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	/* get endpoint configuration from setting register */
	usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].ADDR.reg = (uint32_t)pbuf;
	usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = 8;
	usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT = 0;
	module_inst->hw->DEVICE.DeviceEndpoint[0].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK0RDY;

	return STATUS_OK;
}

static void _usb_device_interrupt_handler(void)
{
	uint16_t ep_inst;
	uint16_t flags, flags_run;
	ep_inst = _usb_instances->hw->DEVICE.EPINTSMRY.reg;

	/* device interrupt */
	if (0 == ep_inst) {
		int i;

		/* get interrupt flags */
		flags = _usb_instances->hw->DEVICE.INTFLAG.reg;
		flags_run = flags &
				_usb_instances->device_enabled_callback_mask &
				_usb_instances->device_registered_callback_mask;

		for (i = 0; i < USB_DEVICE_CALLBACK_N; i ++) {
			if (flags & _usb_device_irq_bits[i]) {
				_usb_instances->hw->DEVICE.INTFLAG.reg =
						_usb_device_irq_bits[i];
			}
			if (flags_run & _usb_device_irq_bits[i]) {
				if (i == USB_DEVICE_CALLBACK_LPMSUSP) {
					device_callback_lpm_wakeup_enable =
							usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].EXTREG.bit.VARIABLE
							& USB_LPM_ATTRIBUT_REMOTEWAKE_MASK;
				}
				(_usb_instances->device_callback[i])(_usb_instances, &device_callback_lpm_wakeup_enable);
			}
		}

	} else {
		/* endpoint interrupt */

		for (uint8_t i = 0; i < USB_EPT_NUM; i++) {

			if (ep_inst & (1 << i)) {
				flags = _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg;
				flags_run = flags &
						_usb_instances->device_endpoint_enabled_callback_mask[i] &
						_usb_instances->device_endpoint_registered_callback_mask[i];

				// endpoint transfer stall interrupt
				if (flags & USB_DEVICE_EPINTFLAG_STALL_Msk) {
					if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL1) {
						_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL1;
						ep_callback_para.endpoint_address = USB_EP_DIR_IN | i;
					} else if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL0) {
						_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL0;
						ep_callback_para.endpoint_address = USB_EP_DIR_OUT | i;
					}

					if (flags_run & USB_DEVICE_EPINTFLAG_STALL_Msk) {
						(_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_STALL])(_usb_instances,&ep_callback_para);
					}
					return;
				}

				// endpoint received setup interrupt
				if (flags & USB_DEVICE_EPINTFLAG_RXSTP) {
					_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_RXSTP;
					if(_usb_instances->device_endpoint_enabled_callback_mask[i] & _usb_endpoint_irq_bits[USB_DEVICE_ENDPOINT_CALLBACK_RXSTP]) {
						ep_callback_para.received_bytes = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT);
						(_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_RXSTP])(_usb_instances,&ep_callback_para);
					}
					return;
				}

				// endpoint transfer complete interrupt
				if (flags & USB_DEVICE_EPINTFLAG_TRCPT_Msk) {
					if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT1) {
						_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1;
						ep_callback_para.endpoint_address = USB_EP_DIR_IN | i;
						ep_callback_para.sent_bytes = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[1].PCKSIZE.bit.BYTE_COUNT);

					} else if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT0) {
						_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0;
						ep_callback_para.endpoint_address = USB_EP_DIR_OUT | i;
						ep_callback_para.received_bytes = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT);
						ep_callback_para.out_buffer_size = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE);
					}
					if(flags_run & USB_DEVICE_EPINTFLAG_TRCPT_Msk) {
						(_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_TRCPT])(_usb_instances,&ep_callback_para);
					}
					return;
				}

				// endpoint transfer fail interrupt
				if (flags & USB_DEVICE_EPINTFLAG_TRFAIL_Msk) {
					if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRFAIL1) {
						_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL1;
						if (usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[1].STATUS_BK.reg & USB_DEVICE_STATUS_BK_ERRORFLOW) {
							usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[1].STATUS_BK.reg &= ~USB_DEVICE_STATUS_BK_ERRORFLOW;
						}
						ep_callback_para.endpoint_address = USB_EP_DIR_IN | i;
						if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT1) {
							return;
						}
					} else if(_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRFAIL0) {
						_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL0;
						if (usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].STATUS_BK.reg & USB_DEVICE_STATUS_BK_ERRORFLOW) {
							usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].STATUS_BK.reg &= ~USB_DEVICE_STATUS_BK_ERRORFLOW;
						}
						ep_callback_para.endpoint_address = USB_EP_DIR_OUT | i;
						if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT0) {
							return;
						}
					}

					if(flags_run & USB_DEVICE_EPINTFLAG_TRFAIL_Msk) {
						(_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL])(_usb_instances,&ep_callback_para);
					}
					return;
				}
			}
		}
	}
}

/**
 * \brief Enable the USB module peripheral
 *
 * \param module_inst pointer to USB module instance
 */
void usb_enable(struct usb_module *module_inst)
{
	Assert(module_inst);
	Assert(module_inst->hw);

	module_inst->hw->DEVICE.CTRLA.reg |= USB_CTRLA_ENABLE;
	while (module_inst->hw->DEVICE.SYNCBUSY.reg == USB_SYNCBUSY_ENABLE);
}

/**
 * \brief Disable the USB module peripheral
 *
 * \param module_inst pointer to USB module instance
 */
void usb_disable(struct usb_module *module_inst)
{
	Assert(module_inst);
	Assert(module_inst->hw);

	module_inst->hw->DEVICE.INTENCLR.reg = USB_DEVICE_INTENCLR_MASK;
	module_inst->hw->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_MASK;
	module_inst->hw->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE;
	while (module_inst->hw->DEVICE.SYNCBUSY.reg == USB_SYNCBUSY_ENABLE);
}

/**
 * \brief Interrupt handler for the USB module.
 */
void USB_Handler(void)
{
	if (_usb_instances->hw->DEVICE.CTRLA.bit.MODE) {
#if !SAMD11
		/*host mode ISR */
		_usb_host_interrupt_handler();
#endif
	} else {
		/*device mode ISR */
		_usb_device_interrupt_handler();
	}
}

/**
 * \brief Get the default USB module settings
 *
 * \param[out] module_config  Configuration structure to initialize to default values
 */
void usb_get_config_defaults(struct usb_config *module_config)
{
	Assert(module_config);

	/* Sanity check arguments */
	Assert(module_config);
	/* Write default configuration to config struct */
	module_config->select_host_mode = 0;
	module_config->run_in_standby = 1;
	module_config->source_generator = GCLK_GENERATOR_0;
	module_config->speed_mode = USB_SPEED_FULL;
}

#define NVM_USB_PAD_TRANSN_POS  45
#define NVM_USB_PAD_TRANSN_SIZE 5
#define NVM_USB_PAD_TRANSP_POS  50
#define NVM_USB_PAD_TRANSP_SIZE 5
#define NVM_USB_PAD_TRIM_POS  55
#define NVM_USB_PAD_TRIM_SIZE 3

/**
 * \brief Initializes USB module instance
 *
 * Enables the clock and initializes the USB module, based on the given
 * configuration values.
 *
 * \param[in,out] module_inst   Pointer to the software module instance struct
 * \param[in]     hw            Pointer to the USB hardware module
 * \param[in]     module_config Pointer to the USB configuration options struct
 *
 * \return Status of the initialization procedure.
 *
 * \retval STATUS_OK           The module was initialized successfully
 */
enum status_code usb_init(struct usb_module *module_inst, Usb *const hw,
		struct usb_config *module_config)
{
	/* Sanity check arguments */
	Assert(hw);
	Assert(module_inst);
	Assert(module_config);

	uint32_t i,j;
	uint32_t pad_transn, pad_transp, pad_trim;
	struct system_pinmux_config pin_config;
	struct system_gclk_chan_config gclk_chan_config;

#if !SAMD11
	host_pipe_job_busy_status = 0;
#endif

	_usb_instances = module_inst;

	/* Associate the software module instance with the hardware module */
	module_inst->hw = hw;

	/* Turn on the digital interface clock */
	system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBB, PM_APBBMASK_USB);

	/* Set up the USB DP/DN pins */
	system_pinmux_get_config_defaults(&pin_config);
	pin_config.mux_position = MUX_PA24G_USB_DM;
	system_pinmux_pin_set_config(PIN_PA24G_USB_DM, &pin_config);
	pin_config.mux_position = MUX_PA25G_USB_DP;
	system_pinmux_pin_set_config(PIN_PA25G_USB_DP, &pin_config);

	/* Setup clock for module */
	system_gclk_chan_get_config_defaults(&gclk_chan_config);
	gclk_chan_config.source_generator = module_config->source_generator;
	system_gclk_chan_set_config(USB_GCLK_ID, &gclk_chan_config);
	system_gclk_chan_enable(USB_GCLK_ID);

	/* Reset */
	hw->DEVICE.CTRLA.bit.SWRST = 1;
	while (hw->DEVICE.SYNCBUSY.bit.SWRST) {
		/* Sync wait */
	}

	/* Change QOS values to have the best performance and correct USB behaviour */
	USB->DEVICE.QOSCTRL.bit.CQOS = 2;
	USB->DEVICE.QOSCTRL.bit.DQOS = 2;

	/* Load Pad Calibration */
	pad_transn =( *((uint32_t *)(NVMCTRL_OTP4)
			+ (NVM_USB_PAD_TRANSN_POS / 32))
		>> (NVM_USB_PAD_TRANSN_POS % 32))
		& ((1 << NVM_USB_PAD_TRANSN_SIZE) - 1);

	if (pad_transn == 0x1F) {
		pad_transn = 5;
	}

	hw->DEVICE.PADCAL.bit.TRANSN = pad_transn;

	pad_transp =( *((uint32_t *)(NVMCTRL_OTP4)
			+ (NVM_USB_PAD_TRANSP_POS / 32))
			>> (NVM_USB_PAD_TRANSP_POS % 32))
			& ((1 << NVM_USB_PAD_TRANSP_SIZE) - 1);

	if (pad_transp == 0x1F) {
		pad_transp = 29;
	}

	hw->DEVICE.PADCAL.bit.TRANSP = pad_transp;

	pad_trim =( *((uint32_t *)(NVMCTRL_OTP4)
			+ (NVM_USB_PAD_TRIM_POS / 32))
			>> (NVM_USB_PAD_TRIM_POS % 32))
			& ((1 << NVM_USB_PAD_TRIM_SIZE) - 1);

	if (pad_trim == 0x7) {
		pad_trim = 3;
	}

	hw->DEVICE.PADCAL.bit.TRIM = pad_trim;

	/* Set the configuration */
	hw->DEVICE.CTRLA.bit.MODE = module_config->select_host_mode;
	hw->DEVICE.CTRLA.bit.RUNSTDBY = module_config->run_in_standby;
	hw->DEVICE.DESCADD.reg = (uint32_t)(&usb_descriptor_table.usb_endpoint_table[0]);
	if (USB_SPEED_FULL == module_config->speed_mode) {
		module_inst->hw->DEVICE.CTRLB.bit.SPDCONF = USB_DEVICE_CTRLB_SPDCONF_FS_Val;
	} else if(USB_SPEED_LOW == module_config->speed_mode) {
		module_inst->hw->DEVICE.CTRLB.bit.SPDCONF = USB_DEVICE_CTRLB_SPDCONF_LS_Val;
	}

	memset((uint8_t *)(&usb_descriptor_table.usb_endpoint_table[0]), 0,
			sizeof(usb_descriptor_table.usb_endpoint_table));

#if !SAMD11
	/* callback related init */
	for (i = 0; i < USB_HOST_CALLBACK_N; i++) {
		module_inst->host_callback[i] = NULL;
	};
	for (i = 0; i < USB_PIPE_NUM; i++) {
		for (j = 0; j < USB_HOST_PIPE_CALLBACK_N; j++) {
			module_inst->host_pipe_callback[i][j] = NULL;
		}
	};
	module_inst->host_registered_callback_mask = 0;
	module_inst->host_enabled_callback_mask = 0;
	for (i = 0; i < USB_PIPE_NUM; i++) {
		module_inst->host_pipe_registered_callback_mask[i] = 0;
		module_inst->host_pipe_enabled_callback_mask[i] = 0;
	}
#endif

	/*  device callback related */
	for (i = 0; i < USB_DEVICE_CALLBACK_N; i++) {
		module_inst->device_callback[i] = NULL;
	}
	for (i = 0; i < USB_EPT_NUM; i++) {
		for(j = 0; j < USB_DEVICE_EP_CALLBACK_N; j++) {
			module_inst->device_endpoint_callback[i][j] = NULL;
		}
	}
	module_inst->device_registered_callback_mask = 0;
	module_inst->device_enabled_callback_mask = 0;
	for (j = 0; j < USB_EPT_NUM; j++) {
		module_inst->device_endpoint_registered_callback_mask[j] = 0;
		module_inst->device_endpoint_enabled_callback_mask[j] = 0;
	}

	/* Enable interrupts for this USB module */
	system_interrupt_enable(SYSTEM_INTERRUPT_MODULE_USB);

	return STATUS_OK;
}


