/**
 * \file
 *
 * \brief USB Device wrapper layer for compliance with common driver UDD
 *
 * Copyright (C) 2014-2016 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
 */
#include "samd51j18a.h"
#include <string.h>
#include <stdlib.h>

// Get USB device configuration
#include "conf_usb.h"
#include "udd.h"
#include "usb.h"
#include "status_codes.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

// Note: This driver is adapted for SAMD51

#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
 *
 * @{
 */

#define UDD_CLOCK_GEN 0

static inline void udd_wait_clock_ready(void) {}

/**
 * \name Power management
 *
 * @{
 */
#define udd_sleep_mode(arg)
/** @} */

/**
 * \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
 */
UDC_BSS(4)
uint8_t udd_ctrl_buffer[USB_DEVICE_EP_CTRL_SIZE];

/** 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;

/** 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)
UDC_BSS(4) uint8_t udd_ep_out_cache_buffer[USB_DEVICE_MAX_EP][8];
#elif (defined USB_DEVICE_HS_SUPPORT)
UDC_BSS(4) uint8_t udd_ep_out_cache_buffer[USB_DEVICE_MAX_EP][512];
#else
UDC_BSS(4) uint8_t udd_ep_out_cache_buffer[USB_DEVICE_MAX_EP][64];
#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 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;
    uint32_t      irqflags;

    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);

    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();

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

    ptr_job->busy = true;
    __DMB();
    __set_PRIMASK(irqflags);

    /* 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);
            }
        }
    }
}

/**
 * \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);
    }
}

/**
 * \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;
    }
}

/**
 * \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
}

#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
}

/**
 * \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);
}

/**
 * \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 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
}

void udd_enable(void) {
    uint32_t irqflags;

    /* To avoid USB interrupt before end of initialization */
    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();

    struct usb_config config_usb;

    /* USB Module configuration */
    usb_get_config_defaults(&config_usb);
    config_usb.source_generator = UDD_CLOCK_GEN;
    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);

    // No VBus detect, assume always high
#ifndef USB_DEVICE_ATTACH_AUTO_DISABLE
    udd_attach();
#endif

    __DMB();
    __set_PRIMASK(irqflags);
}

void udd_disable(void) {
    udd_detach();

    udd_sleep_mode(UDD_STATE_OFF);
}
/** @} */
