/**
 * Copyright (c) 2016 - 2020, 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_common.h"
#if NRF_MODULE_ENABLED(APP_USBD)

#include "app_usbd.h"
#include "app_usbd_core.h"
#include "app_usbd_request.h"
#include "nrf_power.h"
#include "nrf_drv_clock.h"
#include "nrf_drv_power.h"
#if APP_USBD_CONFIG_EVENT_QUEUE_ENABLE
#include "nrf_atfifo.h"
#include "nrf_atomic.h"
#endif

#define NRF_LOG_MODULE_NAME app_usbd

#if APP_USBD_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL       APP_USBD_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR  APP_USBD_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR APP_USBD_CONFIG_DEBUG_COLOR
#else //APP_USBD_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL       0
#endif //APP_USBD_CONFIG_LOG_ENABLED
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();


/* Base variables tests */

/* Check event of app_usbd_event_type_t enumerator */
STATIC_ASSERT((int32_t)APP_USBD_EVT_FIRST_POWER == (int32_t)NRF_DRV_USBD_EVT_CNT);
STATIC_ASSERT(sizeof(app_usbd_event_type_t) == sizeof(nrf_drv_usbd_event_type_t));

STATIC_ASSERT(sizeof(app_usbd_descriptor_header_t) == 2);
STATIC_ASSERT(sizeof(app_usbd_descriptor_device_t) == 18);
STATIC_ASSERT(sizeof(app_usbd_descriptor_configuration_t) == 9);
STATIC_ASSERT(sizeof(app_usbd_descriptor_iface_t) == 9);
STATIC_ASSERT(sizeof(app_usbd_descriptor_ep_t) == 7);
STATIC_ASSERT(sizeof(app_usbd_descriptor_iad_t) == 8);

STATIC_ASSERT(sizeof(app_usbd_setup_t) == sizeof(nrf_drv_usbd_setup_t));

/**
 * @internal
 * @defgroup app_usbd_internals USBD library internals
 * @ingroup app_usbd
 *
 * Internal variables, auxiliary macros and functions of USBD library.
 * @{
 */

#if (APP_USBD_PROVIDE_SOF_TIMESTAMP) || defined(__SDK_DOXYGEN__)
/**
 * @brief The last received frame number.
 */
static uint16_t m_last_frame;
#endif

/**
 * @brief Variable type for endpoint configuration.
 *
 * Each endpoint would have assigned this type of configuration structure.
 */
typedef struct
{
    /**
     * @brief The class instance.
     *
     * The pointer to the class instance that is connected to the endpoint.
     */
    app_usbd_class_inst_t const * p_cinst;

    /**
     * @brief Endpoint event handler.
     *
     * Event handler for the endpoint.
     * It is set to event handler for the class instance during connection by default,
     * but it can be then updated for as a reaction for @ref APP_USBD_EVT_ATTACHED event.
     * This way we can speed up the interpretation of endpoint related events.
     */
    app_usbd_ep_event_handler_t event_handler;
}app_usbd_ep_conf_t;


/**
 * @brief Internal event with SOF counter.
 */
typedef struct
{
    app_usbd_internal_evt_t evt;     //!< Internal event type

#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE) \
     || defined(__SDK_DOXYGEN__)
    uint16_t                sof_cnt;        //!< Number of the SOF events that appears before current event
    uint16_t                start_frame;    //!< Number of the SOF frame that starts this event
#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)

} app_usbd_internal_queue_evt_t;

#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE) || defined(__SDK_DOXYGEN__)
/**
 * @brief Event queue.
 *
 * The queue with events to be processed.
 */
NRF_ATFIFO_DEF(m_event_queue, app_usbd_internal_queue_evt_t, APP_USBD_CONFIG_EVENT_QUEUE_SIZE);

#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE) \
     || defined(__SDK_DOXYGEN__)

/** @brief SOF events counter */
static nrf_atomic_u32_t m_sof_events_cnt;

/** @brief SOF Frame counter */
static uint16_t m_event_frame;

/* Limit of SOF events stacked until warning message. */
#define APP_USBD_SOF_WARNING_LIMIT 500
#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
       //  || defined(__SDK_DOXYGEN__)

#endif

/**
 * @brief Instances connected with IN endpoints.
 *
 * Array of instance pointers connected with every IN endpoint.
 * @sa m_epout_instances
 */
static app_usbd_ep_conf_t m_epin_conf[NRF_USBD_EPIN_CNT];

/**
 * @brief Instances connected with OUT endpoints.
 *
 * Array of instance pointers connected with every OUT endpoint.
 * @sa m_epin_instances
 */
static app_usbd_ep_conf_t m_epout_conf[NRF_USBD_EPIN_CNT];

/**
 * @brief Beginning of classes list.
 *
 * All enabled in current configuration instances are connected into
 * a single linked list chain.
 * This variable points to first element.
 * Core class instance (connected to endpoint 0) is not listed here.
 */
static app_usbd_class_inst_t const * m_p_first_cinst;

/**
 * @brief Classes list that requires SOF events.
 *
 * Pointer to first class that requires SOF events.
 */
static app_usbd_class_inst_t const * m_p_first_sof_cinst;

/**
 * @brief Classes list that require SOF events in interrupt.
 *
 * Pointer to first class that requires SOF events in interrupt.
 */
static app_usbd_class_inst_t const * m_p_first_sof_interrupt_cinst;

/**
 * @brief Default configuration (when NULL is passed to @ref app_usbd_init).
 */
static const app_usbd_config_t m_default_conf = {
#if (!(APP_USBD_CONFIG_EVENT_QUEUE_ENABLE)) || defined(__SDK_DOXYGEN__)
    .ev_handler = app_usbd_event_execute,
#endif
#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE) || defined(__SDK_DOXYGEN__)
    .ev_isr_handler = NULL,
#endif
    .ev_state_proc = NULL,
    .enable_sof = false
};

/**
 * @brief SUSPEND state machine states.
 *
 * The enumeration of internal SUSPEND state machine states.
 */
typedef enum
{
    SUSTATE_STOPPED,                     /**< The USB driver was not started */
    SUSTATE_STARTED,                     /**< The USB driver was started - waiting for USB RESET */
    SUSTATE_ACTIVE,                      /**< Active state */
    SUSTATE_SUSPENDING,                  /**< Suspending - waiting for the user to acknowledge */
    SUSTATE_SUSPEND,                     /**< Suspended */
    SUSTATE_RESUMING,                    /**< Resuming - waiting for clock */
    SUSTATE_WAKINGUP_WAITING_HFCLK_WREQ, /**< Waking up - waiting for clock and WUREQ from driver */
    SUSTATE_WAKINGUP_WAITING_HFCLK,      /**< Waking up - waiting for HFCLK (WUREQ detected) */
    SUSTATE_WAKINGUP_WAITING_WREQ,       /**< Waking up - waiting for WREQ (HFCLK active) */
}app_usbd_sustate_t;

/**
 * @brief Current suspend state.
 *
 * The state of the suspend state machine.
 */
static app_usbd_sustate_t m_sustate;

/**
 * @brief Remote wake-up register/unregister.
 *
 * Counter incremented when appended instance required remote wake-up functionality.
 * It should be decremented when the class is removed.
 * When this counter is not zero, remote wake-up functionality is activated inside core.
 */
static uint8_t m_rwu_registered_counter;

/**
 * @brief Current configuration.
 */
static app_usbd_config_t m_current_conf;

/**
 * @brief Class interface call: event handler
 *
 * @ref app_usbd_class_interface_t::event_handler
 *
 * @param[in] p_cinst Class instance.
 * @param[in] p_event Event passed to class instance.
 *
 * @return Standard error code @ref ret_code_t
 * @retval NRF_SUCCESS event handled successfully.
 * @retval NRF_ERROR_NOT_SUPPORTED unsupported event.
 * */
static inline ret_code_t class_event_handler(app_usbd_class_inst_t  const * const p_cinst,
                                             app_usbd_complex_evt_t const * const p_event)
{
    ASSERT(p_cinst != NULL);
    ASSERT(p_cinst->p_class_methods != NULL);
    ASSERT(p_cinst->p_class_methods->event_handler != NULL);
    return p_cinst->p_class_methods->event_handler(p_cinst, p_event);
}

