/**
 * Copyright (c) 2016 - 2017, Nordic Semiconductor ASA
 * 
 * All rights reserved.
 * 
 * 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, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, 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. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 * 
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 * 
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

#include "sdk_config.h"
#if USBD_ENABLED
#include "nrf_drv_usbd.h"
#include "nrf.h"
#include "nordic_common.h"
#include "nrf_drv_common.h"
#include "nrf_atomic.h"
#include "nrf_delay.h"
#include "nrf_drv_clock.h"
#include "app_util_platform.h"

#include <string.h>
#include <inttypes.h>

#define NRF_LOG_MODULE_NAME ""
#if NRF_USBD_DRV_LOG_ENABLED
#else //NRF_USBD_DRV_LOG_ENABLED
#define NRF_LOG_LEVEL       0
#endif //NRF_USBD_DRV_LOG_ENABLED
#include "nrf_log.h"

#ifndef NRF_DRV_USBD_EARLY_DMA_PROCESS
/* Try to process DMA request when endpoint transmission has been detected
 * and just after last EasyDMA has been processed.
 * It speeds up the transmission a little (about 10% measured)
 * with a cost of more CPU power used.
 */
#define NRF_DRV_USBD_EARLY_DMA_PROCESS 1
#endif

#ifndef NRF_DRV_USBD_PROTO1_FIX
/* Fix event system */
#define NRF_DRV_USBD_PROTO1_FIX 1
#endif

#ifndef NRF_DRV_USBD_PROTO1_FIX_DEBUG
/* Debug information when events are fixed*/
#define NRF_DRV_USBD_PROTO1_FIX_DEBUG 1
#endif

#if NRF_DRV_USBD_PROTO1_FIX_DEBUG
#include "nrf_log.h"
#define NRF_DRV_USBD_LOG_PROTO1_FIX_PRINTF(...) NRF_LOG_DEBUG(__VA_ARGS__)
#else
#define NRF_DRV_USBD_LOG_PROTO1_FIX_PRINTF(...) do {} while (0)
#endif

#ifndef NRF_DRV_USBD_STARTED_EV_ENABLE
#define NRF_DRV_USBD_STARTED_EV_ENABLE    1
#endif

#ifndef NRF_USBD_ISO_DEBUG
/* Also generate information about ISOCHRONOUS events and transfers.
 * Turn this off if no ISOCHRONOUS transfers are going to be debugged and this
 * option generates a lot of useless messages. */
#define NRF_USBD_ISO_DEBUG 1
#endif

#ifndef NRF_USBD_FAILED_TRANSFERS_DEBUG
/* Also generate debug information for failed transfers.
 * It might be useful but may generate a lot of useless debug messages
 * in some library usages (for example when transfer is generated and the
 * result is used to check whatever endpoint was busy. */
#define NRF_USBD_FAILED_TRANSFERS_DEBUG 1
#endif

#ifndef NRF_USBD_DMAREQ_PROCESS_DEBUG
/* Generate additional messages that mark the status inside
 * @ref usbd_dmareq_process.
 * It is useful to debug library internals but may generate a lot of
 * useless debug messages. */
#define NRF_USBD_DMAREQ_PROCESS_DEBUG 1
#endif




#if NRF_DRV_USBD_PROTO1_FIX
#include "nrf_drv_systick.h"
#endif

/**
 * @defgroup nrf_usbdraw_drv_int USB Device driver internal part
 * @internal
 * @ingroup nrf_usbdraw_drv
 *
 * This part contains auxiliary internal macros, variables and functions.
 * @{
 */

/**
 * @brief Assert endpoint number validity
 *
 * Internal macro to be used during program creation in debug mode.
 * Generates assertion if endpoint number is not valid.
 *
 * @param ep Endpoint number to validity check
 */
#define USBD_ASSERT_EP_VALID(ep) ASSERT(                                         \
    ((NRF_USBD_EPIN_CHECK(ep)  && (NRF_USBD_EP_NR_GET(ep) < NRF_USBD_EPIN_CNT )) \
    ||                                                                           \
    (NRF_USBD_EPOUT_CHECK(ep) && (NRF_USBD_EP_NR_GET(ep) < NRF_USBD_EPOUT_CNT))) \
);

/**
 * @brief Lowest position of bit for IN endpoint
 *
 * The first bit position corresponding to IN endpoint.
 * @sa ep2bit bit2ep
 */
#define USBD_EPIN_BITPOS_0   0

/**
 * @brief Lowest position of bit for OUT endpoint
 *
 * The first bit position corresponding to OUT endpoint
 * @sa ep2bit bit2ep
 */
#define USBD_EPOUT_BITPOS_0  16

/**
 * @brief Input endpoint bits mask
 */
#define USBD_EPIN_BIT_MASK (0xFFFFU << USBD_EPIN_BITPOS_0)

/**
 * @brief Output endpoint bits mask
 */
#define USBD_EPOUT_BIT_MASK (0xFFFFU << USBD_EPOUT_BITPOS_0)

/**
 * @brief Auxiliary macro to change EP number into bit position
 *
 * This macro is used by @ref ep2bit function but also for statically check
 * the bitpos values integrity during compilation.
 *
 * @param[in] ep Endpoint number.
 * @return Endpoint bit position.
 */
#define USBD_EP_BITPOS(ep) \
    ((NRF_USBD_EPIN_CHECK(ep) ? USBD_EPIN_BITPOS_0 : USBD_EPOUT_BITPOS_0) + NRF_USBD_EP_NR_GET(ep))

/**
 * @brief Helper macro for creating an endpoint transfer event.
 *
 * @param[in] name     Name of the created transfer event variable.
 * @param[in] endpoint Endpoint number.
 * @param[in] ep_stat  Endpoint state to report.
 *
 * @return Initialized event constant variable.
 */
#define NRF_DRV_USBD_EP_TRANSFER_EVENT(name, endpont, ep_stat)          \
    const nrf_drv_usbd_evt_t name = {                                   \
        NRF_DRV_USBD_EVT_EPTRANSFER,                                    \
        .data = {                                                       \
            .eptransfer = {                                             \
                    .ep = endpont,                                      \
                    .status = ep_stat                                   \
            }                                                           \
        }                                                               \
    }

/* Check it the bit positions values match defined DATAEPSTATUS bit positions */
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPIN1)  == USBD_EPDATASTATUS_EPIN1_Pos );
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPIN2)  == USBD_EPDATASTATUS_EPIN2_Pos );
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPIN3)  == USBD_EPDATASTATUS_EPIN3_Pos );
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPIN4)  == USBD_EPDATASTATUS_EPIN4_Pos );
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPIN5)  == USBD_EPDATASTATUS_EPIN5_Pos );
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPIN6)  == USBD_EPDATASTATUS_EPIN6_Pos );
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPIN7)  == USBD_EPDATASTATUS_EPIN7_Pos );
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPOUT1) == USBD_EPDATASTATUS_EPOUT1_Pos);
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPOUT2) == USBD_EPDATASTATUS_EPOUT2_Pos);
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPOUT3) == USBD_EPDATASTATUS_EPOUT3_Pos);
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPOUT4) == USBD_EPDATASTATUS_EPOUT4_Pos);
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPOUT5) == USBD_EPDATASTATUS_EPOUT5_Pos);
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPOUT6) == USBD_EPDATASTATUS_EPOUT6_Pos);
STATIC_ASSERT(USBD_EP_BITPOS(NRF_DRV_USBD_EPOUT7) == USBD_EPDATASTATUS_EPOUT7_Pos);


/**
 * @name Internal auxiliary definitions for SETUP packet
 *
 * Definitions used to take out the information about last SETUP packet direction
 * from @c bmRequestType.
 * @{
 */
/** The position of DIR bit in bmRequestType inside SETUP packet */
#define USBD_DRV_REQUESTTYPE_DIR_BITPOS 7
/** The mask of DIR bit in bmRequestType inside SETUP packet */
#define USBD_DRV_REQUESTTYPE_DIR_MASK   (1U << USBD_DRV_REQUESTTYPE_DIR_BITPOS)
/** The value of DIR bit for OUT direction (Host -> Device) */
#define USBD_DRV_REQUESTTYPE_DIR_OUT    (0U << USBD_DRV_REQUESTTYPE_DIR_BITPOS)
/** The value of DIR bit for IN direction (Device -> Host) */
#define USBD_DRV_REQUESTTYPE_DIR_IN     (1U << USBD_DRV_REQUESTTYPE_DIR_BITPOS)
/** @} */

/**
 * @brief Current driver state
 */
static nrf_drv_state_t m_drv_state = NRF_DRV_STATE_UNINITIALIZED;

/**
 * @brief Event handler for the library
 *
 * Event handler that would be called on events.
 *
 * @note Currently it cannot be null if any interrupt is activated.
 */
static nrf_drv_usbd_event_handler_t m_event_handler;

/**
 * @brief Direction of last received Setup transfer
 *
 * This variable is used to redirect internal setup data event
 * into selected endpoint (IN or OUT).
 */
static nrf_drv_usbd_ep_t m_last_setup_dir;

/**
 * @brief Mark endpoint readiness for DMA transfer
 *
 * Bits in this variable are cleared and set in interrupts.
 * 1 means that endpoint is ready for DMA transfer.
 * 0 means that DMA transfer cannot be performed on selected endpoint.
 */
static uint32_t m_ep_ready;

/**
 * @brief Mark endpoint with prepared data to transfer by DMA
 *
 * This variable can be from any place in the code (interrupt or main thread).
 * It would be cleared only from USBD interrupt.
 *
 * Mask prepared USBD data for transmission.
 * It is cleared when no more data to transmit left.
 */
static uint32_t m_ep_dma_waiting;

/**
 * @brief Current EasyDMA state
 *
 * Single flag, updated only inside interrupts, that marks current EasyDMA state.
 * In USBD there is only one DMA channel working in background, and new transfer
 * cannot be started when there is ongoing transfer on any other channel.
 */
