/**
 * \file
 *
 * \brief USB Device Communication Device Class (CDC) interface.
 *
 * Copyright (c) 2009-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 "conf_usb.h"
#include "usb_protocol.h"
#include "usb_protocol_cdc.h"
#include "udd.h"
#include "udc.h"
#include "udi_cdc.h"
#include <string.h>
#include "udi_cdc_conf.h"
#include "udi_device_conf.h"
#include "stdarg.h"
#include "tmk_core/protocol/arm_atsam/clks.h"

#ifdef VIRTSER_ENABLE

#    ifdef UDI_CDC_LOW_RATE
#        ifdef USB_DEVICE_HS_SUPPORT
#            define UDI_CDC_TX_BUFFERS (UDI_CDC_DATA_EPS_HS_SIZE)
#            define UDI_CDC_RX_BUFFERS (UDI_CDC_DATA_EPS_HS_SIZE)
#        else
#            define UDI_CDC_TX_BUFFERS (UDI_CDC_DATA_EPS_FS_SIZE)
#            define UDI_CDC_RX_BUFFERS (UDI_CDC_DATA_EPS_FS_SIZE)
#        endif
#    else
#        ifdef USB_DEVICE_HS_SUPPORT
#            define UDI_CDC_TX_BUFFERS (UDI_CDC_DATA_EPS_HS_SIZE)
#            define UDI_CDC_RX_BUFFERS (UDI_CDC_DATA_EPS_HS_SIZE)
#        else
#            define UDI_CDC_TX_BUFFERS (5 * UDI_CDC_DATA_EPS_FS_SIZE)
#            define UDI_CDC_RX_BUFFERS (5 * UDI_CDC_DATA_EPS_FS_SIZE)
#        endif
#    endif

#    ifndef UDI_CDC_TX_EMPTY_NOTIFY
#        define UDI_CDC_TX_EMPTY_NOTIFY(port)
#    endif

/**
 * \ingroup udi_cdc_group
 * \defgroup udi_cdc_group_udc Interface with USB Device Core (UDC)
 *
 * Structures and functions required by UDC.
 *
 * @{
 */
bool                       udi_cdc_comm_enable(void);
void                       udi_cdc_comm_disable(void);
bool                       udi_cdc_comm_setup(void);
bool                       udi_cdc_data_enable(void);
void                       udi_cdc_data_disable(void);
bool                       udi_cdc_data_setup(void);
uint8_t                    udi_cdc_getsetting(void);
void                       udi_cdc_data_sof_notify(void);
UDC_DESC_STORAGE udi_api_t udi_api_cdc_comm = {.enable = udi_cdc_comm_enable, .disable = udi_cdc_comm_disable, .setup = udi_cdc_comm_setup, .getsetting = udi_cdc_getsetting, .sof_notify = NULL};
UDC_DESC_STORAGE udi_api_t udi_api_cdc_data = {
    .enable     = udi_cdc_data_enable,
    .disable    = udi_cdc_data_disable,
    .setup      = udi_cdc_data_setup,
    .getsetting = udi_cdc_getsetting,
    .sof_notify = udi_cdc_data_sof_notify,
};
//@}

/**
 * \ingroup udi_cdc_group
 * \defgroup udi_cdc_group_internal Implementation of UDI CDC
 *
 * Class internal implementation
 * @{
 */

/**
 * \name Internal routines
 */
//@{

/**
 * \name Routines to control serial line
 */
//@{

/**
 * \brief Returns the port number corresponding at current setup request
 *
 * \return port number
 */
static uint8_t udi_cdc_setup_to_port(void);

/**
 * \brief Sends line coding to application
 *
 * Called after SETUP request when line coding data is received.
 */
static void udi_cdc_line_coding_received(void);

/**
 * \brief Records new state
 *
 * \param port       Communication port number to manage
 * \param b_set      State is enabled if true, else disabled
 * \param bit_mask   Field to process (see CDC_SERIAL_STATE_ defines)
 */
static void udi_cdc_ctrl_state_change(uint8_t port, bool b_set, le16_t bit_mask);

/**
 * \brief Check and eventually notify the USB host of new state
 *
 * \param port       Communication port number to manage
 * \param ep         Port communication endpoint
 */
static void udi_cdc_ctrl_state_notify(uint8_t port, udd_ep_id_t ep);

/**
 * \brief Ack sent of serial state message
 * Callback called after serial state message sent
 *
 * \param status     UDD_EP_TRANSFER_OK, if transfer finished
 * \param status     UDD_EP_TRANSFER_ABORT, if transfer aborted
 * \param n          number of data transfered
 */
static void udi_cdc_serial_state_msg_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep);

//@}

/**
 * \name Routines to process data transfer
 */
//@{

/**
 * \brief Enable the reception of data from the USB host
 *
 * The value udi_cdc_rx_trans_sel indicate the RX buffer to fill.
 *
 * \param port       Communication port number to manage
 *
 * \return \c 1 if function was successfully done, otherwise \c 0.
 */
static bool udi_cdc_rx_start(uint8_t port);

/**
 * \brief Update rx buffer management with a new data
 * Callback called after data reception on USB line
 *
 * \param status     UDD_EP_TRANSFER_OK, if transfer finish
 * \param status     UDD_EP_TRANSFER_ABORT, if transfer aborted
 * \param n          number of data received
 */