#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE) || defined(__SDK_DOXYGEN__)
static inline void class_sof_interrupt_handler(app_usbd_class_inst_t  const * const p_cinst,
                                               app_usbd_complex_evt_t const * const p_event)
{
    ASSERT(p_cinst != NULL);
    ASSERT(p_cinst->p_data != NULL);
    ASSERT(p_cinst->p_data->sof_handler != NULL);
    p_cinst->p_data->sof_handler(p_event->drv_evt.data.sof.framecnt);
}

/**
 * @brief User event handler call (passed via configuration).
 *
 * @param p_event Handler of an event that is going to be added into queue.
 * @param queued  The event is visible in the queue.
 */
static inline void user_event_handler(app_usbd_internal_evt_t const * const p_event, bool queued)
{
    if ((m_current_conf.ev_isr_handler) != NULL)
    {
        m_current_conf.ev_isr_handler(p_event, queued);
    }
}
#endif

/**
 * @brief User event processor call (passed via configuration).
 *
 * @param event Event type.
 */
static inline void user_event_state_proc(app_usbd_event_type_t event)
{
    if ((m_current_conf.ev_state_proc) != NULL)
    {
        m_current_conf.ev_state_proc(event);
    }
}

/**
 * @brief Find a specified descriptor.
 *
 * @param[in] p_cinst       Class instance.
 * @param[in] desc_type     Descriptor type @ref app_usbd_descriptor_t
 * @param[in] desc_index    Descriptor index.
 * @param[out] p_desc       Pointer to escriptor.
 * @param[out] p_desc_len   Length of descriptor.
 *
 * @return Standard error code @ref ret_code_t
 * @retval NRF_SUCCESS          Descriptor successfully found.
 * @retval NRF_ERROR_NOT_FOUND  Descriptor not found.
 * */
ret_code_t app_usbd_class_descriptor_find(app_usbd_class_inst_t const * const p_cinst,
                                          uint8_t                             desc_type,
                                          uint8_t                             desc_index,
                                          uint8_t                           * p_desc,
                                          size_t                            * p_desc_len)
{
    app_usbd_class_descriptor_ctx_t siz;
    APP_USBD_CLASS_DESCRIPTOR_INIT(&siz);
    uint32_t total_size = 0;
    while(p_cinst->p_class_methods->feed_descriptors(&siz, p_cinst, NULL, sizeof(uint8_t)))
    {
        total_size++;
    }

    uint8_t cur_len = 0;
    uint32_t cur_size = 0;

    uint8_t index = 0;
    app_usbd_class_descriptor_ctx_t descr;
    APP_USBD_CLASS_DESCRIPTOR_INIT(&descr);

    while(cur_size < total_size)
    {
        /* First byte of a descriptor is its size */
        UNUSED_RETURN_VALUE(p_cinst->p_class_methods->feed_descriptors(&descr,
                                                                       p_cinst,
                                                                       &cur_len,
                                                                       sizeof(uint8_t)));

        /* Second byte is type of the descriptor */
        uint8_t type;
        UNUSED_RETURN_VALUE(p_cinst->p_class_methods->feed_descriptors(&descr,
                                                                       p_cinst,
                                                                       &type,
                                                                       sizeof(uint8_t)));

        if(type == desc_type)
        {
            if(index == desc_index)
            {
                /* Copy the length of descriptor to *p_desc_len */
                *p_desc_len = cur_len;
                /* Two first bytes of descriptor have already been fed - copy them to *p_desc */
                *p_desc++ = cur_len;
                *p_desc++ = desc_type;
                /* Copy the rest of descriptor to *p_desc */
                UNUSED_RETURN_VALUE(p_cinst->p_class_methods->feed_descriptors(&descr,
                                                                               p_cinst,
                                                                               p_desc,
                                                                               cur_len-2));
                return NRF_SUCCESS;
            }
            else
            {
                index++;
            }
        }
        /* Fast-forward through unmatched descriptor */
        UNUSED_RETURN_VALUE(p_cinst->p_class_methods->feed_descriptors(&descr,
                                                                       p_cinst,
                                                                       NULL,
                                                                       cur_len-2));
        cur_size += cur_len;
    }
    return NRF_ERROR_NOT_FOUND;
}

/**
 * @brief Access into selected endpoint configuration structure.
 *
 * @param ep Endpoint address.
 * @return A pointer to the endpoint configuration structure.
 *
 * @note This function would assert when endpoint number is not correct and debugging is enabled.
 */
static app_usbd_ep_conf_t * app_usbd_ep_conf_access(nrf_drv_usbd_ep_t ep)
{
    if (NRF_USBD_EPIN_CHECK(ep))
    {
        uint8_t nr = NRF_USBD_EP_NR_GET(ep);
        ASSERT(nr < NRF_USBD_EPIN_CNT);
        return &m_epin_conf[nr];
    }
    else
    {
        uint8_t nr = NRF_USBD_EP_NR_GET(ep);
        ASSERT(nr < NRF_USBD_EPOUT_CNT);
        return &m_epout_conf[nr];
    }
}

/**
 * @brief Accessing instance connected with selected endpoint.
 *
 * @param ep Endpoint number.
 *
 * @return The pointer to the instance connected with endpoint.
 */
static inline app_usbd_class_inst_t const * app_usbd_ep_instance_get(nrf_drv_usbd_ep_t ep)
{
    return app_usbd_ep_conf_access(ep)->p_cinst;
}

/**
 * @brief Connect instance with selected endpoint.
 *
 * This function configures instance connected to endpoint but also sets
 * default event handler function pointer.
 *
 * @param ep      Endpoint number.
 * @param p_cinst The instance to connect into the selected endpoint.
 *                NULL if endpoint is going to be disconnected.
 *
 * @note Disconnecting EP0 is not allowed and protected by assertion.
 */
static void app_usbd_ep_instance_set(nrf_drv_usbd_ep_t ep, app_usbd_class_inst_t const * p_cinst)
{
    app_usbd_ep_conf_t * p_ep_conf = app_usbd_ep_conf_access(ep);
    /* Set instance and default event handler */
    p_ep_conf->p_cinst = p_cinst;
    if (p_cinst == NULL)
    {
        ASSERT((ep != NRF_DRV_USBD_EPOUT0) && (ep != NRF_DRV_USBD_EPIN0)); /* EP0 should never be disconnected */
        p_ep_conf->event_handler = NULL;
    }
    else
    {
        p_ep_conf->event_handler = p_cinst->p_class_methods->event_handler;
    }
}

/**
 * @brief Call the core handler.
 *
 * Core instance is special kind of instance that is connected only to endpoint 0.
 * It is not present in instance list.
 * This auxiliary function makes future changes easier.
 * Just call the event instance for core module here.
 */
static inline ret_code_t app_usbd_core_handler_call(app_usbd_internal_evt_t const * const p_event)
{
    return m_epout_conf[0].event_handler(
        m_epout_conf[0].p_cinst,
        (app_usbd_complex_evt_t const *)p_event);
}



/**
 * @brief Add event for execution.
 *
 * Dependent on configuration event would be executed in place or would be added into queue
 * to be executed later.
 *
 * @param p_event_input Event to be executed.
 */