static uint8_t m_dma_pending;

#if NRF_DRV_USBD_PROTO1_FIX
static uint32_t m_simulated_dataepstatus;
#endif

/**
 * @brief The structure that would hold transfer configuration to every endpoint
 *
 * The structure that holds all the data required by the endpoint to proceed
 * with LIST functionality and generate quick callback directly when data
 * buffer is ready.
 */
typedef struct
{
    nrf_drv_usbd_handler_t     handler;          //!< Handler for current transfer, function pointer
    void                     * p_context;        //!< Context for transfer handler
    size_t                     transfer_cnt;     //!< Number of transferred bytes in the current transfer
    uint16_t                   max_packet_size;  //!< Configured endpoint size
    nrf_drv_usbd_ep_status_t   status;           //!< NRF_SUCCESS or error code, never NRF_ERROR_BUSY - this one is calculated
}usbd_drv_ep_state_t;

/**
 * @brief The array of transfer configurations for the endpoints.
 *
 * The status of the transfer on each endpoint.
 */
static struct
{
    usbd_drv_ep_state_t ep_out[NRF_USBD_EPOUT_CNT]; //!< Status for OUT endpoints.
    usbd_drv_ep_state_t ep_in [NRF_USBD_EPIN_CNT ]; //!< Status for IN endpoints.
}m_ep_state;

/**
 * @brief Status variables for integrated feeders.
 *
 * Current status for integrated feeders (IN transfers).
 * Integrated feeders are used for default transfers:
 * 1. Simple RAM transfer
 * 2. Simple flash transfer
 * 3. RAM transfer with automatic ZLP
 * 4. Flash transfer with automatic ZLP
 */
nrf_drv_usbd_transfer_t m_ep_feeder_state[NRF_USBD_EPIN_CNT];

/**
 * @brief Status variables for integrated consumers
 *
 * Current status for integrated consumers
 * Currently one type of transfer is supported:
 * 1. Transfer to RAM
 *
 * Transfer is finished automatically when received data block is smaller
 * than the endpoint buffer or all the required data is received.
 */
nrf_drv_usbd_transfer_t m_ep_consumer_state[NRF_USBD_EPOUT_CNT];


/**
 * @brief Buffer used to send data directly from FLASH
 *
 * This is internal buffer that would be used to emulate the possibility
 * to transfer data directly from FLASH.
 * We do not have to care about the source of data when calling transfer functions.
 *
 * We do not need more buffers that one, because only one transfer can be pending
 * at once.
 */
static uint32_t m_tx_buffer[CEIL_DIV(
    NRF_DRV_USBD_FEEDER_BUFFER_SIZE, sizeof(uint32_t))];


/* Early declaration. Documentation above definition. */
static void usbd_dmareq_process(void);


#if NRF_DRV_USBD_PROTO1_FIX
static inline nrf_usbd_event_t nrf_drv_usbd_ep_to_endevent(nrf_drv_usbd_ep_t ep)
{
    USBD_ASSERT_EP_VALID(ep);

    static const nrf_usbd_event_t epin_endev[] =
    {
        NRF_USBD_EVENT_ENDEPIN0,
        NRF_USBD_EVENT_ENDEPIN1,
        NRF_USBD_EVENT_ENDEPIN2,
        NRF_USBD_EVENT_ENDEPIN3,
        NRF_USBD_EVENT_ENDEPIN4,
        NRF_USBD_EVENT_ENDEPIN5,
        NRF_USBD_EVENT_ENDEPIN6,
        NRF_USBD_EVENT_ENDEPIN7,
        NRF_USBD_EVENT_ENDISOIN0
    };
    static const nrf_usbd_event_t epout_endev[] =
    {
        NRF_USBD_EVENT_ENDEPOUT0,
        NRF_USBD_EVENT_ENDEPOUT1,
        NRF_USBD_EVENT_ENDEPOUT2,
        NRF_USBD_EVENT_ENDEPOUT3,
        NRF_USBD_EVENT_ENDEPOUT4,
        NRF_USBD_EVENT_ENDEPOUT5,
        NRF_USBD_EVENT_ENDEPOUT6,
        NRF_USBD_EVENT_ENDEPOUT7,
        NRF_USBD_EVENT_ENDISOOUT0
    };

    return (NRF_USBD_EPIN_CHECK(ep) ? epin_endev : epout_endev)[NRF_USBD_EP_NR_GET(ep)];
}
#endif


/**
 * @brief Get interrupt mask for selected endpoint
 *
 * @param[in] ep Endpoint number
 *
 * @return Interrupt mask related to the EasyDMA transfer end for the
 *         chosen endpoint.
 */
static inline uint32_t nrf_drv_usbd_ep_to_int(nrf_drv_usbd_ep_t ep)
{
    USBD_ASSERT_EP_VALID(ep);

    static const uint8_t epin_bitpos[] =
    {
        USBD_INTEN_ENDEPIN0_Pos,
        USBD_INTEN_ENDEPIN1_Pos,
        USBD_INTEN_ENDEPIN2_Pos,
        USBD_INTEN_ENDEPIN3_Pos,
        USBD_INTEN_ENDEPIN4_Pos,
        USBD_INTEN_ENDEPIN5_Pos,
        USBD_INTEN_ENDEPIN6_Pos,
        USBD_INTEN_ENDEPIN7_Pos,
        USBD_INTEN_ENDISOIN_Pos
    };
    static const uint8_t epout_bitpos[] =
    {
        USBD_INTEN_ENDEPOUT0_Pos,
        USBD_INTEN_ENDEPOUT1_Pos,
        USBD_INTEN_ENDEPOUT2_Pos,
        USBD_INTEN_ENDEPOUT3_Pos,
        USBD_INTEN_ENDEPOUT4_Pos,
        USBD_INTEN_ENDEPOUT5_Pos,
        USBD_INTEN_ENDEPOUT6_Pos,
        USBD_INTEN_ENDEPOUT7_Pos,
        USBD_INTEN_ENDISOOUT_Pos
    };

    return 1UL << (NRF_USBD_EPIN_CHECK(ep) ? epin_bitpos : epout_bitpos)[NRF_USBD_EP_NR_GET(ep)];
}

/**
 * @name Integrated feeders and consumers
 *
 * Internal, default functions for transfer processing.
 * @{
 */

/**
 * @brief Integrated consumer to RAM buffer.
 *
 * @param p_next    See @ref nrf_drv_usbd_consumer_t documentation.
 * @param p_context See @ref nrf_drv_usbd_consumer_t documentation.
 * @param ep_size   See @ref nrf_drv_usbd_consumer_t documentation.
 * @param data_size See @ref nrf_drv_usbd_consumer_t documentation.
 *
 * @retval true  Continue transfer.
 * @retval false This was the last transfer.
 */
bool nrf_drv_usbd_consumer(
    nrf_drv_usbd_ep_transfer_t * p_next,
    void * p_context,
    size_t ep_size,
    size_t data_size)
{
    nrf_drv_usbd_transfer_t * p_transfer = p_context;
    ASSERT(ep_size >= data_size);
    ASSERT((p_transfer->p_data.rx == NULL) ||
        nrf_drv_is_in_RAM((const void*)(p_transfer->p_data.ptr)));

    size_t size = p_transfer->size;
    if (size < data_size)
    {
        /* Buffer size to small */
        p_next->size = 0;
        p_next->p_data = p_transfer->p_data;
    }
    else
    {
        p_next->size = data_size;
        p_next->p_data = p_transfer->p_data;
        size -= data_size;
        p_transfer->size = size;
        p_transfer->p_data.ptr += data_size;
    }
    return (ep_size == data_size) && (size != 0);
}

/**
 * @brief Integrated feeder from RAM source.
 *
 * @param[out]    p_next    See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in,out] p_context See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in]     ep_size   See @ref nrf_drv_usbd_feeder_t documentation.
 *
 * @retval true  Continue transfer.
 * @retval false This was the last transfer.
 */
bool nrf_drv_usbd_feeder_ram(
    nrf_drv_usbd_ep_transfer_t * p_next,
    void * p_context,
    size_t ep_size)
{
    nrf_drv_usbd_transfer_t * p_transfer = p_context;
    ASSERT(nrf_drv_is_in_RAM((const void*)(p_transfer->p_data.ptr)));

    size_t tx_size = p_transfer->size;
    if (tx_size > ep_size)
    {
        tx_size = ep_size;
    }

    p_next->p_data = p_transfer->p_data;
    p_next->size = tx_size;

    p_transfer->size -= tx_size;
    p_transfer->p_data.ptr += tx_size;

    return (p_transfer->size != 0);
}

/**
 * @brief Integrated feeder from RAM source with ZLP.
 *
 * @param[out]    p_next    See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in,out] p_context See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in]     ep_size   See @ref nrf_drv_usbd_feeder_t documentation.
 *
 * @retval true  Continue transfer.
 * @retval false This was the last transfer.
 */
bool nrf_drv_usbd_feeder_ram_zlp(
    nrf_drv_usbd_ep_transfer_t * p_next,
    void * p_context,
    size_t ep_size)
{
    nrf_drv_usbd_transfer_t * p_transfer = p_context;
    ASSERT(nrf_drv_is_in_RAM((const void*)(p_transfer->p_data.ptr)));

    size_t tx_size = p_transfer->size;
    if (tx_size > ep_size)
    {
        tx_size = ep_size;
    }

    p_next->p_data.tx = (tx_size == 0) ? NULL : p_transfer->p_data.tx;
    p_next->size = tx_size;

    p_transfer->size -= tx_size;
    p_transfer->p_data.ptr += tx_size;

    return (tx_size != 0);
}

/**
 * @brief Integrated feeder from a flash source.
 *
 * @param[out]    p_next    See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in,out] p_context See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in]     ep_size   See @ref nrf_drv_usbd_feeder_t documentation.
 *
 * @retval true  Continue transfer.
 * @retval false This was the last transfer.
 */