static void udi_cdc_data_received(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep);

/**
 * \brief Ack sent of tx buffer
 * Callback called after data transfer on USB line
 *
 * \param status     UDD_EP_TRANSFER_OK, if transfer finished
 * \param status     UDD_EP_TRANSFER_ABORT, if transfer aborted
 * \param n          number of data transfered
 */
static void udi_cdc_data_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep);

/**
 * \brief Send buffer on line or wait a SOF event
 *
 * \param port       Communication port number to manage
 */
static void udi_cdc_tx_send(uint8_t port);

//@}

//@}

/**
 * \name Information about configuration of communication line
 */
//@{
COMPILER_WORD_ALIGNED
static usb_cdc_line_coding_t                               udi_cdc_line_coding[UDI_CDC_PORT_NB];
static bool                                                udi_cdc_serial_state_msg_ongoing[UDI_CDC_PORT_NB];
static volatile le16_t                                     udi_cdc_state[UDI_CDC_PORT_NB];
COMPILER_WORD_ALIGNED static usb_cdc_notify_serial_state_t uid_cdc_state_msg[UDI_CDC_PORT_NB];

//! Status of CDC COMM interfaces
static volatile uint8_t udi_cdc_nb_comm_enabled = 0;
//@}

/**
 * \name Variables to manage RX/TX transfer requests
 * Two buffers for each sense are used to optimize the speed.
 */
//@{

//! Status of CDC DATA interfaces
static volatile uint8_t udi_cdc_nb_data_enabled = 0;
static volatile bool    udi_cdc_data_running    = false;
//! Buffer to receive data
COMPILER_WORD_ALIGNED static uint8_t udi_cdc_rx_buf[UDI_CDC_PORT_NB][2][UDI_CDC_RX_BUFFERS];
//! Data available in RX buffers
static volatile uint16_t udi_cdc_rx_buf_nb[UDI_CDC_PORT_NB][2];
//! Give the current RX buffer used (rx0 if 0, rx1 if 1)
static volatile uint8_t udi_cdc_rx_buf_sel[UDI_CDC_PORT_NB];
//! Read position in current RX buffer
static volatile uint16_t udi_cdc_rx_pos[UDI_CDC_PORT_NB];
//! Signal a transfer on-going
static volatile bool udi_cdc_rx_trans_ongoing[UDI_CDC_PORT_NB];

//! Define a transfer halted
#    define UDI_CDC_TRANS_HALTED 2

//! Buffer to send data
COMPILER_WORD_ALIGNED static uint8_t udi_cdc_tx_buf[UDI_CDC_PORT_NB][2][UDI_CDC_TX_BUFFERS];
//! Data available in TX buffers
static uint16_t udi_cdc_tx_buf_nb[UDI_CDC_PORT_NB][2];
//! Give current TX buffer used (tx0 if 0, tx1 if 1)
static volatile uint8_t udi_cdc_tx_buf_sel[UDI_CDC_PORT_NB];
//! Value of SOF during last TX transfer
static uint16_t udi_cdc_tx_sof_num[UDI_CDC_PORT_NB];
//! Signal a transfer on-going
static volatile bool udi_cdc_tx_trans_ongoing[UDI_CDC_PORT_NB];
//! Signal that both buffer content data to send
static volatile bool udi_cdc_tx_both_buf_to_send[UDI_CDC_PORT_NB];

//@}

bool udi_cdc_comm_enable(void) {
    uint8_t port;
    uint8_t iface_comm_num;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port                    = 0;
    udi_cdc_nb_comm_enabled = 0;
    //#else
    //    if (udi_cdc_nb_comm_enabled > UDI_CDC_PORT_NB) {
    //        udi_cdc_nb_comm_enabled = 0;
    //    }
    //    port = udi_cdc_nb_comm_enabled;
    //#endif

    // Initialize control signal management
    udi_cdc_state[port] = CPU_TO_LE16(0);

    uid_cdc_state_msg[port].header.bmRequestType = USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | USB_REQ_RECIP_INTERFACE;
    uid_cdc_state_msg[port].header.bNotification = USB_REQ_CDC_NOTIFY_SERIAL_STATE;
    uid_cdc_state_msg[port].header.wValue        = LE16(0);

    /*
    switch (port) {
    #define UDI_CDC_PORT_TO_IFACE_COMM(index, unused) \
        case index: \
            iface_comm_num = UDI_CDC_COMM_IFACE_NUMBER_##index; \
            break;
        MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_PORT_TO_IFACE_COMM, ~)
    #undef UDI_CDC_PORT_TO_IFACE_COMM
        default:
            iface_comm_num = UDI_CDC_COMM_IFACE_NUMBER_0;
            break;
        }
    */
    iface_comm_num = UDI_CDC_COMM_IFACE_NUMBER_0;

    uid_cdc_state_msg[port].header.wIndex  = LE16(iface_comm_num);
    uid_cdc_state_msg[port].header.wLength = LE16(2);
    uid_cdc_state_msg[port].value          = CPU_TO_LE16(0);

    udi_cdc_line_coding[port].dwDTERate   = CPU_TO_LE32(UDI_CDC_DEFAULT_RATE);
    udi_cdc_line_coding[port].bCharFormat = UDI_CDC_DEFAULT_STOPBITS;
    udi_cdc_line_coding[port].bParityType = UDI_CDC_DEFAULT_PARITY;
    udi_cdc_line_coding[port].bDataBits   = UDI_CDC_DEFAULT_DATABITS;
    // Call application callback
    // to initialize memories or indicate that interface is enabled
#    if 0
    UDI_CDC_SET_CODING_EXT(port,(&udi_cdc_line_coding[port]));
    if (!UDI_CDC_ENABLE_EXT(port)) {
        return false;
    }
#    endif
    udi_cdc_nb_comm_enabled++;
    return true;
}