static inline void app_usbd_event_add(app_usbd_internal_evt_t const * const p_event_input)
{
    app_usbd_internal_evt_t const * p_event = p_event_input;


    if (p_event->type == APP_USBD_EVT_DRV_SETUP)
    {
        uint8_t bRequest      = nrf_usbd_setup_brequest_get();
        uint8_t bmRequestType = nrf_usbd_setup_bmrequesttype_get();

        if ((bmRequestType == app_usbd_setup_req_val(
                APP_USBD_SETUP_REQREC_DEVICE,
                APP_USBD_SETUP_REQTYPE_STD,
                APP_USBD_SETUP_REQDIR_OUT))
                && (bRequest == APP_USBD_SETUP_STDREQ_SET_ADDRESS))
        {
            static const app_usbd_internal_evt_t event_setaddress =
            {
                .type = APP_USBD_EVT_SETUP_SETADDRESS,
            };
            p_event = &event_setaddress;
        }
    }

#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE)

    if (p_event->app_evt.type == APP_USBD_EVT_DRV_SOF)
    {
        /* Propagate SOF event to classes that need it in interrupt */
        app_usbd_class_inst_t const * p_inst = app_usbd_class_sof_interrupt_first_get();
        while (NULL != p_inst)
        {
            class_sof_interrupt_handler(p_inst, (app_usbd_complex_evt_t const *)p_event);
            p_inst = app_usbd_class_sof_interrupt_next_get(p_inst);
        }

#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
        CRITICAL_REGION_ENTER();
        if (m_sof_events_cnt == 0)
        {
            m_event_frame = p_event->drv_evt.data.sof.framecnt;
        }
        UNUSED_RETURN_VALUE(nrf_atomic_u32_add(&m_sof_events_cnt, 1));
        CRITICAL_REGION_EXIT();

        user_event_handler(p_event, true);
        if (m_sof_events_cnt == APP_USBD_SOF_WARNING_LIMIT)
        {
            NRF_LOG_WARNING("Stacked over %d SOF events.", APP_USBD_SOF_WARNING_LIMIT);
        }
        return;
    
#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)

#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_INTERRUPT)

        user_event_handler(p_event, false);
        app_usbd_event_execute(p_event);
        return;

#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_INTERRUPT)
    }

    nrf_atfifo_item_put_t cx;
    app_usbd_internal_queue_evt_t * p_event_item = nrf_atfifo_item_alloc(m_event_queue, &cx);

    if (NULL != p_event_item)
    {
        bool visible;
        p_event_item->evt = *p_event;

#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
        CRITICAL_REGION_ENTER();
        p_event_item->start_frame = m_event_frame - m_sof_events_cnt + 1;
        p_event_item->sof_cnt = nrf_atomic_u32_fetch_store(&m_sof_events_cnt, 0);
        CRITICAL_REGION_EXIT();
#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)

        visible = nrf_atfifo_item_put(m_event_queue, &cx);
        user_event_handler(p_event, visible);
    }
    else
    {
        NRF_LOG_ERROR("Event queue full.");
    }
#else
    m_current_conf.ev_handler(p_event);
#endif
}

/**
 * @brief Power event handler.
 *
 * The function that pushes power events into the queue.
 * @param p_event Event from power driver to map into APP_USBD_EVT_POWER_ event.
 */
#if APP_USBD_CONFIG_POWER_EVENTS_PROCESS
static void app_usbd_power_event_handler(nrf_drv_power_usb_evt_t event)
{
    switch(event)
    {
        case NRF_DRV_POWER_USB_EVT_DETECTED:
        {
            static const app_usbd_internal_evt_t ev = {
                .type = APP_USBD_EVT_POWER_DETECTED
            };
            app_usbd_event_add(&ev);
            break;
        }
        case NRF_DRV_POWER_USB_EVT_REMOVED:
        {
            static const app_usbd_internal_evt_t ev = {
                .type = APP_USBD_EVT_POWER_REMOVED
            };
            app_usbd_event_add(&ev);
            break;
        }
        case NRF_DRV_POWER_USB_EVT_READY:
        {
            static const app_usbd_internal_evt_t ev = {
                .type = APP_USBD_EVT_POWER_READY
            };
            app_usbd_event_add(&ev);
            break;
        }
        default:
            ASSERT(false);
    }
}
#endif

/**
 * @brief Event handler.
 *
 * The function that pushes the event into the queue.
 * @param p_event Event to push.
 */
static void app_usbd_event_handler(nrf_drv_usbd_evt_t const * const p_event)
{
    app_usbd_event_add((app_usbd_internal_evt_t const *)p_event);
}

/**
 * @brief HF clock ready event handler.
 *
 * Function that is called when high frequency clock is started.
 *
 * @param event Event type that comes from clock driver.
 */
static void app_usbd_hfclk_ready(nrf_drv_clock_evt_type_t event)
{
    ASSERT(NRF_DRV_CLOCK_EVT_HFCLK_STARTED == event);
    static const app_usbd_evt_t evt_data = {
        .type = APP_USBD_EVT_HFCLK_READY
    };
    app_usbd_event_add((app_usbd_internal_evt_t const * )&evt_data);
}

/**
 * @brief Check if the HFCLK was requested in selected suspend state machine state.
 *
 *
 * @param sustate State to be checked.
 *
 * @retval true  High frequency clock was requested in selected state.
 * @retval false High frequency clock was released in selected state.
 */
static inline bool app_usbd_sustate_with_requested_hfclk(app_usbd_sustate_t sustate)
{
    switch(sustate)
    {
        case SUSTATE_STOPPED:                       return false;
        case SUSTATE_STARTED:                       return false;
        case SUSTATE_ACTIVE:                        return true;
        case SUSTATE_SUSPENDING:                    return false;
        case SUSTATE_SUSPEND:                       return false;
        case SUSTATE_RESUMING:                      return true;
        case SUSTATE_WAKINGUP_WAITING_HFCLK_WREQ:   return true;
        case SUSTATE_WAKINGUP_WAITING_HFCLK:        return true;
        case SUSTATE_WAKINGUP_WAITING_WREQ:         return true;
        default:
            return false;
    }
}

/**
 * @brief Check it the HFCLK is running in selected suspend state machine state.
 *
 * @param sustate State to be checked.
 *
 * @retval true  High frequency clock is running in selected state.
 * @retval false High frequency clock is released in selected state.
 */
static inline bool app_usbd_sustate_with_running_hfclk(app_usbd_sustate_t sustate)
{
    switch(sustate)
    {
        case SUSTATE_STOPPED:                       return false;
        case SUSTATE_STARTED:                       return false;
        case SUSTATE_ACTIVE:                        return true;
        case SUSTATE_SUSPENDING:                    return false;
        case SUSTATE_SUSPEND:                       return false;
        case SUSTATE_RESUMING:                      return false;
        case SUSTATE_WAKINGUP_WAITING_HFCLK_WREQ:   return false;
        case SUSTATE_WAKINGUP_WAITING_HFCLK:        return false;
        case SUSTATE_WAKINGUP_WAITING_WREQ:         return true;
        default:
            return false;
    }
}

/**
 * @brief Get current suspend state machine state.
 *
 * @return The state of the suspend state machine.
 */
static inline app_usbd_sustate_t sustate_get(void)
{
    return m_sustate;
}

/**
 * @brief Set current suspend state machine state.
 *
 * @param sustate The requested state of the state machine.
 */
static inline void sustate_set(app_usbd_sustate_t sustate)
{
    if (app_usbd_sustate_with_requested_hfclk(sustate) != app_usbd_sustate_with_requested_hfclk(m_sustate))
    {
        if (app_usbd_sustate_with_requested_hfclk(sustate))
        {
            static nrf_drv_clock_handler_item_t clock_handler_item =
            {
                .event_handler = app_usbd_hfclk_ready
            };
            nrf_drv_clock_hfclk_request(&clock_handler_item);
        }
        else
        {
            nrf_drv_clock_hfclk_release();
        }
    }
    if (app_usbd_sustate_with_running_hfclk(sustate) != app_usbd_sustate_with_running_hfclk(m_sustate))
    {
        if (app_usbd_sustate_with_running_hfclk(sustate))
        {
            nrf_drv_usbd_active_irq_config();
        }
        else
        {
            nrf_drv_usbd_suspend_irq_config();
        }
    }
    m_sustate = sustate;
}

/**
 * @brief Default selection function for interface.
 *
 * This function just enables and clears interface endpoints.
 *
 * @param[in] p_inst    Class instance.
 * @param[in] iface_idx Interface index.
 * @param[in] alternate Interface alternate setting.
 *
 * @note Currently only alternate setting 0 is supported.
 *
 * @return Standard error code @ref ret_code_t
 * @retval NRF_SUCCESS               Endpoints  enabled and cleared.
 * @retval NRF_ERROR_INVALID_PARAM   Unsupported alternate selected.
 */
