/**
 * 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 APP_USBD_ENABLED
#include "sdk_common.h"
#include "app_usbd.h"
#include "app_usbd_core.h"

/* Base variables tests */

/* Check event of app_usbd_event_type_t enumerator */
STATIC_ASSERT((int32_t)APP_USBD_EVT_FIRST_APP == (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.
 * @{
 */

/**
 * @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 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
 *
 * @todo RK Implement and documentation
 */
static app_usbd_class_inst_t const * m_p_first_sof_cinst;

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

/**
 * @brief Class interface call: get descriptors
 *
 * @ref app_usbd_class_interface_t::get_descriptors
 *
 * @param[in]  p_inst Class instance
 * @param[out] p_size Descriptors size
 *
 * @return Class descriptors start address
 * */
static inline const void * class_get_descriptors(app_usbd_class_inst_t const * const p_cinst,
                                                 size_t * p_size)
{
    ASSERT(p_cinst != NULL);
    ASSERT(p_cinst->p_class_methods != NULL);
    ASSERT(p_cinst->p_class_methods->get_descriptors != NULL);
    return p_cinst->p_class_methods->get_descriptors(p_cinst, p_size);
}

const void * app_usbd_class_descriptor_find(app_usbd_class_inst_t const * const p_cinst,
                                            uint8_t  desc_type,
                                            uint8_t  desc_index,
                                            size_t * p_desc_len)
{
    app_usbd_descriptor_header_t const * p_header;
    uint8_t const * p_raw = class_get_descriptors(p_cinst, p_desc_len);
    if (p_raw == NULL)
    {
        return NULL;
    }

    size_t pos = 0;
    uint8_t index = 0;
    while (pos < *p_desc_len)
    {
        p_header = (app_usbd_descriptor_header_t const *)(p_raw + pos);
        if (p_header->bDescriptorType == desc_type)
        {
            if (desc_index == index)
            {
                *p_desc_len = p_header->bLength;
                return p_header;
            }

            index++;
        }

        pos += p_header->bLength;
    }

    return NULL;
}

/**
 * @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(nrf_drv_usbd_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 USBD event handler
 *
 *
 */
static void app_usbd_event_handler(nrf_drv_usbd_evt_t const * const p_event)
{
    ASSERT(NULL != m_p_first_cinst);

    /* Note - there should never be situation that event is generated on disconnected endpoint */
    switch(p_event->type)
    {
        case NRF_DRV_USBD_EVT_SOF:
        {
            app_usbd_class_inst_t const * p_inst = app_usbd_class_sof_first_get();
            ASSERT(NULL != p_inst); /* This should not happen - when no SOF instances are in the list, SOF event is disabled */

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

        /* Reset and */
        case NRF_DRV_USBD_EVT_RESET:
        case NRF_DRV_USBD_EVT_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 NRF_DRV_USBD_EVT_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 NRF_DRV_USBD_EVT_SETUP:
        {
            UNUSED_RETURN_VALUE(app_usbd_core_handler_call(p_event));
            break;
        }

        case NRF_DRV_USBD_EVT_EPTRANSFER:
        {
            app_usbd_ep_conf_t const * p_ep_conf =
                    app_usbd_ep_conf_access(p_event->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->data.eptransfer.ep) ||
                   NRF_USBD_EPISO_CHECK(p_event->data.eptransfer.ep)))
                {
                    nrf_drv_usbd_ep_stall(p_event->data.eptransfer.ep);
                }
            }
            break;
        }

        default:
            ASSERT(0);
            break;
    }
}

/** @} */


ret_code_t app_usbd_init(void)
{
    ret_code_t ret;

    ret = nrf_drv_usbd_init(app_usbd_event_handler);
    if (NRF_SUCCESS != ret)
    {
        return ret;
    }

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

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

    return class_event_handler(p_inst, (app_usbd_complex_evt_t const *)(&evt_data));
}


ret_code_t app_usbd_uninit(void)
{
    ret_code_t ret;

    ret = nrf_drv_usbd_uninit();
    if(NRF_SUCCESS != ret)
        return ret;

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

    /* Clear all endpoints configurations */
    memset(m_epin_conf , 0, sizeof(m_epin_conf ));
    memset(m_epout_conf, 0, sizeof(m_epout_conf));

    return ret;
}


void app_usbd_enable(void)
{
    nrf_drv_usbd_enable();
    while (!app_usbd_core_power_regulator_is_ready())
        ;
}


void app_usbd_disable(void)
{
    if (nrf_drv_usbd_is_started())
    {
        app_usbd_stop();
    }
    nrf_drv_usbd_disable();
}


void app_usbd_start(void)
{
    const app_usbd_evt_t evt_data = {
            .type = APP_USBD_EVT_START
    };

    /* Enable all connected endpoints */
    uint8_t n;
    for (n = 1; n < ARRAY_SIZE(m_epin_conf); ++n)
    {
        if (NULL != m_epin_conf[n].p_cinst)
        {
            nrf_drv_usbd_ep_enable(NRF_DRV_USBD_EPIN(n));
        }
    }
    for (n = 1; n < ARRAY_SIZE(m_epout_conf); ++n)
    {
        if (NULL != m_epout_conf[n].p_cinst)
        {
            nrf_drv_usbd_ep_enable(NRF_DRV_USBD_EPOUT(n));
        }
    }

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

    nrf_drv_usbd_start(NULL != m_p_first_sof_cinst);
}


void app_usbd_stop(void)
{
    const app_usbd_evt_t evt_data = {
            .type = APP_USBD_EVT_STOP
    };

    nrf_drv_usbd_stop();

    /* 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((nrf_drv_usbd_evt_t const *)&evt_data));
}


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

    /* Next SOF event requiring instance have 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 */
    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);
    }
    (*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_interface_std_req_handle(app_usbd_class_inst_t const * p_cinst,
                                             app_usbd_setup_evt_t  const * p_setup_ev)
{
    switch (p_setup_ev->setup.bmRequest)
    {
        case APP_USBD_SETUP_STDREQ_GET_STATUS:
        {
            size_t tx_size;
            uint16_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size);

            p_tx_buff[0] = 0;
            return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint16_t));
        }
        default:
            break;
    }

    return NRF_ERROR_NOT_SUPPORTED;
}