bool udi_cdc_data_enable(void) {
    uint8_t port;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port                    = 0;
    udi_cdc_nb_data_enabled = 0;
    //#else
    //    if (udi_cdc_nb_data_enabled > UDI_CDC_PORT_NB) {
    //        udi_cdc_nb_data_enabled = 0;
    //    }
    //    port = udi_cdc_nb_data_enabled;
    //#endif

    // Initialize TX management
    udi_cdc_tx_trans_ongoing[port]    = false;
    udi_cdc_tx_both_buf_to_send[port] = false;
    udi_cdc_tx_buf_sel[port]          = 0;
    udi_cdc_tx_buf_nb[port][0]        = 0;
    udi_cdc_tx_buf_nb[port][1]        = 0;
    udi_cdc_tx_sof_num[port]          = 0;
    udi_cdc_tx_send(port);

    // Initialize RX management
    udi_cdc_rx_trans_ongoing[port] = false;
    udi_cdc_rx_buf_sel[port]       = 0;
    udi_cdc_rx_buf_nb[port][0]     = 0;
    udi_cdc_rx_buf_nb[port][1]     = 0;
    udi_cdc_rx_pos[port]           = 0;
    if (!udi_cdc_rx_start(port)) {
        return false;
    }
    udi_cdc_nb_data_enabled++;
    if (udi_cdc_nb_data_enabled == UDI_CDC_PORT_NB) {
        udi_cdc_data_running = true;
    }
    return true;
}

void udi_cdc_comm_disable(void) {
    Assert(udi_cdc_nb_comm_enabled != 0);
    udi_cdc_nb_comm_enabled--;
}

void udi_cdc_data_disable(void) {
    //  uint8_t port;

    Assert(udi_cdc_nb_data_enabled != 0);
    udi_cdc_nb_data_enabled--;
    //  port = udi_cdc_nb_data_enabled;
    //  UDI_CDC_DISABLE_EXT(port);
    udi_cdc_data_running = false;
}

bool udi_cdc_comm_setup(void) {
    uint8_t port = udi_cdc_setup_to_port();

    if (Udd_setup_is_in()) {
        // GET Interface Requests
        if (Udd_setup_type() == USB_REQ_TYPE_CLASS) {
            // Requests Class Interface Get
            switch (udd_g_ctrlreq.req.bRequest) {
                case USB_REQ_CDC_GET_LINE_CODING:
                    // Get configuration of CDC line
                    if (sizeof(usb_cdc_line_coding_t) != udd_g_ctrlreq.req.wLength) return false; // Error for USB host
                    udd_g_ctrlreq.payload      = (uint8_t *)&udi_cdc_line_coding[port];
                    udd_g_ctrlreq.payload_size = sizeof(usb_cdc_line_coding_t);
                    return true;
            }
        }
    }
    if (Udd_setup_is_out()) {
        // SET Interface Requests
        if (Udd_setup_type() == USB_REQ_TYPE_CLASS) {
            // Requests Class Interface Set
            switch (udd_g_ctrlreq.req.bRequest) {
                case USB_REQ_CDC_SET_LINE_CODING:
                    // Change configuration of CDC line
                    if (sizeof(usb_cdc_line_coding_t) != udd_g_ctrlreq.req.wLength) return false; // Error for USB host
                    udd_g_ctrlreq.callback     = udi_cdc_line_coding_received;
                    udd_g_ctrlreq.payload      = (uint8_t *)&udi_cdc_line_coding[port];
                    udd_g_ctrlreq.payload_size = sizeof(usb_cdc_line_coding_t);
                    return true;
                case USB_REQ_CDC_SET_CONTROL_LINE_STATE:
                    // According cdc spec 1.1 chapter 6.2.14
                    //              UDI_CDC_SET_DTR_EXT(port, (0 !=
                    //                      (udd_g_ctrlreq.req.wValue
                    //                       & CDC_CTRL_SIGNAL_DTE_PRESENT)));
                    //              UDI_CDC_SET_RTS_EXT(port, (0 !=
                    //                      (udd_g_ctrlreq.req.wValue
                    //                       & CDC_CTRL_SIGNAL_ACTIVATE_CARRIER)));
                    return true;
            }
        }
    }
    return false; // request Not supported
}

bool udi_cdc_data_setup(void) {
    return false; // request Not supported
}

uint8_t udi_cdc_getsetting(void) {
    return 0; // CDC don't have multiple alternate setting
}

void udi_cdc_data_sof_notify(void) {
    static uint8_t port_notify = 0;

    // A call of udi_cdc_data_sof_notify() is done for each port
    udi_cdc_tx_send(port_notify);
    /*
#if UDI_CDC_PORT_NB != 1 // To optimize code
    port_notify++;
    if (port_notify >= UDI_CDC_PORT_NB) {
        port_notify = 0;
    }
#endif
    */
}

