blob: 23a8c4bc56df9379858b381cc4e2f4cbdf2b4b74 [file] [log] [blame]
/**
* Copyright (c) 2016 - 2019, 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)