static inline ret_code_t default_iface_select(
    app_usbd_class_inst_t const * const p_inst,
    uint8_t                             iface_idx,
    uint8_t                             alternate)
{
    ASSERT(iface_idx <= app_usbd_class_iface_count_get(p_inst));

    if (alternate != 0)
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    app_usbd_class_iface_conf_t const * p_iface  = app_usbd_class_iface_get(p_inst, iface_idx);
    uint8_t ep_count = app_usbd_class_iface_ep_count_get(p_iface);

    for (uint8_t i = 0; i < ep_count; ++i)
    {
        /* Enable every endpoint */
        app_usbd_class_ep_conf_t const * p_ep = app_usbd_class_iface_ep_get(p_iface, i);
        app_usbd_ep_enable(p_ep->address);
    }
    return NRF_SUCCESS;
}

/**
 * @brief Default deselection function for interface.
 *
 * This function just disables all interface endpoints.
 *
 * @param[in] p_inst    Class instance.
 * @param[in] iface_idx Interface index.
 */
static inline void default_iface_deselect(
    app_usbd_class_inst_t const * const p_inst,
    uint8_t                             iface_idx)
{
    ASSERT(iface_idx <= app_usbd_class_iface_count_get(p_inst));

    app_usbd_class_iface_conf_t const * p_iface  = app_usbd_class_iface_get(p_inst, iface_idx);
    uint8_t ep_count = app_usbd_class_iface_ep_count_get(p_iface);

    for (uint8_t i = 0; i < ep_count; ++i)
    {
        /* Disable every endpoint */
        app_usbd_class_ep_conf_t const * p_ep = app_usbd_class_iface_ep_get(p_iface, i);
        app_usbd_ep_disable(p_ep->address);
    }
}


/** @} */

#if (APP_USBD_PROVIDE_SOF_TIMESTAMP) || defined(__SDK_DOXYGEN__)
uint32_t app_usbd_sof_timestamp_get(void)
{
    return m_last_frame;
}
#endif

ret_code_t app_usbd_init(app_usbd_config_t const * p_config)
{
    ASSERT(nrf_drv_clock_init_check());
    ret_code_t ret;

#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE) || defined(__SDK_DOXYGEN__)
    ret = NRF_ATFIFO_INIT(m_event_queue);
    if (NRF_SUCCESS != ret)
    {
        return NRF_ERROR_INTERNAL;
    }
#endif

    /* This is called at the beginning to secure multiple calls to init function */
    ret = nrf_drv_usbd_init(app_usbd_event_handler);
    if (NRF_SUCCESS != ret)
    {
        return ret;
    }

    /* Clear the variables */
    m_sustate = SUSTATE_STOPPED;
    m_p_first_cinst = NULL;
    m_p_first_sof_cinst = NULL;
    memset(m_epin_conf , 0, sizeof(m_epin_conf ));
    memset(m_epout_conf, 0, sizeof(m_epout_conf));
    /* Save the new configuration */
    if (p_config == NULL)
    {
        m_current_conf = m_default_conf;
    }
    else
    {
        m_current_conf = *p_config;
    }

#if (!(APP_USBD_CONFIG_EVENT_QUEUE_ENABLE))
    if(m_current_conf.ev_handler == NULL)
    {
        m_current_conf.ev_handler = m_default_conf.ev_handler;
    }
#endif

#if APP_USBD_CONFIG_POWER_EVENTS_PROCESS
    ret = nrf_drv_power_init(NULL);
    if ((ret != NRF_SUCCESS) && (ret != NRF_ERROR_MODULE_ALREADY_INITIALIZED))
    {
        /* This should never happen */
        APP_ERROR_HANDLER(ret);
    }
#endif

    /*Pin core class to required endpoints*/
    uint8_t iface_idx;
    app_usbd_class_iface_conf_t const * p_iface;
    app_usbd_class_inst_t const * const p_inst = app_usbd_core_instance_access();
    iface_idx = 0;
    while ((p_iface = app_usbd_class_iface_get(p_inst, iface_idx++)) != NULL)
    {
        uint8_t ep_idx = 0;
        app_usbd_class_ep_conf_t const * p_ep;
        while ((p_ep = app_usbd_class_iface_ep_get(p_iface, ep_idx++)) != NULL)
        {
            app_usbd_ep_instance_set(app_usbd_class_ep_address_get(p_ep), p_inst);
        }
    }

    /* Successfully attached */
    const app_usbd_evt_t evt_data = {
        .type = APP_USBD_EVT_INST_APPEND
    };

    ret = class_event_handler(p_inst, (app_usbd_complex_evt_t const *)(&evt_data));
    if (NRF_SUCCESS != ret)
    {
        UNUSED_RETURN_VALUE(nrf_drv_usbd_uninit());
        return ret;
    }

    return NRF_SUCCESS;
}


ret_code_t app_usbd_uninit(void)
{
#if APP_USBD_CONFIG_POWER_EVENTS_PROCESS
    nrf_drv_power_usbevt_uninit();
#endif

    /* We get this error at very beginning but it would be used at the end of the function */
    const ret_code_t ret = nrf_drv_usbd_uninit();

    /* Unchain instance list */
    app_usbd_class_inst_t const * * pp_inst;
    pp_inst = &m_p_first_cinst;
    while (NULL != (*pp_inst))
    {
        app_usbd_class_inst_t const * * pp_next = &app_usbd_class_data_access(*pp_inst)->p_next;
        (*pp_inst) = NULL;
        pp_inst = pp_next;
    }

    /* Unchain SOF list */
    pp_inst = &m_p_first_sof_cinst;
    while (NULL != (*pp_inst))
    {
        app_usbd_class_inst_t const * * pp_next = &app_usbd_class_data_access(*pp_inst)->p_sof_next;
        (*pp_inst) = NULL;
        pp_inst = pp_next;
    }
    
    /* Unchain SOF interrupt list */
    pp_inst = &m_p_first_sof_interrupt_cinst;
    while (NULL != (*pp_inst))
    {
        app_usbd_class_inst_t const * * pp_next = &app_usbd_class_data_access(*pp_inst)->p_sof_next;
        (*pp_inst) = NULL;
        pp_inst = pp_next;
    }

    /* Clear all endpoints configurations */
    memset(m_epin_conf , 0, sizeof(m_epin_conf ));
    memset(m_epout_conf, 0, sizeof(m_epout_conf));
    /* Clear current configuration */
    memset(&m_current_conf, 0, sizeof(m_current_conf));

    return ret;
}


#if APP_USBD_CONFIG_POWER_EVENTS_PROCESS
ret_code_t app_usbd_power_events_enable(void)
{
    if (!nrf_drv_usbd_is_initialized() || nrf_drv_usbd_is_enabled())
    {
        return NRF_ERROR_INVALID_STATE;
    }

    ASSERT((!APP_USBD_CONFIG_EVENT_QUEUE_ENABLE) || (USBD_CONFIG_IRQ_PRIORITY == POWER_CONFIG_IRQ_PRIORITY));

    ret_code_t ret;
    static const nrf_drv_power_usbevt_config_t config =
    {
        .handler = app_usbd_power_event_handler
    };

    ret = nrf_drv_power_usbevt_init(&config);
    APP_ERROR_CHECK(ret);

    return NRF_SUCCESS;
}
#endif /* APP_USBD_CONFIG_POWER_EVENTS_PROCESS */


void app_usbd_enable(void)
{
    nrf_drv_usbd_enable();
}


void app_usbd_disable(void)
{
    ASSERT(!nrf_drv_usbd_is_started());
    nrf_drv_usbd_disable();
}


void app_usbd_start(void)
{
    ASSERT(nrf_drv_usbd_is_enabled());

    /* Check if interface numbers are in correct order */
    if (APP_USBD_CONFIG_LOG_ENABLED)
    {
        uint8_t next_iface = 0;
        for (app_usbd_class_inst_t const * * pp_inst = &m_p_first_cinst;
             (*pp_inst) != NULL;
             pp_inst = &(app_usbd_class_data_access(*pp_inst)->p_next))
        {
            uint8_t iface_idx = 0;
            app_usbd_class_iface_conf_t const * p_iface;
            while (NULL != (p_iface = app_usbd_class_iface_get(*pp_inst, iface_idx++)))
            {
                if (p_iface->number != next_iface)
                {
                    NRF_LOG_WARNING("Unexpected interface number, expected %d, got %d",
                                    next_iface,
                                    p_iface->number);
                }
                ++next_iface;
            }
        }
    }

    /* Power should be already enabled - wait just in case if user calls
     * app_usbd_start just after app_usbd_enable without waiting for the event. */
    while (!nrf_power_usbregstatus_outrdy_get())
    {
        /* Wait for the power but terminate the function if USBD power disappears */
        if (!nrf_power_usbregstatus_vbusdet_get())
            return;
    }

    static const app_usbd_evt_t evt_data = {
        .type = APP_USBD_EVT_START_REQ
    };
    app_usbd_event_add((app_usbd_internal_evt_t const * )&evt_data);
}