//-------------------------------------------------
//------- Internal routines to control serial line

static uint8_t udi_cdc_setup_to_port(void) {
    uint8_t port;

    /*
    switch (udd_g_ctrlreq.req.wIndex & 0xFF) {
#define UDI_CDC_IFACE_COMM_TO_PORT(iface, unused) \
    case UDI_CDC_COMM_IFACE_NUMBER_##iface: \
        port = iface; \
        break;
    MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_IFACE_COMM_TO_PORT, ~)
#undef UDI_CDC_IFACE_COMM_TO_PORT
    default:
        port = 0;
        break;
    }
    */
    port = 0;

    return port;
}

static void udi_cdc_line_coding_received(void) {
    uint8_t port = udi_cdc_setup_to_port();
    UNUSED(port);

    //  UDI_CDC_SET_CODING_EXT(port, (&udi_cdc_line_coding[port]));
}

static void udi_cdc_ctrl_state_change(uint8_t port, bool b_set, le16_t bit_mask) {
    udd_ep_id_t ep_comm;
    uint32_t    irqflags; // irqflags_t

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    // Update state
    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    if (b_set) {
        udi_cdc_state[port] |= bit_mask;
    } else {
        udi_cdc_state[port] &= ~(unsigned)bit_mask;
    }
    __DMB();
    __set_PRIMASK(irqflags);

    /*
    // Send it if possible and state changed
    switch (port) {
#define UDI_CDC_PORT_TO_COMM_EP(index, unused) \
    case index: \
        ep_comm = UDI_CDC_COMM_EP_##index; \
        break;
    MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_PORT_TO_COMM_EP, ~)
#undef UDI_CDC_PORT_TO_COMM_EP
    default:
        ep_comm = UDI_CDC_COMM_EP_0;
        break;
    }
    */
    ep_comm = UDI_CDC_COMM_EP_0;

    udi_cdc_ctrl_state_notify(port, ep_comm);
}

static void udi_cdc_ctrl_state_notify(uint8_t port, udd_ep_id_t ep) {
#    if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
#    endif

    // Send it if possible and state changed
    if ((!udi_cdc_serial_state_msg_ongoing[port]) && (udi_cdc_state[port] != uid_cdc_state_msg[port].value)) {
        // Fill notification message
        uid_cdc_state_msg[port].value = udi_cdc_state[port];
        // Send notification message
        udi_cdc_serial_state_msg_ongoing[port] = udd_ep_run(ep, false, (uint8_t *)&uid_cdc_state_msg[port], sizeof(uid_cdc_state_msg[0]), udi_cdc_serial_state_msg_sent);
    }
}

static void udi_cdc_serial_state_msg_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep) {
    uint8_t port;
    UNUSED(n);
    UNUSED(status);

    /*
    switch (ep) {
#define UDI_CDC_GET_PORT_FROM_COMM_EP(iface, unused) \
    case UDI_CDC_COMM_EP_##iface: \
        port = iface; \
        break;
    MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_GET_PORT_FROM_COMM_EP, ~)
#undef UDI_CDC_GET_PORT_FROM_COMM_EP
    default:
        port = 0;
        break;
    }
    */
    port = 0;

    udi_cdc_serial_state_msg_ongoing[port] = false;

    // For the irregular signals like break, the incoming ring signal,
    // or the overrun error state, this will reset their values to zero
    // and again will not send another notification until their state changes.
    udi_cdc_state[port] &= ~(CDC_SERIAL_STATE_BREAK | CDC_SERIAL_STATE_RING | CDC_SERIAL_STATE_FRAMING | CDC_SERIAL_STATE_PARITY | CDC_SERIAL_STATE_OVERRUN);
    uid_cdc_state_msg[port].value &= ~(CDC_SERIAL_STATE_BREAK | CDC_SERIAL_STATE_RING | CDC_SERIAL_STATE_FRAMING | CDC_SERIAL_STATE_PARITY | CDC_SERIAL_STATE_OVERRUN);
    // Send it if possible and state changed
    udi_cdc_ctrl_state_notify(port, ep);
}

//-------------------------------------------------
//------- Internal routines to process data transfer

static bool udi_cdc_rx_start(uint8_t port) {
    uint32_t    irqflags; // irqflags_t
    uint8_t     buf_sel_trans;
    udd_ep_id_t ep;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    buf_sel_trans = udi_cdc_rx_buf_sel[port];
    if (udi_cdc_rx_trans_ongoing[port] || (udi_cdc_rx_pos[port] < udi_cdc_rx_buf_nb[port][buf_sel_trans])) {
        // Transfer already on-going or current buffer no empty
        __DMB();
        __set_PRIMASK(irqflags);
        return false;
    }

    // Change current buffer
    udi_cdc_rx_pos[port]     = 0;
    udi_cdc_rx_buf_sel[port] = (buf_sel_trans == 0) ? 1 : 0;

    // Start transfer on RX
    udi_cdc_rx_trans_ongoing[port] = true;
    __DMB();
    __set_PRIMASK(irqflags);

    if (udi_cdc_multi_is_rx_ready(port)) {
        //      UDI_CDC_RX_NOTIFY(port);
    }

    /*
    // Send the buffer with enable of short packet
    switch (port) {
#define UDI_CDC_PORT_TO_DATA_EP_OUT(index, unused) \
    case index: \
        ep = UDI_CDC_DATA_EP_OUT_##index; \
        break;
    MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_PORT_TO_DATA_EP_OUT, ~)
#undef UDI_CDC_PORT_TO_DATA_EP_OUT
    default:
        ep = UDI_CDC_DATA_EP_OUT_0;
        break;
    }
    */
    ep = UDI_CDC_DATA_EP_OUT_0;

    return udd_ep_run(ep, true, udi_cdc_rx_buf[port][buf_sel_trans], UDI_CDC_RX_BUFFERS, udi_cdc_data_received);
}