bool nrf_drv_usbd_feeder_flash(
    nrf_drv_usbd_ep_transfer_t * p_next,
    void * p_context,
    size_t ep_size)
{
    nrf_drv_usbd_transfer_t * p_transfer = p_context;
    ASSERT(!nrf_drv_is_in_RAM((const void*)(p_transfer->p_data.ptr)));

    size_t tx_size  = p_transfer->size;
    void * p_buffer = nrf_drv_usbd_feeder_buffer_get();

    if (tx_size > ep_size)
    {
        tx_size = ep_size;
    }

    ASSERT(tx_size <= NRF_DRV_USBD_FEEDER_BUFFER_SIZE);
    memcpy(p_buffer, (p_transfer->p_data.tx), tx_size);

    p_next->p_data.tx = p_buffer;
    p_next->size = tx_size;

    p_transfer->size -= tx_size;
    p_transfer->p_data.ptr += tx_size;

    return (p_transfer->size != 0);
}

/**
 * @brief Integrated feeder from a flash source with ZLP.
 *
 * @param[out]    p_next    See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in,out] p_context See @ref nrf_drv_usbd_feeder_t documentation.
 * @param[in]     ep_size   See @ref nrf_drv_usbd_feeder_t documentation.
 *
 * @retval true  Continue transfer.
 * @retval false This was the last transfer.
 */
bool nrf_drv_usbd_feeder_flash_zlp(
    nrf_drv_usbd_ep_transfer_t * p_next,
    void * p_context,
    size_t ep_size)
{
    nrf_drv_usbd_transfer_t * p_transfer = p_context;
    ASSERT(!nrf_drv_is_in_RAM((const void*)(p_transfer->p_data.ptr)));

    size_t tx_size  = p_transfer->size;
    void * p_buffer = nrf_drv_usbd_feeder_buffer_get();

    if (tx_size > ep_size)
    {
        tx_size = ep_size;
    }

    ASSERT(tx_size <= NRF_DRV_USBD_FEEDER_BUFFER_SIZE);

    if (tx_size != 0)
    {
        memcpy(p_buffer, (p_transfer->p_data.tx), tx_size);
        p_next->p_data.tx = p_buffer;
    }
    else
    {
        p_next->p_data.tx = NULL;
    }
    p_next->size = tx_size;

    p_transfer->size -= tx_size;
    p_transfer->p_data.ptr += tx_size;

    return (tx_size != 0);
}

/** @} */

/**
 * @brief Change Driver endpoint number to HAL endpoint number
 *
 * @param ep Driver endpoint identifier
 *
 * @return Endpoint identifier in HAL
 *
 * @sa nrf_drv_usbd_ep_from_hal
 */
static inline uint8_t ep_to_hal(nrf_drv_usbd_ep_t ep)
{
    USBD_ASSERT_EP_VALID(ep);
    return (uint8_t)ep;
}

/**
 * @brief Generate start task number for selected endpoint index
 *
 * @param ep Endpoint number
 *
 * @return Task for starting EasyDMA transfer on selected endpoint.
 */
static inline nrf_usbd_task_t task_start_ep(nrf_drv_usbd_ep_t ep)
{
    USBD_ASSERT_EP_VALID(ep);
    return (nrf_usbd_task_t)(
        (NRF_USBD_EPIN_CHECK(ep) ? NRF_USBD_TASK_STARTEPIN0 : NRF_USBD_TASK_STARTEPOUT0) +
        (NRF_USBD_EP_NR_GET(ep) * sizeof(uint32_t)));
}

/**
 * @brief Access selected endpoint state structure
 *
 * Function used to change or just read the state of selected endpoint.
 * It is used for internal transmission state.
 *
 * @param ep Endpoint number
 */
static inline usbd_drv_ep_state_t* ep_state_access(nrf_drv_usbd_ep_t ep)
{
    USBD_ASSERT_EP_VALID(ep);
    return ((NRF_USBD_EPIN_CHECK(ep) ? m_ep_state.ep_in : m_ep_state.ep_out) +
        NRF_USBD_EP_NR_GET(ep));
}

/**
 * @brief Change endpoint number to bit position
 *
 * Bit positions are defined the same way as they are placed in DATAEPSTATUS register,
 * but bits for endpoint 0 are included.
 *
 * @param ep Endpoint number
 *
 * @return Bit position related to the given endpoint number
 *
 * @sa bit2ep
 */
static inline uint8_t ep2bit(nrf_drv_usbd_ep_t ep)
{
    USBD_ASSERT_EP_VALID(ep);
    return USBD_EP_BITPOS(ep);
}

/**
 * @brief Change bit position to endpoint number
 *
 * @param bitpos Bit position
 *
 * @return Endpoint number corresponding to given bit position.
 *
 * @sa ep2bit
 */
static inline nrf_drv_usbd_ep_t bit2ep(uint8_t bitpos)
{
    STATIC_ASSERT(USBD_EPOUT_BITPOS_0 > USBD_EPIN_BITPOS_0);
    return (nrf_drv_usbd_ep_t)((bitpos >= USBD_EPOUT_BITPOS_0) ?
        NRF_USBD_EPOUT(bitpos - USBD_EPOUT_BITPOS_0) : NRF_USBD_EPIN(bitpos));
}

/**
 * @brief Start selected EasyDMA transmission
 *
 * This is internal auxiliary function.
 * No checking is made if EasyDMA is ready for new transmission.
 *
 * @param[in] ep Number of endpoint for transmission.
 *               If it is OUT endpoint transmission would be directed from endpoint to RAM.
 *               If it is in endpoint transmission would be directed from RAM to endpoint.
 */
static inline void usbd_dma_start(nrf_drv_usbd_ep_t ep)
{
    nrf_usbd_task_trigger(task_start_ep(ep));
}

/**
 * @brief Abort pending transfer on selected endpoint
 *
 * @param ep Endpoint number.
 *
 * @note
 * This function locks interrupts that may be costly.
 * It is good idea to test if the endpoint is still busy before calling this function:
 * @code
   (m_ep_dma_waiting & (1U << ep2bit(ep)))
 * @endcode
 * This function would check it again, but it makes it inside critical section.
 */
static inline void usbd_ep_abort(nrf_drv_usbd_ep_t ep)
{
    CRITICAL_REGION_ENTER();

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);

    if (NRF_USBD_EPOUT_CHECK(ep))
    {
        /* Host -> Device */
        if ((~m_ep_dma_waiting) & (1U<<ep2bit(ep)))
        {
            /* If the bit in m_ep_dma_waiting in cleared - nothing would be
             * processed inside transfer processing */
            nrf_drv_usbd_transfer_out_drop(ep);
        }
        else
        {
            p_state->handler.consumer = NULL;
            m_ep_dma_waiting &= ~(1U<<ep2bit(ep));
            m_ep_ready &= ~(1U<<ep2bit(ep));
        }
        /* Aborted */
        p_state->status = NRF_USBD_EP_ABORTED;
    }
    else
    {
        if ((m_ep_dma_waiting | (~m_ep_ready)) & (1U<<ep2bit(ep)))
        {
            /* Device -> Host */
            m_ep_dma_waiting &= ~(1U<<ep2bit(ep));
            m_ep_ready       |=   1U<<ep2bit(ep) ;

            p_state->handler.feeder = NULL;
            p_state->status = NRF_USBD_EP_ABORTED;
            NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_ABORTED);
            m_event_handler(&evt);
        }
    }
    CRITICAL_REGION_EXIT();
}

void usbd_drv_ep_abort(nrf_drv_usbd_ep_t ep)
{
    usbd_ep_abort(ep);
}


/**
 * @brief Abort all pending endpoints
 *
 * Function aborts all pending endpoint transfers.
 */
static void usbd_ep_abort_all(void)
{
    uint32_t ep_waiting = m_ep_dma_waiting | (m_ep_ready & USBD_EPOUT_BIT_MASK);
    while (0 != ep_waiting)
    {
        uint8_t bitpos = __CLZ(__RBIT(ep_waiting));
        usbd_ep_abort(bit2ep(bitpos));
        ep_waiting &= ~(1U << bitpos);
    }

    m_ep_ready = (((1U<<NRF_USBD_EPIN_CNT) - 1U) << USBD_EPIN_BITPOS_0);
}

/**
 * @brief Force the USBD interrupt into pending state
 *
 * This function is used to force USBD interrupt to be processed right now.
 * It makes it possible to process all EasyDMA access on one thread priority level.
 */
static inline void usbd_int_rise(void)
{
    NVIC_SetPendingIRQ(USBD_IRQn);
}

/**
 * @name USBD interrupt runtimes
 *
 * Interrupt runtimes that would be vectorized using @ref m_ivec_isr
 * @{
 */

static void USBD_ISR_Usbreset(void)
{
    m_last_setup_dir = NRF_DRV_USBD_EPOUT0;
    usbd_ep_abort_all();

    const nrf_drv_usbd_evt_t evt = {
            .type = NRF_DRV_USBD_EVT_RESET
    };

    m_event_handler(&evt);
}

static void USBD_ISR_Started(void)
{
#if NRF_DRV_USBD_STARTED_EV_ENABLE
    uint32_t epstatus = nrf_usbd_epstatus_get_and_clear();

    /* All finished endpoint have to be marked as busy */
    // #warning Check this one
    // ASSERT(epstatus == ((~m_ep_ready) & epstatus));
    while (epstatus)
    {
        uint8_t           bitpos = __CLZ(__RBIT(epstatus));
        nrf_drv_usbd_ep_t ep     = bit2ep(bitpos);
        epstatus                &= ~(1UL << bitpos);

        UNUSED_VARIABLE(ep);
    }
#endif
}

/**
 * @brief Handler for EasyDMA event without endpoint clearing.
 *
 * This handler would be called when EasyDMA transfer for endpoints that does not require clearing.
 * All in endpoints are cleared automatically when new EasyDMA transfer is initialized.
 * For endpoint 0 see @ref nrf_usbd_ep0out_dma_handler
 *
 * @param[in] ep Endpoint number
 */