void app_usbd_stop(void)
{
    const app_usbd_evt_t evt_data = {
        .type = APP_USBD_EVT_STOP_REQ
    };
    app_usbd_event_add((app_usbd_internal_evt_t const * )&evt_data);
}

void app_usbd_suspend_req(void)
{
    const app_usbd_evt_t evt_data = {
       .type = APP_USBD_EVT_SUSPEND_REQ
   };
   app_usbd_event_add((app_usbd_internal_evt_t const * )&evt_data);
}

bool app_usbd_wakeup_req(void)
{
    ASSERT(app_usbd_class_rwu_enabled_check());
    if (!app_usbd_core_feature_state_get(APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP))
        return false;

    const app_usbd_evt_t evt_data = {
        .type = APP_USBD_EVT_WAKEUP_REQ
    };
    app_usbd_event_add((app_usbd_internal_evt_t const * )&evt_data);
    return true;
}

bool app_usbd_active_check(void)
{
    return (sustate_get() == SUSTATE_ACTIVE);
}

void app_usbd_event_execute(app_usbd_internal_evt_t const * const p_event)
{
    ASSERT(NULL != m_p_first_cinst);
    /* If no event queue is implemented, it has to be ensured that this function is never called
     * from the context higher than USB interrupt level
     * If queue is implemented it would be called always from Thread level
     * if the library is used correctly.
     * NOTE: Higher interrupt level -> lower priority value.
     */
    ASSERT(USBD_CONFIG_IRQ_PRIORITY <= current_int_priority_get());

    /* Note - there should never be situation that event is generated on disconnected endpoint */
    switch (p_event->type)
    {
        case APP_USBD_EVT_START_REQ:
        {
            static const app_usbd_evt_t evt_data = {
                .type = APP_USBD_EVT_STARTED
            };

            /* Send event to all classes */
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call((app_usbd_internal_evt_t const * )&evt_data));
            app_usbd_all_call((app_usbd_complex_evt_t const *)&evt_data);
            user_event_state_proc(APP_USBD_EVT_STARTED);

            app_usbd_all_iface_deselect();
            app_usbd_core_ep0_disable();
            nrf_drv_usbd_start((NULL != m_p_first_sof_cinst) || (m_current_conf.enable_sof) || (APP_USBD_PROVIDE_SOF_TIMESTAMP));
            sustate_set(SUSTATE_STARTED);
            break;
        }
        case APP_USBD_EVT_STOP_REQ:
        {
            static const app_usbd_evt_t evt_data = {
                .type = APP_USBD_EVT_STOPPED
            };

            app_usbd_all_iface_deselect();
            nrf_drv_usbd_stop();
            sustate_set(SUSTATE_STOPPED);

            /* Send event to all classes */
            app_usbd_all_call((app_usbd_complex_evt_t const * )&evt_data);
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call((app_usbd_internal_evt_t const *)&evt_data));
            user_event_state_proc(APP_USBD_EVT_STOPPED);
            if (app_usbd_sustate_with_requested_hfclk(sustate_get()))
            {
                nrf_drv_clock_hfclk_release();
            }

            break;
        }
        case APP_USBD_EVT_HFCLK_READY:
        {
            switch(sustate_get())
            {
                case SUSTATE_RESUMING:
                {
                    sustate_set(SUSTATE_ACTIVE);
                    break;
                }
                case SUSTATE_WAKINGUP_WAITING_HFCLK_WREQ:
                {
                    sustate_set(SUSTATE_WAKINGUP_WAITING_WREQ);
                    break;
                }
                case SUSTATE_WAKINGUP_WAITING_HFCLK:
                {
                    sustate_set(SUSTATE_ACTIVE);
                    break;
                }
                default:
                    break; // Just ignore - it can happen in specific situation
            }
            break;
        }
        case APP_USBD_EVT_SUSPEND_REQ:
        {
            /* Suspend request can be only processed when we are in suspending state */
            if (SUSTATE_SUSPENDING == sustate_get())
            {
                if (nrf_drv_usbd_suspend())
                {
                    sustate_set(SUSTATE_SUSPEND);
                }
            }
            break;
        }
        case APP_USBD_EVT_WAKEUP_REQ:
        {
            /* Suspend temporary if no suspend function was called from the application.
             * This makes it possible to generate APP_USBD_EVT_DRV_WUREQ event from the driver */
            if (sustate_get() == SUSTATE_SUSPENDING)
            {
                if (nrf_drv_usbd_suspend())
                {
                    sustate_set(SUSTATE_SUSPEND);
                }
            }
            if (nrf_drv_usbd_wakeup_req())
            {
                sustate_set(SUSTATE_WAKINGUP_WAITING_HFCLK_WREQ);
            }
            break;
        }

        case APP_USBD_EVT_DRV_SOF:
        {
#if (APP_USBD_PROVIDE_SOF_TIMESTAMP) || defined(__SDK_DOXYGEN__)
            m_last_frame = p_event->drv_evt.data.sof.framecnt;
#endif
            /* Wake up if suspended */
            if ((sustate_get() == SUSTATE_SUSPENDING) || (sustate_get() == SUSTATE_WAKINGUP_WAITING_WREQ))
            {
                static const app_usbd_evt_t evt_data = {
                    .type = APP_USBD_EVT_DRV_RESUME
                };
                app_usbd_event_execute((app_usbd_internal_evt_t *)&evt_data);
            }
            
            user_event_state_proc(APP_USBD_EVT_DRV_SOF);

            app_usbd_class_inst_t const * p_inst = app_usbd_class_sof_first_get();
            while (NULL != p_inst)
            {
                ret_code_t r = class_event_handler(p_inst, (app_usbd_complex_evt_t const *)p_event);
                UNUSED_VARIABLE(r);
                p_inst = app_usbd_class_sof_next_get(p_inst);
            }
            break;
        }

        case APP_USBD_EVT_DRV_RESET:
        {
            app_usbd_all_iface_deselect();
            app_usbd_core_ep0_enable();
            sustate_set(SUSTATE_ACTIVE);
            user_event_state_proc(APP_USBD_EVT_DRV_RESET);
            /* Processing core interface (connected only to EP0) and then all instances from the list */
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call(p_event));
            app_usbd_all_call((app_usbd_complex_evt_t const *)p_event);
            break;
        }
        case APP_USBD_EVT_DRV_RESUME:
        {
            if (sustate_get() == SUSTATE_WAKINGUP_WAITING_WREQ)
            {
                sustate_set(SUSTATE_ACTIVE);
                nrf_drv_usbd_force_bus_wakeup();
            }
            else
            {
                sustate_set(SUSTATE_RESUMING);
            }
            user_event_state_proc(APP_USBD_EVT_DRV_RESUME);
            /* Processing core interface (connected only to EP0) and then all instances from the list */
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call(p_event));
            app_usbd_all_call((app_usbd_complex_evt_t const *)p_event);
            break;
        }
        case APP_USBD_EVT_DRV_WUREQ:
        {
            static const app_usbd_evt_t evt_data = {
                .type = APP_USBD_EVT_DRV_RESUME
            };
            user_event_state_proc(APP_USBD_EVT_DRV_RESUME);
            /* Processing core interface (connected only to EP0) and then all instances from the list */
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call((app_usbd_internal_evt_t const *)&evt_data));
            app_usbd_all_call((app_usbd_complex_evt_t const *)&evt_data);

            switch(sustate_get())
            {
                case SUSTATE_WAKINGUP_WAITING_HFCLK_WREQ:
                    sustate_set(SUSTATE_WAKINGUP_WAITING_HFCLK);
                    break;
                case SUSTATE_WAKINGUP_WAITING_WREQ:
                    sustate_set(SUSTATE_ACTIVE);
                    break;
                default:
                {
                    /* This should not happen - but try to recover by setting directly active state */
                    NRF_LOG_WARNING("Unexpected state on WUREQ event (%u)", sustate_get());
                    sustate_set(SUSTATE_ACTIVE);
                }
            }
            break;
        }
        case APP_USBD_EVT_DRV_SUSPEND:
        {
            sustate_set(SUSTATE_SUSPENDING);

            user_event_state_proc(APP_USBD_EVT_DRV_SUSPEND);

            /* Processing all instances from the list and then core interface (connected only to EP0) */
            app_usbd_all_call((app_usbd_complex_evt_t const *)p_event);
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call(p_event));
            break;
        }

        case APP_USBD_EVT_STATE_CHANGED:
        {
            user_event_state_proc(APP_USBD_EVT_STATE_CHANGED);
            /* Processing all instances from the list and then core interface (connected only to EP0) */
            app_usbd_all_call((app_usbd_complex_evt_t const *)p_event);
            break;
        }

        case APP_USBD_EVT_DRV_SETUP:
        {
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call(p_event));
            break;
        }

        case APP_USBD_EVT_SETUP_SETADDRESS:
        {
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call(p_event));
            break;
        }

        case APP_USBD_EVT_DRV_EPTRANSFER:
        {
            app_usbd_ep_conf_t const * p_ep_conf =
                    app_usbd_ep_conf_access(p_event->drv_evt.data.eptransfer.ep);
            ASSERT(NULL != p_ep_conf->p_cinst);
            ASSERT(NULL != p_ep_conf->event_handler);

            if (NRF_SUCCESS != p_ep_conf->event_handler(p_ep_conf->p_cinst,
                                                       (app_usbd_complex_evt_t const *)p_event))
            {
                /* If error returned, every bulk/interrupt endpoint would be stalled */
                if (!(0 == NRF_USBD_EP_NR_GET(p_event->drv_evt.data.eptransfer.ep) ||
                   NRF_USBD_EPISO_CHECK(p_event->drv_evt.data.eptransfer.ep)))
                {
                    nrf_drv_usbd_ep_stall(p_event->drv_evt.data.eptransfer.ep);
                }
            }
            break;
        }