static void udi_cdc_data_received(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep) {
    uint8_t buf_sel_trans;
    uint8_t port;

    /*
    switch (ep) {
#define UDI_CDC_DATA_EP_OUT_TO_PORT(index, unused) \
    case UDI_CDC_DATA_EP_OUT_##index: \
        port = index; \
        break;
    MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_DATA_EP_OUT_TO_PORT, ~)
#undef UDI_CDC_DATA_EP_OUT_TO_PORT
    default:
        port = 0;
        break;
    }
    */
    port = 0;

    if (UDD_EP_TRANSFER_OK != status) {
        // Abort reception
        return;
    }

    buf_sel_trans = (udi_cdc_rx_buf_sel[port] == 0) ? 1 : 0;

    if (!n) {
        udd_ep_run(ep, true, udi_cdc_rx_buf[port][buf_sel_trans], UDI_CDC_RX_BUFFERS, udi_cdc_data_received);
        return;
    }

    udi_cdc_rx_buf_nb[port][buf_sel_trans] = n;
    udi_cdc_rx_trans_ongoing[port]         = false;
    udi_cdc_rx_start(port);
}

static void udi_cdc_data_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep) {
    uint8_t port;
    UNUSED(n);

    /*
    switch (ep) {
#define UDI_CDC_DATA_EP_IN_TO_PORT(index, unused) \
    case UDI_CDC_DATA_EP_IN_##index: \
        port = index; \
        break;
    MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_DATA_EP_IN_TO_PORT, ~)
#undef UDI_CDC_DATA_EP_IN_TO_PORT
    default:
        port = 0;
        break;
    }
    */
    port = 0;

    if (UDD_EP_TRANSFER_OK != status) {
        // Abort transfer
        return;
    }

    udi_cdc_tx_buf_nb[port][(udi_cdc_tx_buf_sel[port] == 0) ? 1 : 0] = 0;
    udi_cdc_tx_both_buf_to_send[port]                                = false;
    udi_cdc_tx_trans_ongoing[port]                                   = false;

    if (n != 0) {
        UDI_CDC_TX_EMPTY_NOTIFY(port);
    }

    udi_cdc_tx_send(port);
}

static void udi_cdc_tx_send(uint8_t port) {
    uint32_t        irqflags; // irqflags_t
    uint8_t         buf_sel_trans;
    bool            b_short_packet;
    udd_ep_id_t     ep;
    static uint16_t sof_zlp_counter = 0;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    if (udi_cdc_tx_trans_ongoing[port]) {
        return; // Already on going or wait next SOF to send next data
    }
    if (udd_is_high_speed()) {
        if (udi_cdc_tx_sof_num[port] == udd_get_micro_frame_number()) {
            return; // Wait next SOF to send next data
        }
    } else {
        if (udi_cdc_tx_sof_num[port] == udd_get_frame_number()) {
            return; // Wait next SOF to send next data
        }
    }

    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    buf_sel_trans = udi_cdc_tx_buf_sel[port];
    if (udi_cdc_tx_buf_nb[port][buf_sel_trans] == 0) {
        sof_zlp_counter++;
        if (((!udd_is_high_speed()) && (sof_zlp_counter < 100)) || (udd_is_high_speed() && (sof_zlp_counter < 800))) {
            __DMB();
            __set_PRIMASK(irqflags);
            return;
        }
    }
    sof_zlp_counter = 0;

    if (!udi_cdc_tx_both_buf_to_send[port]) {
        // Send current Buffer
        // and switch the current buffer
        udi_cdc_tx_buf_sel[port] = (buf_sel_trans == 0) ? 1 : 0;
    } else {
        // Send the other Buffer
        // and no switch the current buffer
        buf_sel_trans = (buf_sel_trans == 0) ? 1 : 0;
    }
    udi_cdc_tx_trans_ongoing[port] = true;
    __DMB();
    __set_PRIMASK(irqflags);

    b_short_packet = (udi_cdc_tx_buf_nb[port][buf_sel_trans] != UDI_CDC_TX_BUFFERS);
    if (b_short_packet) {
        if (udd_is_high_speed()) {
            udi_cdc_tx_sof_num[port] = udd_get_micro_frame_number();
        } else {
            udi_cdc_tx_sof_num[port] = udd_get_frame_number();
        }
    } else {
        udi_cdc_tx_sof_num[port] = 0; // Force next transfer without wait SOF
    }

    /*
    // Send the buffer with enable of short packet
    switch (port) {
#define UDI_CDC_PORT_TO_DATA_EP_IN(index, unused) \
    case index: \
        ep = UDI_CDC_DATA_EP_IN_##index; \
        break;
    MREPEAT(UDI_CDC_PORT_NB, UDI_CDC_PORT_TO_DATA_EP_IN, ~)
#undef UDI_CDC_PORT_TO_DATA_EP_IN
    default:
        ep = UDI_CDC_DATA_EP_IN_0;
        break;
    }
    */
    ep = UDI_CDC_DATA_EP_IN_0;

    udd_ep_run(ep, b_short_packet, udi_cdc_tx_buf[port][buf_sel_trans], udi_cdc_tx_buf_nb[port][buf_sel_trans], udi_cdc_data_sent);
}