static inline void nrf_usbd_ep0in_dma_handler(void)
{
    const nrf_drv_usbd_ep_t ep = NRF_DRV_USBD_EPIN0;
    NRF_LOG_DEBUG("USB event: DMA ready IN0\r\n");
    m_dma_pending = 0;

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);
    if (NRF_USBD_EP_ABORTED == p_state->status)
    {
        /* Nothing to do - just ignore */
    }
    else if (p_state->handler.feeder == NULL)
    {
        UNUSED_RETURN_VALUE(nrf_atomic_u32_and(&m_ep_dma_waiting, ~(1U<<ep2bit(ep))));
    }
}

/**
 * @brief Handler for EasyDMA event without endpoint clearing.
 *
 * This handler would be called when EasyDMA transfer for endpoints that does not require clearing.
 * All in endpoints are cleared automatically when new EasyDMA transfer is initialized.
 * For endpoint 0 see @ref nrf_usbd_ep0out_dma_handler
 *
 * @param[in] ep Endpoint number
 */
static inline void nrf_usbd_epin_dma_handler(nrf_drv_usbd_ep_t ep)
{

    NRF_LOG_DEBUG("USB event: DMA ready IN: %x\r\n", ep);
    ASSERT(NRF_USBD_EPIN_CHECK(ep));
    ASSERT(!NRF_USBD_EPISO_CHECK(ep));
    ASSERT(NRF_USBD_EP_NR_GET(ep) > 0);
    m_dma_pending = 0;

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);
    if (NRF_USBD_EP_ABORTED == p_state->status)
    {
        /* Nothing to do - just ignore */
    }
    else if (p_state->handler.feeder == NULL)
    {
        UNUSED_RETURN_VALUE(nrf_atomic_u32_and(&m_ep_dma_waiting, ~(1U<<ep2bit(ep))));
    }
}

/**
 * @brief Handler for EasyDMA event from in isochronous endpoint
 *
 * @todo RK documentation
 */
static inline void nrf_usbd_epiniso_dma_handler(nrf_drv_usbd_ep_t ep)
{
    if (NRF_USBD_ISO_DEBUG)
    {
        NRF_LOG_DEBUG("USB event: DMA ready ISOIN: %x\r\n", ep);
    }
    ASSERT(NRF_USBD_EPIN_CHECK(ep));
    ASSERT(NRF_USBD_EPISO_CHECK(ep));
    m_dma_pending = 0;

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);
    if (NRF_USBD_EP_ABORTED == p_state->status)
    {
        /* Nothing to do - just ignore */
    }
    else if (p_state->handler.feeder == NULL)
    {
        UNUSED_RETURN_VALUE(nrf_atomic_u32_and(&m_ep_dma_waiting, ~(1U<<ep2bit(ep))));
        /* Send event to the user - for an ISO IN endpoint, the whole transfer is finished in this moment */
        NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_OK);
        m_event_handler(&evt);
    }
}

/**
 * @brief Handler for EasyDMA event for OUT endpoint 0.
 *
 * EP0 OUT have to be cleared automatically in special way - only in the middle of the transfer.
 * It cannot be cleared when required transfer is finished because it means the same that accepting the comment.
 */
static inline void nrf_usbd_ep0out_dma_handler(void)
{
    const nrf_drv_usbd_ep_t ep = NRF_DRV_USBD_EPOUT0;
    NRF_LOG_DEBUG("USB event: DMA ready OUT0\r\n");
    m_dma_pending = 0;

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);
    if (NRF_USBD_EP_ABORTED == p_state->status)
    {
        /* Nothing to do - just ignore */
    }
    else if (p_state->handler.consumer == NULL)
    {
        UNUSED_RETURN_VALUE(nrf_atomic_u32_and(&m_ep_dma_waiting, ~(1U<<ep2bit(ep))));
        /* Send event to the user - for an OUT endpoint, the whole transfer is finished in this moment */
        NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_OK);
        m_event_handler(&evt);
        return;
    }

    nrf_drv_usbd_setup_data_clear();
}

/**
 * @brief Handler for EasyDMA event from endpoinpoint that requires clearing.
 *
 * This handler would be called when EasyDMA transfer for OUT endpoint has been finished.
 *
 * @param[in] ep Endpoint number
 *
 */
static inline void nrf_usbd_epout_dma_handler(nrf_drv_usbd_ep_t ep)
{
    NRF_LOG_DEBUG("USB drv: DMA ready OUT: %x\r\n", ep);
    ASSERT(NRF_USBD_EPOUT_CHECK(ep));
    ASSERT(!NRF_USBD_EPISO_CHECK(ep));
    ASSERT(NRF_USBD_EP_NR_GET(ep) > 0);
    m_dma_pending = 0;

    nrf_usbd_epout_clear(ep);

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);
    if (NRF_USBD_EP_ABORTED == p_state->status)
    {
        /* Nothing to do - just ignore */
    }
    else if (p_state->handler.consumer == NULL)
    {
        UNUSED_RETURN_VALUE(nrf_atomic_u32_and(&m_ep_dma_waiting, ~(1U<<ep2bit(ep))));
        /* Send event to the user - for an OUT endpoint, the whole transfer is finished in this moment */
        NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_OK);
        m_event_handler(&evt);
    }

#if NRF_DRV_USBD_EARLY_DMA_PROCESS
    /* Speed up */
    usbd_dmareq_process();
#endif
}

/**
 * @brief Handler for EasyDMA event from out isochronous endpoint
 *
 * @todo RK documentation
 */

static inline void nrf_usbd_epoutiso_dma_handler(nrf_drv_usbd_ep_t ep)
{
    if (NRF_USBD_ISO_DEBUG)
    {
        NRF_LOG_DEBUG("USB drv: DMA ready ISOOUT: %x\r\n", ep);
    }
    ASSERT(NRF_USBD_EPISO_CHECK(ep));

    m_dma_pending = 0;

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);
    if (NRF_USBD_EP_ABORTED == p_state->status)
    {
        /* Nothing to do - just ignore */
    }
    else if (p_state->handler.consumer == NULL)
    {
        UNUSED_RETURN_VALUE(nrf_atomic_u32_and(&m_ep_dma_waiting, ~(1U<<ep2bit(ep))));
        /* Send event to the user - for an OUT endpoint, the whole transfer is finished in this moment */
        NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_OK);
        m_event_handler(&evt);
    }
}


static void USBD_ISR_dma_epin0(void)  { nrf_usbd_ep0in_dma_handler(); }
static void USBD_ISR_dma_epin1(void)  { nrf_usbd_epin_dma_handler(NRF_DRV_USBD_EPIN1 ); }
static void USBD_ISR_dma_epin2(void)  { nrf_usbd_epin_dma_handler(NRF_DRV_USBD_EPIN2 ); }
static void USBD_ISR_dma_epin3(void)  { nrf_usbd_epin_dma_handler(NRF_DRV_USBD_EPIN3 ); }
static void USBD_ISR_dma_epin4(void)  { nrf_usbd_epin_dma_handler(NRF_DRV_USBD_EPIN4 ); }
static void USBD_ISR_dma_epin5(void)  { nrf_usbd_epin_dma_handler(NRF_DRV_USBD_EPIN5 ); }
static void USBD_ISR_dma_epin6(void)  { nrf_usbd_epin_dma_handler(NRF_DRV_USBD_EPIN6 ); }
static void USBD_ISR_dma_epin7(void)  { nrf_usbd_epin_dma_handler(NRF_DRV_USBD_EPIN7 ); }
static void USBD_ISR_dma_epin8(void)  { nrf_usbd_epiniso_dma_handler(NRF_DRV_USBD_EPIN8 ); }

static void USBD_ISR_dma_epout0(void) { nrf_usbd_ep0out_dma_handler(); }
static void USBD_ISR_dma_epout1(void) { nrf_usbd_epout_dma_handler(NRF_DRV_USBD_EPOUT1); }
static void USBD_ISR_dma_epout2(void) { nrf_usbd_epout_dma_handler(NRF_DRV_USBD_EPOUT2); }
static void USBD_ISR_dma_epout3(void) { nrf_usbd_epout_dma_handler(NRF_DRV_USBD_EPOUT3); }
static void USBD_ISR_dma_epout4(void) { nrf_usbd_epout_dma_handler(NRF_DRV_USBD_EPOUT4); }
static void USBD_ISR_dma_epout5(void) { nrf_usbd_epout_dma_handler(NRF_DRV_USBD_EPOUT5); }
static void USBD_ISR_dma_epout6(void) { nrf_usbd_epout_dma_handler(NRF_DRV_USBD_EPOUT6); }
static void USBD_ISR_dma_epout7(void) { nrf_usbd_epout_dma_handler(NRF_DRV_USBD_EPOUT7); }
static void USBD_ISR_dma_epout8(void) { nrf_usbd_epoutiso_dma_handler(NRF_DRV_USBD_EPOUT8); }

static void USBD_ISR_Sof(void)
{
    nrf_drv_usbd_evt_t evt =  {
            NRF_DRV_USBD_EVT_SOF,
            .data = { .sof = { .framecnt = nrf_usbd_framecntr_get() }}
    };

    /* Process isochronous endpoints */
    m_ep_ready |=
        (1U << ep2bit(NRF_DRV_USBD_EPIN8 )) |
        (1U << ep2bit(NRF_DRV_USBD_EPOUT8));

    m_event_handler(&evt);
}

/**
 * @brief React on data transfer finished
 *
 * Auxiliary internal function.
 * @param ep     Endpoint number
 * @param bitpos Bit position for selected endpoint number
 */