#if APP_USBD_CONFIG_POWER_EVENTS_PROCESS
        case APP_USBD_EVT_POWER_DETECTED:
        {
            user_event_state_proc(APP_USBD_EVT_POWER_DETECTED);
            app_usbd_all_call((app_usbd_complex_evt_t const *)p_event);
            break;
        }
        case APP_USBD_EVT_POWER_REMOVED:
        {
            user_event_state_proc(APP_USBD_EVT_POWER_REMOVED);
            app_usbd_all_call((app_usbd_complex_evt_t const *)p_event);
            break;
        }
        case APP_USBD_EVT_POWER_READY:
        {
            user_event_state_proc(APP_USBD_EVT_POWER_READY);
            app_usbd_all_call((app_usbd_complex_evt_t const *)p_event);
            break;
        }
#endif
        default:
            ASSERT(0);
            break;
    }
}


#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE) || defined(__SDK_DOXYGEN__)
bool app_usbd_event_queue_process(void)
{
#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
    app_usbd_internal_evt_t sof_event = {
        .app_evt.type = APP_USBD_EVT_DRV_SOF
    };
#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
    static nrf_atfifo_item_get_t cx;
    static app_usbd_internal_queue_evt_t * p_event_item = NULL;
    if (NULL == p_event_item)
    {
        p_event_item = nrf_atfifo_item_get(m_event_queue, &cx);
    }

    if (NULL != p_event_item)
    {

#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
        if (p_event_item->sof_cnt > 0)
        {
            if (p_event_item->start_frame > USBD_FRAMECNTR_FRAMECNTR_Msk)
            {
                p_event_item->start_frame = 0;
            }
            sof_event.drv_evt.data.sof.framecnt = (p_event_item->start_frame)++;
            --(p_event_item->sof_cnt);
            app_usbd_event_execute(&sof_event);
            return true;
        }
#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)

        app_usbd_event_execute(&(p_event_item->evt));
        UNUSED_RETURN_VALUE(nrf_atfifo_item_free(m_event_queue, &cx));
        p_event_item = NULL;
        return true;
    }
#if (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
    else if (m_sof_events_cnt > 0)
    {
        CRITICAL_REGION_ENTER();
        if (m_event_frame > USBD_FRAMECNTR_FRAMECNTR_Msk)
        {
            m_event_frame = 0;
        }
        sof_event.drv_evt.data.sof.framecnt = m_event_frame++;
        UNUSED_RETURN_VALUE(nrf_atomic_u32_sub_hs(&m_sof_events_cnt, 1));
        CRITICAL_REGION_EXIT();
        app_usbd_event_execute(&sof_event);
        return true;
    }
#endif // (APP_USBD_CONFIG_SOF_HANDLING_MODE == APP_USBD_SOF_HANDLING_COMPRESS_QUEUE)
    else
    {
        return false;
    }
}
#endif


ret_code_t app_usbd_class_append(app_usbd_class_inst_t const * p_cinst)
{
    ASSERT(NULL != p_cinst);
    ASSERT(NULL != p_cinst->p_class_methods);
    ASSERT(NULL != p_cinst->p_class_methods->event_handler);
    ASSERT(NULL == app_usbd_class_data_access(p_cinst)->p_next);

    /* This should be only called if USBD is disabled
     * We simply assume that USBD is enabled if its interrupts are */
    ASSERT(!nrf_drv_usbd_is_enabled() && nrf_drv_usbd_is_initialized());

    /* Check if all required endpoints are available
     * Checking is splitted from setting to avoid situation that anything
     * is modified and then operation finishes with error */
    uint8_t iface_idx;
    app_usbd_class_iface_conf_t const * p_iface;

    iface_idx = 0;
    while (NULL != (p_iface = app_usbd_class_iface_get(p_cinst, iface_idx++)))
    {
        uint8_t ep_idx = 0;
        app_usbd_class_ep_conf_t const * p_ep;
        while (NULL != (p_ep = app_usbd_class_iface_ep_get(p_iface, ep_idx++)))
        {
            if (NULL != app_usbd_ep_instance_get(app_usbd_class_ep_address_get(p_ep)))
            {
                return NRF_ERROR_BUSY;
            }
        }
    }

    /* Connecting all required endpoints */
    iface_idx = 0;
    while (NULL != (p_iface = app_usbd_class_iface_get(p_cinst, iface_idx++)))
    {
        uint8_t ep_idx = 0;
        app_usbd_class_ep_conf_t const * p_ep;
        while (NULL != (p_ep = app_usbd_class_iface_ep_get(p_iface, ep_idx++)))
        {
            app_usbd_ep_instance_set(app_usbd_class_ep_address_get(p_ep), p_cinst);
        }
    }

    /* Adding pointer to this instance to the end of the chain */
    app_usbd_class_inst_t const * * pp_last = &m_p_first_cinst;
    while (NULL != (*pp_last))
    {
        ASSERT((*pp_last) != p_cinst);
        pp_last = &(app_usbd_class_data_access(*pp_last)->p_next);
    }
    (*pp_last) = p_cinst;

    /* Successfully attached */
    const app_usbd_evt_t evt_data = {.type = APP_USBD_EVT_INST_APPEND };
    return class_event_handler(p_cinst, (app_usbd_complex_evt_t const *)(&evt_data));
}