//---------------------------------------------
//------- Application interface

void udi_cdc_ctrl_signal_dcd(bool b_set) {
    udi_cdc_ctrl_state_change(0, b_set, CDC_SERIAL_STATE_DCD);
}

void udi_cdc_ctrl_signal_dsr(bool b_set) {
    udi_cdc_ctrl_state_change(0, b_set, CDC_SERIAL_STATE_DSR);
}

void udi_cdc_signal_framing_error(void) {
    udi_cdc_ctrl_state_change(0, true, CDC_SERIAL_STATE_FRAMING);
}

void udi_cdc_signal_parity_error(void) {
    udi_cdc_ctrl_state_change(0, true, CDC_SERIAL_STATE_PARITY);
}

void udi_cdc_signal_overrun(void) {
    udi_cdc_ctrl_state_change(0, true, CDC_SERIAL_STATE_OVERRUN);
}

void udi_cdc_multi_ctrl_signal_dcd(uint8_t port, bool b_set) {
    udi_cdc_ctrl_state_change(port, b_set, CDC_SERIAL_STATE_DCD);
}

void udi_cdc_multi_ctrl_signal_dsr(uint8_t port, bool b_set) {
    udi_cdc_ctrl_state_change(port, b_set, CDC_SERIAL_STATE_DSR);
}

void udi_cdc_multi_signal_framing_error(uint8_t port) {
    udi_cdc_ctrl_state_change(port, true, CDC_SERIAL_STATE_FRAMING);
}

void udi_cdc_multi_signal_parity_error(uint8_t port) {
    udi_cdc_ctrl_state_change(port, true, CDC_SERIAL_STATE_PARITY);
}

void udi_cdc_multi_signal_overrun(uint8_t port) {
    udi_cdc_ctrl_state_change(port, true, CDC_SERIAL_STATE_OVERRUN);
}

iram_size_t udi_cdc_multi_get_nb_received_data(uint8_t port) {
    uint32_t    irqflags; // irqflags_t
    uint16_t    pos;
    iram_size_t nb_received;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    pos         = udi_cdc_rx_pos[port];
    nb_received = udi_cdc_rx_buf_nb[port][udi_cdc_rx_buf_sel[port]] - pos;
    __DMB();
    __set_PRIMASK(irqflags);
    return nb_received;
}

iram_size_t udi_cdc_get_nb_received_data(void) {
    return udi_cdc_multi_get_nb_received_data(0);
}

bool udi_cdc_multi_is_rx_ready(uint8_t port) {
    return (udi_cdc_multi_get_nb_received_data(port) > 0);
}

bool udi_cdc_is_rx_ready(void) {
    return udi_cdc_multi_is_rx_ready(0);
}

int udi_cdc_multi_getc(uint8_t port) {
    uint32_t irqflags; // irqflags_t
    int      rx_data = 0;
    bool     b_databit_9;
    uint16_t pos;
    uint8_t  buf_sel;
    bool     again;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    b_databit_9 = (9 == udi_cdc_line_coding[port].bDataBits);

udi_cdc_getc_process_one_byte:
    // Check available data
    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    pos     = udi_cdc_rx_pos[port];
    buf_sel = udi_cdc_rx_buf_sel[port];
    again   = pos >= udi_cdc_rx_buf_nb[port][buf_sel];
    __DMB();
    __set_PRIMASK(irqflags);
    while (again) {
        if (!udi_cdc_data_running) {
            return 0;
        }
        goto udi_cdc_getc_process_one_byte;
    }

    // Read data
    rx_data |= udi_cdc_rx_buf[port][buf_sel][pos];
    udi_cdc_rx_pos[port] = pos + 1;

    udi_cdc_rx_start(port);

    if (b_databit_9) {
        // Receive MSB
        b_databit_9 = false;
        rx_data     = rx_data << 8;
        goto udi_cdc_getc_process_one_byte;
    }
    return rx_data;
}

int udi_cdc_getc(void) {
    return udi_cdc_multi_getc(0);
}