static void usbd_ep_data_handler(nrf_drv_usbd_ep_t ep, uint8_t bitpos)
{
    NRF_LOG_DEBUG("USBD event: EndpointData: %x\r\n", ep);
    /* Mark endpoint ready for next DMA access */
    m_ep_ready |= (1U<<bitpos);

    if (NRF_USBD_EPIN_CHECK(ep))
    {
        /* IN endpoint (Device -> Host) */
        if (0 == (m_ep_dma_waiting & (1U<<bitpos)))
        {
            NRF_LOG_DEBUG("USBD event: EndpointData: In finished\r\n");
            /* No more data to be send - transmission finished */
            NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_OK);
            m_event_handler(&evt);
        }
    }
    else
    {
        /* OUT endpoint (Host -> Device) */
        if (0 == (m_ep_dma_waiting & (1U<<bitpos)))
        {
            NRF_LOG_DEBUG("USBD event: EndpointData: Out waiting\r\n");
            /* No buffer prepared - send event to the application */
            NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_WAITING);
            m_event_handler(&evt);
        }
    }
}

static void USBD_ISR_SetupData(void)
{
    usbd_ep_data_handler(m_last_setup_dir, ep2bit(m_last_setup_dir));
}

static void USBD_ISR_Setup(void)
{
    nrf_usbd_shorts_disable(NRF_USBD_SHORT_EP0DATADONE_EP0STATUS_MASK);
    NRF_LOG_DEBUG("USBD event: Setup (rt:%.2x r:%.2x v:%.4x i:%.4x l:%u )\r\n",
        nrf_usbd_setup_bmrequesttype_get(),
        nrf_usbd_setup_brequest_get(),
        nrf_usbd_setup_wvalue_get(),
        nrf_usbd_setup_windex_get(),
        nrf_usbd_setup_wlength_get());
    uint8_t bmRequestType = nrf_usbd_setup_bmrequesttype_get();


    if ((m_ep_dma_waiting | ((~m_ep_ready) & USBD_EPIN_BIT_MASK)) & (1U <<ep2bit(m_last_setup_dir)))
    {
        NRF_LOG_DEBUG("USBD drv: Trying to abort last transfer on EP0\r\n");
        usbd_ep_abort(m_last_setup_dir);
    }

    m_last_setup_dir =
        ((bmRequestType & USBD_DRV_REQUESTTYPE_DIR_MASK) == USBD_DRV_REQUESTTYPE_DIR_OUT) ?
        NRF_DRV_USBD_EPOUT0 : NRF_DRV_USBD_EPIN0;

    UNUSED_RETURN_VALUE(nrf_atomic_u32_and(
        &m_ep_dma_waiting,
        ~((1U<<ep2bit(NRF_DRV_USBD_EPOUT0)) | (1U<<ep2bit(NRF_DRV_USBD_EPIN0)))));
    m_ep_ready |= 1U<<ep2bit(NRF_DRV_USBD_EPIN0);


    const nrf_drv_usbd_evt_t evt = {
            .type = NRF_DRV_USBD_EVT_SETUP
    };
    m_event_handler(&evt);
}

static void USBD_ISR_Event(void)
{
    uint32_t event = nrf_usbd_eventcause_get_and_clear();

    if (event & NRF_USBD_EVENTCAUSE_ISOOUTCRC_MASK)
    {
        /* Currently no support */
    }
    if (event & NRF_USBD_EVENTCAUSE_SUSPEND_MASK)
    {
        const nrf_drv_usbd_evt_t evt = {
                .type = NRF_DRV_USBD_EVT_SUSPEND
        };
        m_event_handler(&evt);
    }
    if (event & NRF_USBD_EVENTCAUSE_RESUME_MASK)
    {
        const nrf_drv_usbd_evt_t evt = {
                .type = NRF_DRV_USBD_EVT_RESUME
        };
        m_event_handler(&evt);
    }
}

static void USBD_ISR_EpDataStatus(void)
{
    /* Get all endpoints that have acknowledged transfer */
    uint32_t dataepstatus = nrf_usbd_epdatastatus_get_and_clear();
#if NRF_DRV_USBD_PROTO1_FIX
    dataepstatus |= (m_simulated_dataepstatus &
        ~((1U<<USBD_EPOUT_BITPOS_0) | (1U<<USBD_EPIN_BITPOS_0)));
    m_simulated_dataepstatus &=
         ((1U<<USBD_EPOUT_BITPOS_0) | (1U<<USBD_EPIN_BITPOS_0));
#endif
    NRF_LOG_DEBUG("USBD event: EndpointEPStatus: %x\r\n", dataepstatus);

    /* All finished endpoint have to be marked as busy */
    while (dataepstatus)
    {
        uint8_t           bitpos = __CLZ(__RBIT(dataepstatus));
        nrf_drv_usbd_ep_t ep     = bit2ep(bitpos);
        dataepstatus &= ~(1UL << bitpos);

        UNUSED_RETURN_VALUE(usbd_ep_data_handler(ep, bitpos));
    }
#if NRF_DRV_USBD_EARLY_DMA_PROCESS
    /* Speed up */
    usbd_dmareq_process();
#endif
}

static void USBD_ISR_AccessFault(void)
{
    /** @todo RK Currently do nothing about it.
     *  Implement it when accessfault would be better documented */
    // ASSERT(0);
}

/**
 * @brief Function to select the endpoint to start
 *
 * Function that realizes algorithm to schedule right channel for EasyDMA transfer.
 * It gets a variable with flags for the endpoints currently requiring transfer.
 *
 * @param[in] req Bit flags for channels currently requiring transfer.
 *                Bits 0...8 used for IN endpoints.
 *                Bits 16...24 used for OUT endpoints.
 * @note
 * This function would be never called with 0 as a @c req argument.
 * @return The bit number of the endpoint that should be processed now.
 */
static uint8_t usbd_dma_scheduler_algorithm(uint32_t req)
{
    /** @todo RK This is just simple algorithm for testing and should be updated */
    return __CLZ(__RBIT(req));
}

/**
 * @brief Get the size of isochronous endpoint
 *
 * The size of isochronous endpoint is configurable.
 * This function returns the size of isochronous buffer taking into account
 * current configuration.
 *
 * @param[in] ep Endpoint number.
 *
 * @return The size of endpoint buffer.
 */
static inline size_t usbd_ep_iso_capacity(nrf_drv_usbd_ep_t ep)
{
    UNUSED_PARAMETER(ep);
    nrf_usbd_isosplit_t split = nrf_usbd_isosplit_get();
    if (NRF_USBD_ISOSPLIT_Half == split)
    {
        return NRF_DRV_USBD_ISOSIZE / 2;
    }
    return NRF_DRV_USBD_ISOSIZE;
}

/**
 * @brief Process all DMA requests
 *
 * Function that have to be called from USBD interrupt handler.
 * It have to be called when all the interrupts connected with endpoints transfer
 * and DMA transfer are already handled.
 */
static void usbd_dmareq_process(void)
{
    if (0 == m_dma_pending)
    {
        uint32_t req;
        while (0 != (req = m_ep_dma_waiting & m_ep_ready))
        {
            uint8_t pos = usbd_dma_scheduler_algorithm(req);
            nrf_drv_usbd_ep_t ep = bit2ep(pos);
            usbd_drv_ep_state_t * p_state = ep_state_access(ep);

            nrf_drv_usbd_ep_transfer_t transfer;
            bool continue_transfer;

            STATIC_ASSERT(offsetof(usbd_drv_ep_state_t, handler.feeder) ==
                offsetof(usbd_drv_ep_state_t, handler.consumer));
            ASSERT((p_state->handler.feeder) != NULL);

            if (NRF_USBD_EPIN_CHECK(ep))
            {
                /* Device -> Host */
                continue_transfer = p_state->handler.feeder(
                    &transfer,
                    p_state->p_context,
                    p_state->max_packet_size);

                if (!continue_transfer)
                {
                    p_state->handler.feeder = NULL;
                    if (ep == NRF_DRV_USBD_EPIN0)
                    {
                        /** Configure short right now - now if the last data is transferred,
                         *   when host tries another data transfer, the endpoint will stall. */
                        NRF_LOG_DEBUG("USB DMA process: Enable status short\r\n");
                        nrf_usbd_shorts_enable(NRF_USBD_SHORT_EP0DATADONE_EP0STATUS_MASK);
                    }
                }
            }
            else
            {
                /* Host -> Device */
                const size_t rx_size = nrf_drv_usbd_epout_size_get(ep);
                continue_transfer = p_state->handler.consumer(
                    &transfer,
                    p_state->p_context,
                    p_state->max_packet_size,
                    rx_size);

                if (transfer.p_data.rx == NULL)
                {
                    /* Dropping transfer - allow processing */
                    ASSERT(transfer.size == 0);
                }
                else if (transfer.size < rx_size)
                {
                    p_state->status = NRF_USBD_EP_OVERLOAD;
                    UNUSED_RETURN_VALUE(nrf_atomic_u32_and(&m_ep_dma_waiting, ~(1U<<pos)));
                    NRF_DRV_USBD_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_EP_OVERLOAD);
                    m_event_handler(&evt);
                    /* This endpoint will not be transmitted now, repeat the loop */
                    continue;
                }
                else
                {
                    /* Nothing to do */
                }
                if (!continue_transfer)
                {
                    p_state->handler.consumer = NULL;
                }
                ASSERT(transfer.size == rx_size);
            }

            m_dma_pending = 1;
            m_ep_ready &= ~(1U << pos);
            if (NRF_USBD_ISO_DEBUG || (!NRF_USBD_EPISO_CHECK(ep)))
            {
                NRF_LOG_DEBUG(
                    "USB DMA process: Starting transfer on EP: %x, size: %u\r\n",
                    ep,
                    transfer.size);
            }
            /* Update number of currently transferred bytes */
            p_state->transfer_cnt += transfer.size;
            /* Start transfer to the endpoint buffer */
            nrf_usbd_ep_easydma_set(ep, transfer.p_data.ptr, (uint32_t)transfer.size);

#if NRF_DRV_USBD_PROTO1_FIX
            uint32_t cnt_end = (uint32_t)(-1);
            do
            {
                uint32_t cnt = (uint32_t)(-1);
                do
                {
                    nrf_usbd_event_clear(NRF_USBD_EVENT_STARTED);
                    usbd_dma_start(ep);
                    nrf_drv_systick_delay_us(2);
                    ++cnt;
                }while (!nrf_usbd_event_check(NRF_USBD_EVENT_STARTED));
                if (cnt)
                {
                    NRF_DRV_USBD_LOG_PROTO1_FIX_PRINTF("   DMA restarted: %u times\r\n", cnt);
                }

                nrf_drv_systick_delay_us(20);
                while (0 == (0x20 & *((volatile uint32_t *)(NRF_USBD_BASE + 0x474))))
                {
                    nrf_drv_systick_delay_us(2);
                }
                nrf_drv_systick_delay_us(1);

                ++cnt_end;
            } while (!nrf_usbd_event_check(nrf_drv_usbd_ep_to_endevent(ep)));
            if (cnt_end)
            {
                NRF_DRV_USBD_LOG_PROTO1_FIX_PRINTF("   DMA fully restarted: %u times\r\n", cnt_end);
            }
#else
            usbd_dma_start(ep);
#endif
            if (NRF_USBD_DMAREQ_PROCESS_DEBUG)
            {
                NRF_LOG_DEBUG("USB DMA process - finishing\r\n");
            }
            /* Transfer started - exit the loop */
            break;
        }
    }
    else
    {
        if (NRF_USBD_DMAREQ_PROCESS_DEBUG)
        {
            NRF_LOG_DEBUG("USB DMA process - EasyDMA busy\r\n");
        }
    }
}
/** @} */