ret_code_t app_usbd_class_remove(app_usbd_class_inst_t const * p_cinst)
{
    ASSERT(NULL != p_cinst);
    ASSERT(NULL != p_cinst->p_class_methods);
    ASSERT(NULL != p_cinst->p_class_methods->event_handler);
    /* This function should be only called if USBD is disabled */
    ASSERT(!nrf_drv_usbd_is_enabled() && nrf_drv_usbd_is_initialized());
    ret_code_t ret;
    /* Remove this class from the chain */
    app_usbd_class_inst_t const * * pp_last = &m_p_first_cinst;
    while (NULL != (*pp_last))
    {
        if ((*pp_last) == p_cinst)
        {
            /* Inform class instance that removing process is going to be started */
            const app_usbd_evt_t evt_data = {
                .type = APP_USBD_EVT_INST_REMOVE
            };
            ret = class_event_handler(p_cinst, (app_usbd_complex_evt_t const *)(&evt_data));
            if (ret != NRF_SUCCESS)
            {
                return ret;
            }

            /* Breaking chain */
            (*pp_last) = (app_usbd_class_data_access(p_cinst)->p_next);
            app_usbd_class_data_access(p_cinst)->p_next = NULL;

            /* Disconnecting endpoints */
            uint8_t ep_idx;
            for (ep_idx = 0; ep_idx < NRF_USBD_EPIN_CNT; ++ep_idx)
            {
                nrf_drv_usbd_ep_t ep = NRF_DRV_USBD_EPIN(ep_idx);
                if (app_usbd_ep_instance_get(ep) == p_cinst)
                {
                    app_usbd_ep_instance_set(ep, NULL);
                }
            }
            for (ep_idx = 0; ep_idx < NRF_USBD_EPOUT_CNT; ++ep_idx)
            {
                nrf_drv_usbd_ep_t ep = NRF_DRV_USBD_EPOUT(ep_idx);
                if (app_usbd_ep_instance_get(ep) == p_cinst)
                {
                    app_usbd_ep_instance_set(ep, NULL);
                }
            }

            return NRF_SUCCESS;
        }
        pp_last = &(app_usbd_class_data_access(*pp_last)->p_next);
    }

    return NRF_ERROR_NOT_FOUND;
}


ret_code_t app_usbd_class_remove_all(void)
{
    ret_code_t ret = NRF_SUCCESS;
    while (NULL != m_p_first_cinst)
    {
        ret = app_usbd_class_remove(m_p_first_cinst);
        if (ret != NRF_SUCCESS)
        {
            break;
        }
    }

    return ret;
}


ret_code_t app_usbd_ep_handler_set(app_usbd_class_inst_t const * const p_cinst,
                                   nrf_drv_usbd_ep_t ep,
                                   app_usbd_ep_event_handler_t handler)
{
    ASSERT(NULL != p_cinst);
    ASSERT(NULL != handler);
    /* This function should be only called if USBD is disabled */
    ASSERT(!nrf_drv_usbd_is_enabled() && nrf_drv_usbd_is_initialized());

    if (p_cinst != app_usbd_ep_instance_get(ep))
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    (app_usbd_ep_conf_access(ep))->event_handler = handler;
    return NRF_SUCCESS;
}


ret_code_t app_usbd_class_sof_register(app_usbd_class_inst_t const * p_cinst)
{
    ASSERT(NULL != p_cinst);
    ASSERT(NULL != p_cinst->p_class_methods);
    ASSERT(NULL != p_cinst->p_class_methods->event_handler);
    /* This function should be only called if USBD is disabled */
    ASSERT(!nrf_drv_usbd_is_enabled() && nrf_drv_usbd_is_initialized());

    /* Make sure it's not in interrupt SOF list */
    app_usbd_class_inst_t const * * pp_last = &m_p_first_sof_interrupt_cinst;
    while (NULL != (*pp_last))
    {
       ASSERT((*pp_last) != p_cinst);
       pp_last = &(app_usbd_class_data_access(*pp_last)->p_sof_next);
    }

    /* Next SOF event requiring instance has to be NULL now  */
    ASSERT(NULL == (app_usbd_class_data_access(p_cinst)->p_sof_next));

    /* Adding pointer to this instance to the end of the chain */
    pp_last = &m_p_first_sof_cinst;
    while (NULL != (*pp_last))
    {
       ASSERT((*pp_last) != p_cinst);
       pp_last = &(app_usbd_class_data_access(*pp_last)->p_sof_next);
    }
    (*pp_last) = p_cinst;

    return NRF_SUCCESS;
}


ret_code_t app_usbd_class_sof_unregister(app_usbd_class_inst_t const * p_cinst)
{
    ASSERT(NULL != p_cinst);
    /** This function should be only called if USBD is disabled */
    ASSERT(!nrf_drv_usbd_is_enabled() && nrf_drv_usbd_is_initialized());

    app_usbd_class_inst_t const * * pp_last = &m_p_first_sof_cinst;
    while (NULL != (*pp_last))
    {
        if ((*pp_last) == p_cinst)
        {
            /* Breaking chain */
            (*pp_last) = (app_usbd_class_data_access(p_cinst)->p_sof_next);
            app_usbd_class_data_access(p_cinst)->p_sof_next = NULL;

            return NRF_SUCCESS;
        }
        pp_last = &(app_usbd_class_data_access(*pp_last)->p_sof_next);
    }
    return NRF_ERROR_NOT_FOUND;
}

ret_code_t app_usbd_class_sof_interrupt_register(app_usbd_class_inst_t const * p_cinst, app_usbd_sof_interrupt_handler_t handler)
{
    ASSERT(NULL != p_cinst);
    ASSERT(NULL != p_cinst->p_class_methods);
    ASSERT(NULL != handler);
    /* This function should be only called if USBD is disabled */
    ASSERT(!nrf_drv_usbd_is_enabled() && nrf_drv_usbd_is_initialized());

    /* Next SOF event requiring instance has to be NULL now  */
    ASSERT(NULL == (app_usbd_class_data_access(p_cinst)->p_sof_next));
    
    app_usbd_class_data_access(p_cinst)->sof_handler = handler;
    
    /* Make sure it's not in normal SOF list */
    app_usbd_class_inst_t const * * pp_last = &m_p_first_sof_cinst;
    while (NULL != (*pp_last))
    {
       ASSERT((*pp_last) != p_cinst);
       pp_last = &(app_usbd_class_data_access(*pp_last)->p_sof_next);
    }

    /* Adding pointer to this instance to the end of the interrupt chain */
    pp_last = &m_p_first_sof_interrupt_cinst;
    while (NULL != (*pp_last))
    {

       ASSERT((*pp_last) != p_cinst);
       pp_last = &(app_usbd_class_data_access(*pp_last)->p_sof_next);
    }
    (*pp_last) = p_cinst;

    return NRF_SUCCESS;
}


ret_code_t app_usbd_class_sof_interrupt_unregister(app_usbd_class_inst_t const * p_cinst)
{
    ASSERT(NULL != p_cinst);
    /** This function should be only called if USBD is disabled */
    ASSERT(!nrf_drv_usbd_is_enabled() && nrf_drv_usbd_is_initialized());

    app_usbd_class_inst_t const * * pp_last = &m_p_first_sof_interrupt_cinst;
    while (NULL != (*pp_last))
    {
        if ((*pp_last) == p_cinst)
        {
            /* Breaking chain */
            (*pp_last) = (app_usbd_class_data_access(p_cinst)->p_sof_next);
            app_usbd_class_data_access(p_cinst)->p_sof_next = NULL;

            return NRF_SUCCESS;
        }
        pp_last = &(app_usbd_class_data_access(*pp_last)->p_sof_next);
    }
    return NRF_ERROR_NOT_FOUND;
}

ret_code_t app_usbd_class_rwu_register(app_usbd_class_inst_t const * const p_inst)
{
    ASSERT(p_inst != NULL);
    ++m_rwu_registered_counter;
    /*Overflow check*/
    ASSERT(m_rwu_registered_counter != 0);

    return NRF_SUCCESS;
}


ret_code_t app_usbd_class_rwu_unregister(app_usbd_class_inst_t const * const p_inst)
{
    ASSERT(p_inst != NULL);
    /* Usage validation. If counter is 0 unregister is not possible.*/
    ASSERT(m_rwu_registered_counter != 0);
    --m_rwu_registered_counter;

    return NRF_SUCCESS;
}

bool app_usbd_class_rwu_enabled_check(void)
{
    return (m_rwu_registered_counter != 0);
}