iram_size_t udi_cdc_multi_read_buf(uint8_t port, void *buf, iram_size_t size) {
    uint32_t    irqflags; // irqflags_t
    uint8_t *   ptr_buf = (uint8_t *)buf;
    iram_size_t copy_nb;
    uint16_t    pos;
    uint8_t     buf_sel;
    bool        again;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

udi_cdc_read_buf_loop_wait:
    // Check available data
    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    pos     = udi_cdc_rx_pos[port];
    buf_sel = udi_cdc_rx_buf_sel[port];
    again   = pos >= udi_cdc_rx_buf_nb[port][buf_sel];
    __DMB();
    __set_PRIMASK(irqflags);
    while (again) {
        if (!udi_cdc_data_running) {
            return size;
        }
        goto udi_cdc_read_buf_loop_wait;
    }

    // Read data
    copy_nb = udi_cdc_rx_buf_nb[port][buf_sel] - pos;
    if (copy_nb > size) {
        copy_nb = size;
    }
    memcpy(ptr_buf, &udi_cdc_rx_buf[port][buf_sel][pos], copy_nb);
    udi_cdc_rx_pos[port] += copy_nb;
    ptr_buf += copy_nb;
    size -= copy_nb;
    udi_cdc_rx_start(port);

    if (size) {
        goto udi_cdc_read_buf_loop_wait;
    }
    return 0;
}

static iram_size_t udi_cdc_multi_read_no_polling(uint8_t port, void *buf, iram_size_t size) {
    uint8_t *   ptr_buf = (uint8_t *)buf;
    iram_size_t nb_avail_data;
    uint16_t    pos;
    uint8_t     buf_sel;
    uint32_t    irqflags; // irqflags_t

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    // Data interface not started... exit
    if (!udi_cdc_data_running) {
        return 0;
    }

    // Get number of available data
    // Check available data
    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    pos           = udi_cdc_rx_pos[port];
    buf_sel       = udi_cdc_rx_buf_sel[port];
    nb_avail_data = udi_cdc_rx_buf_nb[port][buf_sel] - pos;
    __DMB();
    __set_PRIMASK(irqflags);
    // If the buffer contains less than the requested number of data,
    // adjust read size
    if (nb_avail_data < size) {
        size = nb_avail_data;
    }
    if (size > 0) {
        memcpy(ptr_buf, &udi_cdc_rx_buf[port][buf_sel][pos], size);
        irqflags = __get_PRIMASK();
        __disable_irq();
        __DMB();
        udi_cdc_rx_pos[port] += size;
        __DMB();
        __set_PRIMASK(irqflags);
        ptr_buf += size;
        udi_cdc_rx_start(port);
    }
    return (nb_avail_data);
}

iram_size_t udi_cdc_read_no_polling(void *buf, iram_size_t size) {
    return udi_cdc_multi_read_no_polling(0, buf, size);
}

iram_size_t udi_cdc_read_buf(void *buf, iram_size_t size) {
    return udi_cdc_multi_read_buf(0, buf, size);
}

iram_size_t udi_cdc_multi_get_free_tx_buffer(uint8_t port) {
    uint32_t    irqflags; // irqflags_t
    iram_size_t buf_sel_nb, retval;
    uint8_t     buf_sel;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    buf_sel    = udi_cdc_tx_buf_sel[port];
    buf_sel_nb = udi_cdc_tx_buf_nb[port][buf_sel];
    if (buf_sel_nb == UDI_CDC_TX_BUFFERS) {
        if ((!udi_cdc_tx_trans_ongoing[port]) && (!udi_cdc_tx_both_buf_to_send[port])) {
            /* One buffer is full, but the other buffer is not used.
             * (not used = transfer on-going)
             * then move to the other buffer to store data */
            udi_cdc_tx_both_buf_to_send[port] = true;
            udi_cdc_tx_buf_sel[port]          = (buf_sel == 0) ? 1 : 0;
            buf_sel_nb                        = 0;
        }
    }
    retval = UDI_CDC_TX_BUFFERS - buf_sel_nb;
    __DMB();
    __set_PRIMASK(irqflags);
    return retval;
}

iram_size_t udi_cdc_get_free_tx_buffer(void) {
    return udi_cdc_multi_get_free_tx_buffer(0);
}

bool udi_cdc_multi_is_tx_ready(uint8_t port) {
    return (udi_cdc_multi_get_free_tx_buffer(port) != 0);
}

bool udi_cdc_is_tx_ready(void) {
    return udi_cdc_multi_is_tx_ready(0);
}

int udi_cdc_multi_putc(uint8_t port, int value) {
    uint32_t irqflags; // irqflags_t
    bool     b_databit_9;
    uint8_t  buf_sel;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    b_databit_9 = (9 == udi_cdc_line_coding[port].bDataBits);

udi_cdc_putc_process_one_byte:
    // Check available space
    if (!udi_cdc_multi_is_tx_ready(port)) {
        if (!udi_cdc_data_running) {
            return false;
        }
        goto udi_cdc_putc_process_one_byte;
    }

    // Write value
    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    buf_sel                                                           = udi_cdc_tx_buf_sel[port];
    udi_cdc_tx_buf[port][buf_sel][udi_cdc_tx_buf_nb[port][buf_sel]++] = value;
    __DMB();
    __set_PRIMASK(irqflags);

    if (b_databit_9) {
        // Send MSB
        b_databit_9 = false;
        value       = value >> 8;
        goto udi_cdc_putc_process_one_byte;
    }
    return true;
}

int udi_cdc_putc(int value) {
    return udi_cdc_multi_putc(0, value);
}