typedef void (*nrf_drv_usbd_isr_t)(void);

/**
 * @brief USBD interrupt service runtimes
 *
 */
static const nrf_drv_usbd_isr_t m_isr[] =
{
    [USBD_INTEN_USBRESET_Pos   ] = USBD_ISR_Usbreset,
    [USBD_INTEN_STARTED_Pos    ] = USBD_ISR_Started,
    [USBD_INTEN_ENDEPIN0_Pos   ] = USBD_ISR_dma_epin0,
    [USBD_INTEN_ENDEPIN1_Pos   ] = USBD_ISR_dma_epin1,
    [USBD_INTEN_ENDEPIN2_Pos   ] = USBD_ISR_dma_epin2,
    [USBD_INTEN_ENDEPIN3_Pos   ] = USBD_ISR_dma_epin3,
    [USBD_INTEN_ENDEPIN4_Pos   ] = USBD_ISR_dma_epin4,
    [USBD_INTEN_ENDEPIN5_Pos   ] = USBD_ISR_dma_epin5,
    [USBD_INTEN_ENDEPIN6_Pos   ] = USBD_ISR_dma_epin6,
    [USBD_INTEN_ENDEPIN7_Pos   ] = USBD_ISR_dma_epin7,
    [USBD_INTEN_EP0DATADONE_Pos] = USBD_ISR_SetupData,
    [USBD_INTEN_ENDISOIN_Pos   ] = USBD_ISR_dma_epin8,
    [USBD_INTEN_ENDEPOUT0_Pos  ] = USBD_ISR_dma_epout0,
    [USBD_INTEN_ENDEPOUT1_Pos  ] = USBD_ISR_dma_epout1,
    [USBD_INTEN_ENDEPOUT2_Pos  ] = USBD_ISR_dma_epout2,
    [USBD_INTEN_ENDEPOUT3_Pos  ] = USBD_ISR_dma_epout3,
    [USBD_INTEN_ENDEPOUT4_Pos  ] = USBD_ISR_dma_epout4,
    [USBD_INTEN_ENDEPOUT5_Pos  ] = USBD_ISR_dma_epout5,
    [USBD_INTEN_ENDEPOUT6_Pos  ] = USBD_ISR_dma_epout6,
    [USBD_INTEN_ENDEPOUT7_Pos  ] = USBD_ISR_dma_epout7,
    [USBD_INTEN_ENDISOOUT_Pos  ] = USBD_ISR_dma_epout8,
    [USBD_INTEN_SOF_Pos        ] = USBD_ISR_Sof,
    [USBD_INTEN_USBEVENT_Pos   ] = USBD_ISR_Event,
    [USBD_INTEN_EP0SETUP_Pos   ] = USBD_ISR_Setup,
    [USBD_INTEN_EPDATA_Pos     ] = USBD_ISR_EpDataStatus,
    [USBD_INTEN_ACCESSFAULT_Pos] = USBD_ISR_AccessFault
};

/**
 * @name Interrupt handlers
 *
 * @{
 */
void USBD_IRQHandler(void)
{
    const uint32_t enabled = nrf_usbd_int_enable_get();
    uint32_t to_process = enabled;
    uint32_t active = 0;

    /* Check all enabled interrupts */
    while (to_process)
    {
        uint8_t event_nr = __CLZ(__RBIT(to_process));
        if (nrf_usbd_event_get_and_clear((nrf_usbd_event_t)nrf_drv_bitpos_to_event(event_nr)))
        {
            active |= 1UL << event_nr;
        }
        to_process &= ~(1UL << event_nr);
    }

#if NRF_DRV_USBD_PROTO1_FIX
    /* Event correcting */
    if ((0 == m_dma_pending) && (0 != (active & (USBD_INTEN_SOF_Msk))))
    {
        uint8_t usbi, uoi, uii;
        /* Testing */
        *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7A9;
        uii = (uint8_t)(*((volatile uint32_t *)(NRF_USBD_BASE + 0x804)));
        if (0 != uii)
        {
            uii &= (uint8_t)(*((volatile uint32_t *)(NRF_USBD_BASE + 0x804)));
        }

        *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7AA;
        uoi = (uint8_t)(*((volatile uint32_t *)(NRF_USBD_BASE + 0x804)));
        if (0 != uoi)
        {
            uoi &= (uint8_t)(*((volatile uint32_t *)(NRF_USBD_BASE + 0x804)));
        }
        *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7AB;
        usbi = (uint8_t)(*((volatile uint32_t *)(NRF_USBD_BASE + 0x804)));
        if (0 != usbi)
        {
            usbi &= (uint8_t)(*((volatile uint32_t *)(NRF_USBD_BASE + 0x804)));
        }
        /* Processing */
        *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7AC;
        uii &= (uint8_t)*((volatile uint32_t *)(NRF_USBD_BASE + 0x804));
        if (0 != uii)
        {
            uint8_t rb;
            m_simulated_dataepstatus |= ((uint32_t)uii)<<USBD_EPIN_BITPOS_0;
            *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7A9;
            *((volatile uint32_t *)(NRF_USBD_BASE + 0x804)) = uii;
            rb = (uint8_t)*((volatile uint32_t *)(NRF_USBD_BASE + 0x804));
            UNUSED_VARIABLE(rb);
            NRF_DRV_USBD_LOG_PROTO1_FIX_PRINTF("   uii: 0x%.2x (0x%.2x)\r\n", uii, rb);
        }

        *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7AD;
        uoi &= (uint8_t)*((volatile uint32_t *)(NRF_USBD_BASE + 0x804));
        if (0 != uoi)
        {
            uint8_t rb;
            m_simulated_dataepstatus |= ((uint32_t)uoi)<<USBD_EPOUT_BITPOS_0;
            *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7AA;
            *((volatile uint32_t *)(NRF_USBD_BASE + 0x804)) = uoi;
            rb = (uint8_t)*((volatile uint32_t *)(NRF_USBD_BASE + 0x804));
            UNUSED_VARIABLE(rb);
            NRF_DRV_USBD_LOG_PROTO1_FIX_PRINTF("   uoi: 0x%.2u (0x%.2x)\r\n", uoi, rb);
        }

        *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7AE;
        usbi &= (uint8_t)*((volatile uint32_t *)(NRF_USBD_BASE + 0x804));
        if (0 != usbi)
        {
            uint8_t rb;
            if (usbi & 0x01)
            {
                active |= USBD_INTEN_EP0SETUP_Msk;
            }
            if (usbi & 0x10)
            {
                active |= USBD_INTEN_USBRESET_Msk;
            }
            *((volatile uint32_t *)(NRF_USBD_BASE + 0x800)) = 0x7AB;
            *((volatile uint32_t *)(NRF_USBD_BASE + 0x804)) = usbi;
            rb = (uint8_t)*((volatile uint32_t *)(NRF_USBD_BASE + 0x804));
            UNUSED_VARIABLE(rb);
            NRF_DRV_USBD_LOG_PROTO1_FIX_PRINTF("   usbi: 0x%.2u (0x%.2x)\r\n", usbi, rb);
        }

        if (0 != (m_simulated_dataepstatus &
            ~((1U<<USBD_EPOUT_BITPOS_0) | (1U<<USBD_EPIN_BITPOS_0))))
        {
            active |= enabled & NRF_USBD_INT_DATAEP_MASK;
        }
        if (0 != (m_simulated_dataepstatus &
            ((1U<<USBD_EPOUT_BITPOS_0) | (1U<<USBD_EPIN_BITPOS_0))))
        {
            if (0 != (enabled & NRF_USBD_INT_EP0DATADONE_MASK))
            {
                m_simulated_dataepstatus &=
                    ~((1U<<USBD_EPOUT_BITPOS_0) | (1U<<USBD_EPIN_BITPOS_0));
                active |= NRF_USBD_INT_EP0DATADONE_MASK;
            }
        }
    }
#endif

    /* Process the active interrupts */
    bool setup_active = 0 != (active & NRF_USBD_INT_EP0SETUP_MASK);
    active &= ~NRF_USBD_INT_EP0SETUP_MASK;

    while (active)
    {
        uint8_t event_nr = __CLZ(__RBIT(active));
        m_isr[event_nr]();
        active &= ~(1UL << event_nr);
    }
    usbd_dmareq_process();

    if (setup_active)
    {
        m_isr[USBD_INTEN_EP0SETUP_Pos]();
    }
}