ret_code_t app_usbd_interface_ep_reset(app_usbd_class_inst_t const * const p_cinst,
                                       uint8_t iface)
{
    uint8_t iface_count = app_usbd_class_iface_count_get(p_cinst);

    app_usbd_class_iface_conf_t const * p_iface = NULL;
    for (uint8_t j = 0; j < iface_count; ++j)
    {
        p_iface = app_usbd_class_iface_get(p_cinst, j);
        if (app_usbd_class_iface_number_get(p_iface) == iface)
        {
            break;
        }
    }

    if (p_iface == NULL)
    {
        return NRF_ERROR_NOT_SUPPORTED;
    }

    uint8_t ep_count = app_usbd_class_iface_ep_count_get(p_iface);

    for (uint8_t j = 0; j < ep_count; ++j)
    {
        /*Clear stall for every endpoint*/
        app_usbd_class_ep_conf_t const * p_ep = app_usbd_class_iface_ep_get(p_iface, j);

        if (!NRF_USBD_EPISO_CHECK(p_ep->address))
        {
            nrf_drv_usbd_ep_dtoggle_clear(p_ep->address);
            nrf_drv_usbd_ep_stall_clear(p_ep->address);
        }

    }

    return NRF_SUCCESS;
}

void app_usbd_ep_enable(nrf_drv_usbd_ep_t ep)
{
    if (!NRF_USBD_EPISO_CHECK(ep))
    {
        nrf_drv_usbd_ep_dtoggle_clear(ep);
        nrf_drv_usbd_ep_stall_clear(ep);
    }
    nrf_drv_usbd_ep_enable(ep);
}

void app_usbd_ep_disable(nrf_drv_usbd_ep_t ep)
{
    nrf_drv_usbd_ep_disable(ep);
}


app_usbd_class_inst_t const * app_usbd_class_first_get(void)
{
    return m_p_first_cinst;
}

app_usbd_class_inst_t const * app_usbd_class_sof_first_get(void)
{
    return m_p_first_sof_cinst;
}

app_usbd_class_inst_t const * app_usbd_class_sof_interrupt_first_get(void)
{
    return m_p_first_sof_interrupt_cinst;
}

app_usbd_class_inst_t const * app_usbd_iface_find(uint8_t iface, uint8_t * p_iface_idx)
{
    app_usbd_class_inst_t const * p_inst = app_usbd_class_first_get();
    while (p_inst != NULL)
    {
        uint8_t iface_count = app_usbd_class_iface_count_get(p_inst);
        /* Iterate over interfaces */
        for (uint8_t i = 0; i < iface_count; ++i)
        {
            app_usbd_class_iface_conf_t const * p_iface;
            p_iface = app_usbd_class_iface_get(p_inst, i);
            if (app_usbd_class_iface_number_get(p_iface) == iface)
            {
                if (p_iface_idx != NULL)
                {
                    (*p_iface_idx) = i;
                }
                return p_inst;
            }
        }
        p_inst = app_usbd_class_next_get(p_inst);
    }
    return NULL;
}

ret_code_t app_usbd_iface_call(
    app_usbd_class_inst_t const * const p_class_inst,
    uint8_t iface_idx,
    app_usbd_complex_evt_t const * const p_event)
{
    UNUSED_PARAMETER(iface_idx);
    return class_event_handler(p_class_inst, p_event);
}

ret_code_t app_usbd_ep_call(nrf_drv_usbd_ep_t ep, app_usbd_complex_evt_t const * const p_event)
{
    if (NRF_USBD_EP_VALIDATE(ep))
    {
        app_usbd_class_inst_t const * p_inst = app_usbd_ep_conf_access(ep)->p_cinst;
        if (p_inst != NULL)
        {
           return class_event_handler(p_inst, p_event);
        }
    }
    return NRF_ERROR_INVALID_ADDR;
}

void app_usbd_all_call(app_usbd_complex_evt_t const * const p_event)
{
    app_usbd_class_inst_t const * p_inst;
    for (p_inst = app_usbd_class_first_get(); NULL != p_inst;
         p_inst = app_usbd_class_next_get(p_inst))
    {
        UNUSED_RETURN_VALUE(class_event_handler(p_inst, p_event));
    }
}

ret_code_t app_usbd_all_until_served_call(app_usbd_complex_evt_t const * const p_event)
{
    app_usbd_class_inst_t const * p_inst;
    ret_code_t ret = NRF_ERROR_NOT_SUPPORTED;
    /* Try to process via every instance */
    for (p_inst = app_usbd_class_first_get(); NULL != p_inst;
         p_inst = app_usbd_class_next_get(p_inst))
    {

        ret = class_event_handler(p_inst, p_event);
        if (NRF_ERROR_NOT_SUPPORTED != ret)
        {
            /* Processing finished */
            break;
        }
    }

    return ret;
}

ret_code_t app_usbd_ep_transfer(
    nrf_drv_usbd_ep_t                     ep,
    nrf_drv_usbd_transfer_t const * const p_transfer)
{
    if (!nrf_drv_usbd_ep_enable_check(ep))
    {
        return NRF_ERROR_INVALID_STATE;
    }
    if (m_sustate != SUSTATE_ACTIVE)
    {
        return NRF_ERROR_INVALID_STATE;
    }
    return nrf_drv_usbd_ep_transfer(ep, p_transfer);
}

ret_code_t app_usbd_ep_handled_transfer(
    nrf_drv_usbd_ep_t                         ep,
    nrf_drv_usbd_handler_desc_t const * const p_handler)
{
    if (!nrf_drv_usbd_ep_enable_check(ep))
    {
        return NRF_ERROR_INVALID_STATE;
    }
    if (m_sustate != SUSTATE_ACTIVE)
    {
        return NRF_ERROR_INVALID_STATE;
    }
    return nrf_drv_usbd_ep_handled_transfer(ep, p_handler);
}

ret_code_t app_usbd_iface_select(
    app_usbd_class_inst_t const * const p_inst,
    uint8_t                             iface_idx,
    uint8_t                             alternate)
{
    ret_code_t ret = NRF_ERROR_NOT_SUPPORTED;

    if (p_inst->p_class_methods->iface_select != NULL)
    {
        ret = p_inst->p_class_methods->iface_select(p_inst, iface_idx, alternate);
    }

    if(ret == NRF_ERROR_NOT_SUPPORTED)
    {
        ret = default_iface_select(p_inst, iface_idx, alternate);
    }
    return ret;
}

void app_usbd_iface_deselect(
    app_usbd_class_inst_t const * const p_inst,
    uint8_t                             iface_idx)
{
    if (p_inst->p_class_methods->iface_deselect != NULL)
    {
        p_inst->p_class_methods->iface_deselect(p_inst, iface_idx);
    }
    default_iface_deselect(p_inst, iface_idx);
}

uint8_t app_usbd_iface_selection_get(
    app_usbd_class_inst_t const * const p_inst,
    uint8_t                             iface_idx)
{
    uint8_t alt = 0;
    if (p_inst->p_class_methods->iface_selection_get != NULL)
    {
        alt = p_inst->p_class_methods->iface_selection_get(p_inst, iface_idx);
    }
    return alt;
}

void app_usbd_all_iface_select_0(void)
{
    app_usbd_class_inst_t const * p_inst = app_usbd_class_first_get();
    while (p_inst != NULL)
    {
        uint8_t iface_count = app_usbd_class_iface_count_get(p_inst);
        for (uint8_t i = 0; i < iface_count; ++i)
        {
            ret_code_t ret;
            ret = app_usbd_iface_select(p_inst, i, 0);
            ASSERT(ret == NRF_SUCCESS);
            UNUSED_VARIABLE(ret);
        }
        p_inst = app_usbd_class_next_get(p_inst);
    }
}

void app_usbd_all_iface_deselect(void)
{
    app_usbd_class_inst_t const * p_inst = app_usbd_class_first_get();
    while (p_inst != NULL)
    {
        uint8_t iface_count = app_usbd_class_iface_count_get(p_inst);
        for (uint8_t i = 0; i < iface_count; ++i)
        {
            app_usbd_iface_deselect(p_inst, i);
        }
        p_inst = app_usbd_class_next_get(p_inst);
    }
}

#endif //NRF_MODULE_ENABLED(APP_USBD)