iram_size_t udi_cdc_multi_write_buf(uint8_t port, const void *buf, iram_size_t size) {
    uint32_t    irqflags; // irqflags_t
    uint8_t     buf_sel;
    uint16_t    buf_nb;
    iram_size_t copy_nb;
    uint8_t *   ptr_buf = (uint8_t *)buf;

    //#if UDI_CDC_PORT_NB == 1 // To optimize code
    port = 0;
    //#endif

    if (9 == udi_cdc_line_coding[port].bDataBits) {
        size *= 2;
    }

udi_cdc_write_buf_loop_wait:

    // Check available space
    if (!udi_cdc_multi_is_tx_ready(port)) {
        if (!udi_cdc_data_running) {
            return size;
        }
        goto udi_cdc_write_buf_loop_wait;
    }

    // Write values
    irqflags = __get_PRIMASK();
    __disable_irq();
    __DMB();
    buf_sel = udi_cdc_tx_buf_sel[port];
    buf_nb  = udi_cdc_tx_buf_nb[port][buf_sel];
    copy_nb = UDI_CDC_TX_BUFFERS - buf_nb;
    if (copy_nb > size) {
        copy_nb = size;
    }
    memcpy(&udi_cdc_tx_buf[port][buf_sel][buf_nb], ptr_buf, copy_nb);
    udi_cdc_tx_buf_nb[port][buf_sel] = buf_nb + copy_nb;
    __DMB();
    __set_PRIMASK(irqflags);

    // Update buffer pointer
    ptr_buf = ptr_buf + copy_nb;
    size -= copy_nb;

    if (size) {
        goto udi_cdc_write_buf_loop_wait;
    }

    return 0;
}

iram_size_t udi_cdc_write_buf(const void *buf, iram_size_t size) {
    return udi_cdc_multi_write_buf(0, buf, size);
}

#    define MAX_PRINT 256
#    define CDC_SEND_INTERVAL 2
uint32_t cdc_tx_send_time_next;

void CDC_send(void) {
    while (timer_read64() < cdc_tx_send_time_next)
        ;
    udi_cdc_tx_send(0);
    cdc_tx_send_time_next = timer_read64() + CDC_SEND_INTERVAL;
}

uint32_t CDC_print(char *printbuf) {
    uint32_t count = 0;
    char *   buf   = printbuf;
    char     c;

    if (timer_read64() < 5000) return 0;

    while ((c = *buf++) != 0 && !(count >= MAX_PRINT)) {
        count++;
        if (!udi_cdc_is_tx_ready()) return 0;
        udi_cdc_putc(c);
        if (count >= UDI_CDC_TX_BUFFERS) {
            count = 0;
            CDC_send();
        }
    }
    if (count) {
        CDC_send();
    }
    return 1;
}

char printbuf[CDC_PRINTBUF_SIZE];

int CDC_printf(const char *_Format, ...) {
    va_list va; // Variable argument list variable
    int     result;

    va_start(va, _Format); // Initialize the variable argument list
    result = vsnprintf(printbuf, CDC_PRINTBUF_SIZE, _Format, va);
    va_end(va);

    CDC_print(printbuf);

    return result;
}

// global "inbuf" if desired
inbuf_t inbuf;

uint32_t CDC_input_buf(inbuf_t inbuf, uint32_t inbuf_size) {
    int RXChar;
    int entered = 0;

    if (!udi_cdc_is_rx_ready()) return 0;
    udi_cdc_get_nb_received_data();
    RXChar = udi_cdc_getc();

    if (RXChar) {
        switch (RXChar) {
            case '\t': // tab - repeat last
                inbuf.count                = inbuf.lastcount;
                inbuf.buf[inbuf.count + 1] = 0;
                CDC_print(inbuf.buf);
                break;
            case '\r': // enter
                inbuf.buf[inbuf.count] = 0;
                inbuf.lastcount        = inbuf.count;
                inbuf.count            = 0;
                entered                = 1;
                break;
            case '\b': // backspace
                if (inbuf.count > 0) {
                    inbuf.count -= 1;
                    CDC_print("\b \b\0");
                } else
                    CDC_print("\a\0");
                break;
            default:
                if ((RXChar >= 32) && (RXChar <= 126)) {
                    if (inbuf.count < inbuf_size - 1) {
                        inbuf.buf[inbuf.count]     = RXChar;
                        inbuf.buf[inbuf.count + 1] = 0;
                        CDC_print(&inbuf.buf[inbuf.count]);
                        inbuf.count += 1;
                    } else
                        CDC_print("\a\0");
                }
                break;
        }
        RXChar = 0;
    }
    return entered;
}

uint32_t CDC_input() {
    return CDC_input_buf(inbuf, CDC_INBUF_SIZE);
}

void CDC_init(void) {
    inbuf.count           = 0;
    inbuf.lastcount       = 0;
    printbuf[0]           = 0;
    cdc_tx_send_time_next = timer_read64() + CDC_SEND_INTERVAL;
}

#else // CDC line 62

char printbuf[CDC_PRINTBUF_SIZE];

void CDC_send(void) {
    return;
}

uint32_t CDC_print(char *printbuf) {
    return 0;
}

int CDC_printf(const char *_Format, ...) {
    return 0;
}

inbuf_t inbuf;

uint32_t CDC_input(void) {
    return 0;
}

void CDC_init(void) {
    inbuf.count     = 0;
    inbuf.lastcount = 0;
    printbuf[0]     = 0;
}

#endif // CDC line 62

//@}