/** @} */
/** @} */

ret_code_t nrf_drv_usbd_init(nrf_drv_usbd_event_handler_t const event_handler)
{
    UNUSED_VARIABLE(usbd_ep_iso_capacity);

#if NRF_DRV_USBD_PROTO1_FIX
    nrf_drv_systick_init();
#endif
    if (NULL == event_handler)
    {
        return NRF_ERROR_INVALID_PARAM;
    }
    if ( m_drv_state != NRF_DRV_STATE_UNINITIALIZED)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    nrf_drv_clock_hfclk_request(NULL);
    while (!nrf_drv_clock_hfclk_is_running())
    {
        /* Just waiting */
    }

    m_event_handler = event_handler;
    m_drv_state = NRF_DRV_STATE_INITIALIZED;

    uint8_t n;
    for (n=0; n<NRF_USBD_EPIN_CNT; ++n)
    {
        nrf_drv_usbd_ep_t ep = NRF_DRV_USBD_EPIN(n);
        nrf_drv_usbd_ep_max_packet_size_set(ep, NRF_USBD_EPISO_CHECK(ep) ?
            (NRF_DRV_USBD_ISOSIZE / 2) : NRF_DRV_USBD_EPSIZE);
        usbd_drv_ep_state_t * p_state = ep_state_access(ep);
        p_state->status = NRF_USBD_EP_OK;
        p_state->handler.feeder = NULL;
        p_state->transfer_cnt = 0;
    }
    for (n=0; n<NRF_USBD_EPOUT_CNT; ++n)
    {
        nrf_drv_usbd_ep_t ep = NRF_DRV_USBD_EPOUT(n);
        nrf_drv_usbd_ep_max_packet_size_set(ep, NRF_USBD_EPISO_CHECK(ep) ?
            (NRF_DRV_USBD_ISOSIZE / 2) : NRF_DRV_USBD_EPSIZE);
        usbd_drv_ep_state_t * p_state = ep_state_access(ep);
        p_state->status = NRF_USBD_EP_OK;
        p_state->handler.consumer = NULL;
        p_state->transfer_cnt = 0;
    }

    return NRF_SUCCESS;
}

ret_code_t nrf_drv_usbd_uninit(void)
{
    if (m_drv_state != NRF_DRV_STATE_INITIALIZED)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    nrf_drv_clock_hfclk_release();
    m_event_handler = NULL;
    m_drv_state = NRF_DRV_STATE_UNINITIALIZED;
    return NRF_SUCCESS;
}

void nrf_drv_usbd_enable(void)
{
    ASSERT(m_drv_state == NRF_DRV_STATE_INITIALIZED);

    /* Prepare for READY event receiving */
    nrf_usbd_eventcause_clear(NRF_USBD_EVENTCAUSE_READY_MASK);
    /* Enable the peripheral */
    nrf_usbd_enable();
    /* Waiting for peripheral to enable, this should take a few us */
    while (0 == (NRF_USBD_EVENTCAUSE_READY_MASK & nrf_usbd_eventcause_get()))
    {
        /* Empty loop */
    }
    nrf_usbd_eventcause_clear(NRF_USBD_EVENTCAUSE_READY_MASK);

    nrf_usbd_isosplit_set(NRF_USBD_ISOSPLIT_Half);

    m_ep_ready = (((1U<<NRF_USBD_EPIN_CNT) - 1U) << USBD_EPIN_BITPOS_0);
    m_ep_dma_waiting = 0;
    m_dma_pending    = 0;
    m_last_setup_dir = NRF_DRV_USBD_EPOUT0;

    m_drv_state = NRF_DRV_STATE_POWERED_ON;
}

void nrf_drv_usbd_disable(void)
{
    ASSERT(m_drv_state != NRF_DRV_STATE_UNINITIALIZED);

    /* Stop just in case */
    nrf_drv_usbd_stop();

    /* Disable all parts */
    nrf_usbd_int_disable(nrf_usbd_int_enable_get());
    nrf_usbd_disable();
    m_dma_pending = 0;
    m_drv_state = NRF_DRV_STATE_INITIALIZED;
}

void nrf_drv_usbd_start(bool enable_sof)
{
    ASSERT(m_drv_state == NRF_DRV_STATE_POWERED_ON);

    uint32_t ints_to_enable =
           NRF_USBD_INT_USBRESET_MASK     |
           NRF_USBD_INT_STARTED_MASK      |
           NRF_USBD_INT_ENDEPIN0_MASK     |
           NRF_USBD_INT_EP0DATADONE_MASK  |
           NRF_USBD_INT_ENDEPOUT0_MASK    |
           NRF_USBD_INT_USBEVENT_MASK     |
           NRF_USBD_INT_EP0SETUP_MASK     |
           NRF_USBD_INT_DATAEP_MASK       |
           NRF_USBD_INT_ACCESSFAULT_MASK;

   if (enable_sof || NRF_DRV_USBD_PROTO1_FIX)
   {
       ints_to_enable |= NRF_USBD_INT_SOF_MASK;
   }

   /* Enable all required interrupts */
   nrf_usbd_int_enable(ints_to_enable);

   /* Enable interrupt globally */
   nrf_drv_common_irq_enable(USBD_IRQn, USBD_CONFIG_IRQ_PRIORITY);

   /* Enable pullups */
   nrf_usbd_pullup_enable();
}

void nrf_drv_usbd_stop(void)
{
    ASSERT(m_drv_state == NRF_DRV_STATE_POWERED_ON);

    /* Abort transfers */
    usbd_ep_abort_all();

    /* Disable pullups */
    nrf_usbd_pullup_disable();

    /* Disable interrupt globally */
    nrf_drv_common_irq_disable(USBD_IRQn);
}

bool nrf_drv_usbd_is_initialized(void)
{
    return (m_drv_state >= NRF_DRV_STATE_INITIALIZED);
}

bool nrf_drv_usbd_is_enabled(void)
{
    return (m_drv_state >= NRF_DRV_STATE_POWERED_ON);
}

bool nrf_drv_usbd_is_started(void)
{
    return (nrf_drv_usbd_is_enabled() && nrf_drv_common_irq_enable_check(USBD_IRQn));
}

void nrf_drv_usbd_ep_max_packet_size_set(nrf_drv_usbd_ep_t ep, uint16_t size)
{
    /* Only power of 2 size allowed */
    ASSERT((size != 0) && (size & (size - 1)) == 0);
    /* Packet size cannot be higher than maximum buffer size */
    ASSERT( ( NRF_USBD_EPISO_CHECK(ep)  && (size <= usbd_ep_iso_capacity(ep)))
           ||
           ((!NRF_USBD_EPISO_CHECK(ep)) && (size <= NRF_DRV_USBD_EPSIZE)));

    usbd_drv_ep_state_t * p_state = ep_state_access(ep);
    p_state->max_packet_size = size;
}

uint16_t nrf_drv_usbd_ep_max_packet_size_get(nrf_drv_usbd_ep_t ep)
{
    usbd_drv_ep_state_t const * p_state = ep_state_access(ep);
    return p_state->max_packet_size;
}

bool nrf_drv_usbd_ep_enable_check(nrf_drv_usbd_ep_t ep)
{
    return nrf_usbd_ep_enable_check(ep_to_hal(ep));
}

void nrf_drv_usbd_ep_enable(nrf_drv_usbd_ep_t ep)
{
    nrf_usbd_ep_enable(ep_to_hal(ep));
    nrf_usbd_int_enable(nrf_drv_usbd_ep_to_int(ep));

    if ((NRF_USBD_EP_NR_GET(ep) != 0) && NRF_USBD_EPOUT_CHECK(ep))
    {
        CRITICAL_REGION_ENTER();

        m_ep_ready |= 1U<<ep2bit(ep);
        usbd_drv_ep_state_t * p_state = ep_state_access(ep);

        if (!NRF_USBD_EPISO_CHECK(ep))
        {
            ret_code_t ret;
            NRF_DRV_USBD_TRANSFER_OUT(transfer, 0, 0);
            ret = nrf_drv_usbd_ep_transfer(ep, &transfer);
            ASSERT(ret == NRF_SUCCESS);
            UNUSED_VARIABLE(ret);
        }
        p_state->status = NRF_USBD_EP_ABORTED;

        CRITICAL_REGION_EXIT();
    }
}

void nrf_drv_usbd_ep_disable(nrf_drv_usbd_ep_t ep)
{
    nrf_usbd_ep_disable(ep_to_hal(ep));
    nrf_usbd_int_disable(nrf_drv_usbd_ep_to_int(ep));
}