ret_code_t app_usbd_endpoint_std_req_handle(app_usbd_class_inst_t const * p_cinst,
                                            app_usbd_setup_evt_t  const * p_setup_ev)
{
    size_t i;
    nrf_drv_usbd_ep_t ep_addr = NRF_DRV_USBD_EPIN0;
    for (i = 0; i < app_usbd_class_iface_count_get(p_cinst); ++i)
    {
        app_usbd_class_iface_conf_t const * p_iface_conf = app_usbd_class_iface_get(p_cinst, i);
        const uint8_t ep_count = app_usbd_class_iface_ep_count_get(p_iface_conf);

        ep_addr = (nrf_drv_usbd_ep_t)(p_setup_ev->setup.wIndex.lb);
        uint8_t ep_idx = app_usbd_class_iface_ep_idx_get(p_iface_conf, ep_addr);
        if (ep_idx == ep_count)
        {
            continue;
        }

        break;
    }

    if (i == app_usbd_class_iface_count_get(p_cinst))
    {
        return NRF_ERROR_NOT_SUPPORTED;
    }

    switch (p_setup_ev->setup.bmRequest)
    {
        case APP_USBD_SETUP_STDREQ_GET_STATUS:
        {
            size_t tx_size;
            uint16_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size);

            p_tx_buff[0] = nrf_drv_usbd_ep_stall_check(ep_addr) ? 1 : 0;
            return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint16_t));
        }
        case APP_USBD_SETUP_STDREQ_SET_FEATURE:
        {
            if (p_setup_ev->setup.wValue.w != APP_USBD_SETUP_STDFEATURE_ENDPOINT_HALT)
             {
                 return NRF_ERROR_NOT_SUPPORTED;
             }

            nrf_drv_usbd_ep_stall(ep_addr);
            return NRF_SUCCESS;
        }
        case APP_USBD_SETUP_STDREQ_CLEAR_FEATURE:
        {
            if (p_setup_ev->setup.wValue.w != APP_USBD_SETUP_STDFEATURE_ENDPOINT_HALT)
            {
                return NRF_ERROR_NOT_SUPPORTED;
            }

            if (nrf_usbd_dtoggle_get(ep_addr) != NRF_USBD_DTOGGLE_DATA0)
            {
                nrf_usbd_dtoggle_set(ep_addr, NRF_USBD_DTOGGLE_DATA0);
            }

            if (NRF_USBD_EPISO_CHECK(ep_addr) == 0)
            {
                nrf_drv_usbd_ep_stall_clear(ep_addr);
            }

            return NRF_SUCCESS;
        }
        default:
            return NRF_ERROR_NOT_SUPPORTED;
    }
}

ret_code_t app_usbd_req_std_set_interface(app_usbd_class_inst_t const * const p_cinst,
                                          app_usbd_setup_evt_t  const * const p_setup_ev)
{
    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 (p_iface->number == p_setup_ev->setup.wIndex.w)
        {
            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_dtoggle_get(p_ep->address) != NRF_USBD_DTOGGLE_DATA0)
        {
            nrf_usbd_dtoggle_set(p_ep->address, NRF_USBD_DTOGGLE_DATA0);
        }

        if (NRF_USBD_EPISO_CHECK(p_ep->address) == 0)
        {
            nrf_drv_usbd_ep_stall_clear(p_ep->address);
        }

    }

    return NRF_SUCCESS;
}

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

ret_code_t app_usbd_iface_call(uint8_t iface, app_usbd_complex_evt_t const * const p_event)
{
    ASSERT(NULL != m_p_first_cinst);
    /*Iterate over classes*/
    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)
            {
                return class_event_handler(p_inst, p_event);
            }
        }
        p_inst = app_usbd_class_next_get(p_inst);
    }

    return NRF_ERROR_INVALID_ADDR;
}

ret_code_t app_usbd_ep_call(nrf_drv_usbd_ep_t ep, app_usbd_complex_evt_t const * const p_event)
{
    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;
}

#endif // APP_USBD_ENABLED
