/** | |
* Copyright (c) 2016 - 2018, 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) |