ret_code_t nrf_drv_usbd_ep_transfer(
    nrf_drv_usbd_ep_t                     ep,
    nrf_drv_usbd_transfer_t const * const p_transfer)
{
    ret_code_t ret;
    const uint8_t ep_bitpos = ep2bit(ep);
    ASSERT(NULL != p_transfer);

    CRITICAL_REGION_ENTER();
    /* Setup data transaction can go only in one direction at a time */
    if ((NRF_USBD_EP_NR_GET(ep) == 0) && (ep != m_last_setup_dir))
    {
        ret = NRF_ERROR_INVALID_ADDR;
        if (NRF_USBD_FAILED_TRANSFERS_DEBUG)
        {
            NRF_LOG_DEBUG("USB driver: Transfer failed: Invalid EPr\n");
        }
    }
    else if ((m_ep_dma_waiting | ((~m_ep_ready) & USBD_EPIN_BIT_MASK)) & (1U << ep_bitpos))
    {
        /* IN (Device -> Host) transfer has to be transmitted out to allow new transmission */
        ret = NRF_ERROR_BUSY;
        if (NRF_USBD_FAILED_TRANSFERS_DEBUG)
        {
            NRF_LOG_DEBUG("USB driver: Transfer failed: EP is busy\r\n");\
        }
    }
    else if (nrf_usbd_ep_is_stall(ep))
    {
        ret = NRF_ERROR_FORBIDDEN;
        if (NRF_USBD_FAILED_TRANSFERS_DEBUG)
        {
            NRF_LOG_DEBUG("USB driver: Transfer failed: EP is stalled\r\n");
        }
    }
    else
    {
        usbd_drv_ep_state_t * p_state =  ep_state_access(ep);
        /* Prepare transfer context and handler description */
        nrf_drv_usbd_transfer_t * p_context;
        if (NRF_USBD_EPIN_CHECK(ep))
        {
            p_context = m_ep_feeder_state + NRF_USBD_EP_NR_GET(ep);
            if (nrf_drv_is_in_RAM(p_transfer->p_data.tx))
            {
                /* RAM */
                if (0 == (p_transfer->flags & NRF_DRV_USBD_TRANSFER_ZLP_FLAG))
                {
                    p_state->handler.feeder = nrf_drv_usbd_feeder_ram;
                    if (NRF_USBD_ISO_DEBUG || (!NRF_USBD_EPISO_CHECK(ep)))
                    {
                        NRF_LOG_DEBUG(
                            "USB driver: Transfer called on endpoint %x, size: %u, mode: "
                            "RAM\r\n",
                            ep,
                            p_transfer->size);
                    }
                }
                else
                {
                    p_state->handler.feeder = nrf_drv_usbd_feeder_ram_zlp;
                    if (NRF_USBD_ISO_DEBUG || (!NRF_USBD_EPISO_CHECK(ep)))
                    {
                        NRF_LOG_DEBUG(
                            "USB driver: Transfer called on endpoint %x, size: %u, mode: "
                            "RAM_ZLP\r\n",
                            ep,
                            p_transfer->size);
                    }
                }
            }
            else
            {
                /* Flash */
                if (0 == (p_transfer->flags & NRF_DRV_USBD_TRANSFER_ZLP_FLAG))
                {
                    p_state->handler.feeder = nrf_drv_usbd_feeder_flash;
                    if (NRF_USBD_ISO_DEBUG || (!NRF_USBD_EPISO_CHECK(ep)))
                    {
                        NRF_LOG_DEBUG(
                            "USB driver: Transfer called on endpoint %x, size: %u, mode: "
                            "FLASH\r\n",
                            ep,
                            p_transfer->size);
                    }
                }
                else
                {
                    p_state->handler.feeder = nrf_drv_usbd_feeder_flash_zlp;
                    if (NRF_USBD_ISO_DEBUG || (!NRF_USBD_EPISO_CHECK(ep)))
                    {
                        NRF_LOG_DEBUG(
                            "USB driver: Transfer called on endpoint %x, size: %u, mode: "
                            "FLASH_ZLP\r\n",
                            ep,
                            p_transfer->size);
                    }
                }
            }
        }
        else
        {
            p_context = m_ep_consumer_state + NRF_USBD_EP_NR_GET(ep);
            ASSERT((p_transfer->p_data.rx == NULL) || (nrf_drv_is_in_RAM(p_transfer->p_data.rx)));
            p_state->handler.consumer = nrf_drv_usbd_consumer;
        }
        *p_context = *p_transfer;
        p_state->p_context = p_context;

        p_state->transfer_cnt = 0;
        p_state->status    =  NRF_USBD_EP_OK;
        m_ep_dma_waiting   |= 1U << ep_bitpos;
        ret = NRF_SUCCESS;
        usbd_int_rise();
    }
    CRITICAL_REGION_EXIT();
    return ret;
}

ret_code_t nrf_drv_usbd_ep_handled_transfer(
    nrf_drv_usbd_ep_t                         ep,
    nrf_drv_usbd_handler_desc_t const * const p_handler)
{
    ret_code_t ret;
    const uint8_t ep_bitpos = ep2bit(ep);
    ASSERT(NULL != p_handler);

    CRITICAL_REGION_ENTER();
    /* Setup data transaction can go only in one direction at a time */
    if ((NRF_USBD_EP_NR_GET(ep) == 0) && (ep != m_last_setup_dir))
    {
        ret = NRF_ERROR_INVALID_ADDR;
        if (NRF_USBD_FAILED_TRANSFERS_DEBUG)
        {
            NRF_LOG_DEBUG("USB driver: Transfer failed: Invalid EPr\n");
        }
    }
    else if ((m_ep_dma_waiting | ((~m_ep_ready) & USBD_EPIN_BIT_MASK)) & (1U << ep_bitpos))
    {
        /* IN (Device -> Host) transfer has to be transmitted out to allow a new transmission */
        ret = NRF_ERROR_BUSY;
        if (NRF_USBD_FAILED_TRANSFERS_DEBUG)
        {
            NRF_LOG_DEBUG("USB driver: Transfer failed: EP is busy\r\n");\
        }
    }
    else if (nrf_usbd_ep_is_stall(ep))
    {
        ret = NRF_ERROR_FORBIDDEN;
        if (NRF_USBD_FAILED_TRANSFERS_DEBUG)
        {
            NRF_LOG_DEBUG("USB driver: Transfer failed: EP is stalled\r\n");
        }
    }
    else
    {
        /* Transfer can be configured now */
        usbd_drv_ep_state_t * p_state =  ep_state_access(ep);

        p_state->transfer_cnt = 0;
        p_state->handler   = p_handler->handler;
        p_state->p_context = p_handler->p_context;
        p_state->status    =  NRF_USBD_EP_OK;
        m_ep_dma_waiting   |= 1U << ep_bitpos;

        ret = NRF_SUCCESS;
        if (NRF_USBD_ISO_DEBUG || (!NRF_USBD_EPISO_CHECK(ep)))
        {
            NRF_LOG_DEBUG("USB driver: Transfer called on endpoint %x, mode: Handler\r\n", ep);
        }
        usbd_int_rise();
    }
    CRITICAL_REGION_EXIT();
    return ret;
}

void * nrf_drv_usbd_feeder_buffer_get(void)
{
    return m_tx_buffer;
}

ret_code_t nrf_drv_usbd_ep_status_get(nrf_drv_usbd_ep_t ep, size_t * p_size)
{
    ret_code_t ret;

    usbd_drv_ep_state_t const * p_state = ep_state_access(ep);
    CRITICAL_REGION_ENTER();
    *p_size = p_state->transfer_cnt;
    ret = (p_state->handler.consumer == NULL) ? p_state->status : NRF_ERROR_BUSY;
    CRITICAL_REGION_EXIT();
    return ret;
}

size_t     nrf_drv_usbd_epout_size_get(nrf_drv_usbd_ep_t ep)
{
    return nrf_usbd_epout_size_get(ep_to_hal(ep));
}

bool       nrf_drv_usbd_ep_is_busy(nrf_drv_usbd_ep_t ep)
{
    return (0 != (m_ep_dma_waiting & (1UL << ep2bit(ep))));
}

void nrf_drv_usbd_ep_stall(nrf_drv_usbd_ep_t ep)
{
    NRF_LOG_DEBUG("USB: EP %x stalled.\r\n", ep);
    nrf_usbd_ep_stall(ep_to_hal(ep));
}

void nrf_drv_usbd_ep_stall_clear(nrf_drv_usbd_ep_t ep)
{
    nrf_usbd_ep_unstall(ep_to_hal(ep));
}

bool nrf_drv_usbd_ep_stall_check(nrf_drv_usbd_ep_t ep)
{
    return nrf_usbd_ep_is_stall(ep_to_hal(ep));
}

void nrf_drv_usbd_setup_get(nrf_drv_usbd_setup_t * const p_setup)
{
    memset(p_setup, 0, sizeof(nrf_drv_usbd_setup_t));
    p_setup->bmRequestType = nrf_usbd_setup_bmrequesttype_get();
    p_setup->bmRequest     = nrf_usbd_setup_brequest_get();
    p_setup->wValue        = nrf_usbd_setup_wvalue_get();
    p_setup->wIndex        = nrf_usbd_setup_windex_get();
    p_setup->wLength       = nrf_usbd_setup_wlength_get();
}

void nrf_drv_usbd_setup_data_clear(void)
{
#if NRF_DRV_USBD_PROTO1_FIX
    /* For this fix to work properly, it must be ensured that the task is
     * executed twice one after another - blocking ISR. This is however a temporary
     * solution to be used only before production typeout. */
    uint32_t primask_copy = __get_PRIMASK();
    __disable_irq();
    nrf_usbd_task_trigger(NRF_USBD_TASK_EP0RCVOUT);
    nrf_usbd_task_trigger(NRF_USBD_TASK_EP0RCVOUT);
    __set_PRIMASK(primask_copy);
#else
    nrf_usbd_task_trigger(NRF_USBD_TASK_EP0RCVOUT);
#endif
}

void nrf_drv_usbd_setup_clear(void)
{
    nrf_usbd_task_trigger(NRF_USBD_TASK_EP0STATUS);
}

void nrf_drv_usbd_setup_stall(void)
{
    NRF_LOG_DEBUG("Setup stalled.\r\n");
    nrf_usbd_task_trigger(NRF_USBD_TASK_EP0STALL);
}

nrf_drv_usbd_ep_t nrf_drv_usbd_last_setup_dir_get(void)
{
    return m_last_setup_dir;
}

void nrf_drv_usbd_transfer_out_drop(nrf_drv_usbd_ep_t ep)
{
    ASSERT(NRF_USBD_EPOUT_CHECK(ep));

    if (m_ep_ready & (1U << ep2bit(ep)))
    {
        if (!NRF_USBD_EPISO_CHECK(ep))
        {
            ret_code_t ret;
            NRF_DRV_USBD_TRANSFER_OUT(transfer, 0, 0);
            ret = nrf_drv_usbd_ep_transfer(ep, &transfer);
            ASSERT(ret == NRF_SUCCESS);
            UNUSED_VARIABLE(ret);
        }
    }
}

#endif // USBD_ENABLED