| /* |
| * Copyright (c) 2016 - 2023, Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* This file is undergoing transition towards native Zephyr nrf USB driver. */ |
| |
| /** @cond INTERNAL_HIDDEN */ |
| |
| #include <nrfx.h> |
| |
| #include "nrf_usbd_common.h" |
| #include "nrf_usbd_common_errata.h" |
| #include <string.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(nrf_usbd_common, CONFIG_NRF_USBD_COMMON_LOG_LEVEL); |
| |
| #define NRF_USBD_COMMON_EPIN_CNT 9 |
| #define NRF_USBD_COMMON_EPOUT_CNT 9 |
| #define NRF_USBD_COMMON_EP_NUM(ep) (ep & 0xF) |
| #define NRF_USBD_COMMON_EP_IS_IN(ep) ((ep & 0x80) == 0x80) |
| #define NRF_USBD_COMMON_EP_IS_OUT(ep) ((ep & 0x80) == 0) |
| #define NRF_USBD_COMMON_EP_IS_ISO(ep) ((ep & 0xF) >= 8) |
| |
| #ifndef NRF_USBD_COMMON_ISO_DEBUG |
| /* Also generate information about ISOCHRONOUS events and transfers. |
| * Turn this off if no ISOCHRONOUS transfers are going to be debugged and this |
| * option generates a lot of useless messages. |
| */ |
| #define NRF_USBD_COMMON_ISO_DEBUG 1 |
| #endif |
| |
| #ifndef NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG |
| /* Also generate debug information for failed transfers. |
| * It might be useful but may generate a lot of useless debug messages |
| * in some library usages (for example when transfer is generated and the |
| * result is used to check whatever endpoint was busy. |
| */ |
| #define NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG 1 |
| #endif |
| |
| #ifndef NRF_USBD_COMMON_DMAREQ_PROCESS_DEBUG |
| /* Generate additional messages that mark the status inside |
| * @ref usbd_dmareq_process. |
| * It is useful to debug library internals but may generate a lot of |
| * useless debug messages. |
| */ |
| #define NRF_USBD_COMMON_DMAREQ_PROCESS_DEBUG 1 |
| #endif |
| |
| #ifndef NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211 |
| /* Anomaly 211 - Device remains in SUSPEND too long when host resumes |
| * a bus activity (sending SOF packets) without a RESUME condition. |
| */ |
| #define NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211 0 |
| #endif |
| |
| /** |
| * @defgroup nrf_usbd_common_int USB Device driver internal part |
| * @internal |
| * @ingroup nrf_usbd_common |
| * |
| * This part contains auxiliary internal macros, variables and functions. |
| * @{ |
| */ |
| |
| /** |
| * @brief Assert endpoint number validity. |
| * |
| * Internal macro to be used during program creation in debug mode. |
| * Generates assertion if endpoint number is not valid. |
| * |
| * @param ep Endpoint number to validity check. |
| */ |
| #define NRF_USBD_COMMON_ASSERT_EP_VALID(ep) __ASSERT_NO_MSG( \ |
| ((NRF_USBD_COMMON_EP_IS_IN(ep) && \ |
| (NRF_USBD_COMMON_EP_NUM(ep) < NRF_USBD_COMMON_EPIN_CNT)) || \ |
| (NRF_USBD_COMMON_EP_IS_OUT(ep) && \ |
| (NRF_USBD_COMMON_EP_NUM(ep) < NRF_USBD_COMMON_EPOUT_CNT)))); |
| |
| /** |
| * @brief Lowest position of bit for IN endpoint. |
| * |
| * The first bit position corresponding to IN endpoint. |
| * @sa ep2bit bit2ep |
| */ |
| #define NRF_USBD_COMMON_EPIN_BITPOS_0 0 |
| |
| /** |
| * @brief Lowest position of bit for OUT endpoint. |
| * |
| * The first bit position corresponding to OUT endpoint |
| * @sa ep2bit bit2ep |
| */ |
| #define NRF_USBD_COMMON_EPOUT_BITPOS_0 16 |
| |
| /** |
| * @brief Input endpoint bits mask. |
| */ |
| #define NRF_USBD_COMMON_EPIN_BIT_MASK (0xFFFFU << NRF_USBD_COMMON_EPIN_BITPOS_0) |
| |
| /** |
| * @brief Output endpoint bits mask. |
| */ |
| #define NRF_USBD_COMMON_EPOUT_BIT_MASK (0xFFFFU << NRF_USBD_COMMON_EPOUT_BITPOS_0) |
| |
| /** |
| * @brief Isochronous endpoint bit mask |
| */ |
| #define USBD_EPISO_BIT_MASK \ |
| ((1U << NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT8)) | \ |
| (1U << NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN8))) |
| |
| /** |
| * @brief Auxiliary macro to change EP number into bit position. |
| * |
| * This macro is used by @ref ep2bit function but also for statically check |
| * the bitpos values integrity during compilation. |
| * |
| * @param[in] ep Endpoint number. |
| * @return Endpoint bit position. |
| */ |
| #define NRF_USBD_COMMON_EP_BITPOS(ep) ((NRF_USBD_COMMON_EP_IS_IN(ep) \ |
| ? NRF_USBD_COMMON_EPIN_BITPOS_0 : NRF_USBD_COMMON_EPOUT_BITPOS_0) \ |
| + NRF_USBD_COMMON_EP_NUM(ep)) |
| |
| /** |
| * @brief Helper macro for creating an endpoint transfer event. |
| * |
| * @param[in] name Name of the created transfer event variable. |
| * @param[in] endpoint Endpoint number. |
| * @param[in] ep_stat Endpoint state to report. |
| * |
| * @return Initialized event constant variable. |
| */ |
| #define NRF_USBD_COMMON_EP_TRANSFER_EVENT(name, endpont, ep_stat) \ |
| const nrf_usbd_common_evt_t name = {NRF_USBD_COMMON_EVT_EPTRANSFER, \ |
| .data = {.eptransfer = {.ep = endpont, .status = ep_stat}}} |
| |
| /* Check it the bit positions values match defined DATAEPSTATUS bit positions */ |
| BUILD_ASSERT( |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN1) == USBD_EPDATASTATUS_EPIN1_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN2) == USBD_EPDATASTATUS_EPIN2_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN3) == USBD_EPDATASTATUS_EPIN3_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN4) == USBD_EPDATASTATUS_EPIN4_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN5) == USBD_EPDATASTATUS_EPIN5_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN6) == USBD_EPDATASTATUS_EPIN6_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN7) == USBD_EPDATASTATUS_EPIN7_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT1) == USBD_EPDATASTATUS_EPOUT1_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT2) == USBD_EPDATASTATUS_EPOUT2_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT3) == USBD_EPDATASTATUS_EPOUT3_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT4) == USBD_EPDATASTATUS_EPOUT4_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT5) == USBD_EPDATASTATUS_EPOUT5_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT6) == USBD_EPDATASTATUS_EPOUT6_Pos) && |
| (NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT7) == USBD_EPDATASTATUS_EPOUT7_Pos), |
| "NRF_USBD_COMMON bit positions do not match hardware" |
| ); |
| |
| /** |
| * @brief Current driver state. |
| */ |
| static nrfx_drv_state_t m_drv_state = NRFX_DRV_STATE_UNINITIALIZED; |
| |
| /** |
| * @brief Event handler for the driver. |
| * |
| * Event handler that would be called on events. |
| * |
| * @note Currently it cannot be null if any interrupt is activated. |
| */ |
| static nrf_usbd_common_event_handler_t m_event_handler; |
| |
| /** |
| * @brief Detected state of the bus. |
| * |
| * Internal state changed in interrupts handling when |
| * RESUME or SUSPEND event is processed. |
| * |
| * Values: |
| * - true - bus suspended |
| * - false - ongoing normal communication on the bus |
| * |
| * @note This is only the bus state and does not mean that the peripheral is in suspend state. |
| */ |
| static volatile bool m_bus_suspend; |
| |
| /** |
| * @brief Direction of last received Setup transfer. |
| * |
| * This variable is used to redirect internal setup data event |
| * into selected endpoint (IN or OUT). |
| */ |
| static nrf_usbd_common_ep_t m_last_setup_dir; |
| |
| /** |
| * @brief Mark endpoint readiness for DMA transfer. |
| * |
| * Bits in this variable are cleared and set in interrupts. |
| * 1 means that endpoint is ready for DMA transfer. |
| * 0 means that DMA transfer cannot be performed on selected endpoint. |
| */ |
| static uint32_t m_ep_ready; |
| |
| /** |
| * @brief Mark endpoint with prepared data to transfer by DMA. |
| * |
| * This variable can be set in interrupt context or within critical section. |
| * It would be cleared only from USBD interrupt. |
| * |
| * Mask prepared USBD data for transmission. |
| * It is cleared when no more data to transmit left. |
| */ |
| static uint32_t m_ep_dma_waiting; |
| |
| /* Semaphore to guard EasyDMA access. |
| * In USBD there is only one DMA channel working in background, and new transfer |
| * cannot be started when there is ongoing transfer on any other channel. |
| */ |
| static K_SEM_DEFINE(dma_available, 1, 1); |
| |
| /* Endpoint on which DMA was started. */ |
| static nrf_usbd_common_ep_t dma_ep; |
| |
| /** |
| * @brief Tracks whether total bytes transferred by DMA is even or odd. |
| */ |
| static uint8_t m_dma_odd; |
| |
| /** |
| * @brief First time enabling after reset. Used in nRF52 errata 223. |
| */ |
| static bool m_first_enable = true; |
| |
| /** |
| * @brief The structure that would hold transfer configuration to every endpoint |
| * |
| * The structure that holds all the data required by the endpoint to proceed |
| * with LIST functionality and generate quick callback directly when data |
| * buffer is ready. |
| */ |
| typedef struct { |
| nrf_usbd_common_transfer_t transfer_state; |
| bool more_transactions; |
| /** Number of transferred bytes in the current transfer. */ |
| size_t transfer_cnt; |
| /** Configured endpoint size. */ |
| uint16_t max_packet_size; |
| /** NRFX_SUCCESS or error code, never NRFX_ERROR_BUSY - this one is calculated. */ |
| nrf_usbd_common_ep_status_t status; |
| } usbd_ep_state_t; |
| |
| /** |
| * @brief The array of transfer configurations for the endpoints. |
| * |
| * The status of the transfer on each endpoint. |
| */ |
| static struct { |
| usbd_ep_state_t ep_out[NRF_USBD_COMMON_EPOUT_CNT]; /*!< Status for OUT endpoints. */ |
| usbd_ep_state_t ep_in[NRF_USBD_COMMON_EPIN_CNT]; /*!< Status for IN endpoints. */ |
| } m_ep_state; |
| |
| #define NRF_USBD_COMMON_FEEDER_BUFFER_SIZE NRF_USBD_COMMON_EPSIZE |
| |
| /** |
| * @brief Buffer used to send data directly from FLASH. |
| * |
| * This is internal buffer that would be used to emulate the possibility |
| * to transfer data directly from FLASH. |
| * We do not have to care about the source of data when calling transfer functions. |
| * |
| * We do not need more buffers that one, because only one transfer can be pending |
| * at once. |
| */ |
| static uint32_t m_tx_buffer[NRFX_CEIL_DIV(NRF_USBD_COMMON_FEEDER_BUFFER_SIZE, sizeof(uint32_t))]; |
| |
| /* Early declaration. Documentation above definition. */ |
| static void usbd_dmareq_process(void); |
| static inline void usbd_int_rise(void); |
| static void nrf_usbd_common_stop(void); |
| |
| /* Get EasyDMA end event address for given endpoint */ |
| static volatile uint32_t *usbd_ep_to_endevent(nrf_usbd_common_ep_t ep) |
| { |
| int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep); |
| int ep_num = NRF_USBD_COMMON_EP_NUM(ep); |
| |
| NRF_USBD_COMMON_ASSERT_EP_VALID(ep); |
| |
| if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) { |
| if (ep_in) { |
| return &NRF_USBD->EVENTS_ENDEPIN[ep_num]; |
| } else { |
| return &NRF_USBD->EVENTS_ENDEPOUT[ep_num]; |
| } |
| } |
| |
| return ep_in ? &NRF_USBD->EVENTS_ENDISOIN : &NRF_USBD->EVENTS_ENDISOOUT; |
| } |
| |
| /* Return number of bytes last transferred by EasyDMA on given endpoint */ |
| static uint32_t usbd_ep_amount_get(nrf_usbd_common_ep_t ep) |
| { |
| int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep); |
| int ep_num = NRF_USBD_COMMON_EP_NUM(ep); |
| |
| NRF_USBD_COMMON_ASSERT_EP_VALID(ep); |
| |
| if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) { |
| if (ep_in) { |
| return NRF_USBD->EPIN[ep_num].AMOUNT; |
| } else { |
| return NRF_USBD->EPOUT[ep_num].AMOUNT; |
| } |
| } |
| |
| return ep_in ? NRF_USBD->ISOIN.AMOUNT : NRF_USBD->ISOOUT.AMOUNT; |
| } |
| |
| /* Start EasyDMA on given endpoint */ |
| static void usbd_ep_dma_start(nrf_usbd_common_ep_t ep, uint32_t addr, size_t len) |
| { |
| int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep); |
| int ep_num = NRF_USBD_COMMON_EP_NUM(ep); |
| |
| NRF_USBD_COMMON_ASSERT_EP_VALID(ep); |
| |
| if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) { |
| if (ep_in) { |
| NRF_USBD->EPIN[ep_num].PTR = addr; |
| NRF_USBD->EPIN[ep_num].MAXCNT = len; |
| NRF_USBD->TASKS_STARTEPIN[ep_num] = 1; |
| } else { |
| NRF_USBD->EPOUT[ep_num].PTR = addr; |
| NRF_USBD->EPOUT[ep_num].MAXCNT = len; |
| NRF_USBD->TASKS_STARTEPOUT[ep_num] = 1; |
| } |
| } else if (ep_in) { |
| NRF_USBD->ISOIN.PTR = addr; |
| NRF_USBD->ISOIN.MAXCNT = len; |
| NRF_USBD->TASKS_STARTISOIN = 1; |
| } else { |
| NRF_USBD->ISOOUT.PTR = addr; |
| NRF_USBD->ISOOUT.MAXCNT = len; |
| NRF_USBD->TASKS_STARTISOOUT = 1; |
| } |
| } |
| |
| static bool nrf_usbd_common_consumer(nrf_usbd_common_ep_transfer_t *p_next, |
| nrf_usbd_common_transfer_t *p_transfer, |
| size_t ep_size, size_t data_size) |
| { |
| __ASSERT_NO_MSG(ep_size >= data_size); |
| __ASSERT_NO_MSG((p_transfer->p_data.rx == NULL) || nrfx_is_in_ram(p_transfer->p_data.rx)); |
| |
| size_t size = p_transfer->size; |
| |
| if (size < data_size) { |
| LOG_DBG("consumer: buffer too small: r: %u, l: %u", data_size, size); |
| /* Buffer size to small */ |
| p_next->size = 0; |
| p_next->p_data = p_transfer->p_data; |
| } else { |
| p_next->size = data_size; |
| p_next->p_data = p_transfer->p_data; |
| size -= data_size; |
| p_transfer->size = size; |
| p_transfer->p_data.addr += data_size; |
| } |
| return (ep_size == data_size) && (size != 0); |
| } |
| |
| static bool nrf_usbd_common_feeder(nrf_usbd_common_ep_transfer_t *p_next, |
| nrf_usbd_common_transfer_t *p_transfer, |
| size_t ep_size) |
| { |
| size_t tx_size = p_transfer->size; |
| |
| if (tx_size > ep_size) { |
| tx_size = ep_size; |
| } |
| |
| if (!nrfx_is_in_ram(p_transfer->p_data.tx)) { |
| __ASSERT_NO_MSG(tx_size <= NRF_USBD_COMMON_FEEDER_BUFFER_SIZE); |
| memcpy(m_tx_buffer, (p_transfer->p_data.tx), tx_size); |
| p_next->p_data.tx = m_tx_buffer; |
| } else { |
| p_next->p_data = p_transfer->p_data; |
| } |
| |
| p_next->size = tx_size; |
| |
| p_transfer->size -= tx_size; |
| p_transfer->p_data.addr += tx_size; |
| |
| if (p_transfer->flags & NRF_USBD_COMMON_TRANSFER_ZLP_FLAG) { |
| return (tx_size != 0); |
| } else { |
| return (p_transfer->size != 0); |
| } |
| } |
| |
| /** |
| * @brief Change Driver endpoint number to HAL endpoint number. |
| * |
| * @param ep Driver endpoint identifier. |
| * |
| * @return Endpoint identifier in HAL. |
| * |
| * @sa nrf_usbd_common_ep_from_hal |
| */ |
| static inline uint8_t ep_to_hal(nrf_usbd_common_ep_t ep) |
| { |
| NRF_USBD_COMMON_ASSERT_EP_VALID(ep); |
| return (uint8_t)ep; |
| } |
| |
| /** |
| * @brief Access selected endpoint state structure. |
| * |
| * Function used to change or just read the state of selected endpoint. |
| * It is used for internal transmission state. |
| * |
| * @param ep Endpoint number. |
| */ |
| static inline usbd_ep_state_t *ep_state_access(nrf_usbd_common_ep_t ep) |
| { |
| NRF_USBD_COMMON_ASSERT_EP_VALID(ep); |
| return ((NRF_USBD_COMMON_EP_IS_IN(ep) ? m_ep_state.ep_in : m_ep_state.ep_out) + |
| NRF_USBD_COMMON_EP_NUM(ep)); |
| } |
| |
| /** |
| * @brief Change endpoint number to bit position. |
| * |
| * Bit positions are defined the same way as they are placed in DATAEPSTATUS register, |
| * but bits for endpoint 0 are included. |
| * |
| * @param ep Endpoint number. |
| * |
| * @return Bit position related to the given endpoint number. |
| * |
| * @sa bit2ep |
| */ |
| static inline uint8_t ep2bit(nrf_usbd_common_ep_t ep) |
| { |
| NRF_USBD_COMMON_ASSERT_EP_VALID(ep); |
| return NRF_USBD_COMMON_EP_BITPOS(ep); |
| } |
| |
| /** |
| * @brief Change bit position to endpoint number. |
| * |
| * @param bitpos Bit position. |
| * |
| * @return Endpoint number corresponding to given bit position. |
| * |
| * @sa ep2bit |
| */ |
| static inline nrf_usbd_common_ep_t bit2ep(uint8_t bitpos) |
| { |
| BUILD_ASSERT(NRF_USBD_COMMON_EPOUT_BITPOS_0 > NRF_USBD_COMMON_EPIN_BITPOS_0, |
| "OUT endpoint bits should be higher than IN endpoint bits"); |
| return (nrf_usbd_common_ep_t)((bitpos >= NRF_USBD_COMMON_EPOUT_BITPOS_0) |
| ? NRF_USBD_COMMON_EPOUT(bitpos - NRF_USBD_COMMON_EPOUT_BITPOS_0) |
| : NRF_USBD_COMMON_EPIN(bitpos)); |
| } |
| |
| /** |
| * @brief Mark that EasyDMA is working. |
| * |
| * Internal function to set the flag informing about EasyDMA transfer pending. |
| * This function is called always just after the EasyDMA transfer is started. |
| */ |
| static inline void usbd_dma_pending_set(void) |
| { |
| if (nrf_usbd_common_errata_199()) { |
| *((volatile uint32_t *)0x40027C1C) = 0x00000082; |
| } |
| } |
| |
| /** |
| * @brief Mark that EasyDMA is free. |
| * |
| * Internal function to clear the flag informing about EasyDMA transfer pending. |
| * This function is called always just after the finished EasyDMA transfer is detected. |
| */ |
| static inline void usbd_dma_pending_clear(void) |
| { |
| if (nrf_usbd_common_errata_199()) { |
| *((volatile uint32_t *)0x40027C1C) = 0x00000000; |
| } |
| } |
| |
| /** |
| * @brief Abort pending transfer on selected endpoint. |
| * |
| * @param ep Endpoint number. |
| * |
| * @note |
| * This function locks interrupts that may be costly. |
| * It is good idea to test if the endpoint is still busy before calling this function: |
| * @code |
| (m_ep_dma_waiting & (1U << ep2bit(ep))) |
| * @endcode |
| * This function would check it again, but it makes it inside critical section. |
| */ |
| static inline void usbd_ep_abort(nrf_usbd_common_ep_t ep) |
| { |
| unsigned int irq_lock_key = irq_lock(); |
| usbd_ep_state_t *p_state = ep_state_access(ep); |
| |
| if (NRF_USBD_COMMON_EP_IS_OUT(ep)) { |
| /* Host -> Device */ |
| if ((~m_ep_dma_waiting) & (1U << ep2bit(ep))) { |
| /* If the bit in m_ep_dma_waiting in cleared - nothing would be |
| * processed inside transfer processing |
| */ |
| nrf_usbd_common_transfer_out_drop(ep); |
| } else { |
| p_state->more_transactions = false; |
| m_ep_dma_waiting &= ~(1U << ep2bit(ep)); |
| m_ep_ready &= ~(1U << ep2bit(ep)); |
| } |
| /* Aborted */ |
| p_state->status = NRF_USBD_COMMON_EP_ABORTED; |
| } else { |
| if (!NRF_USBD_COMMON_EP_IS_ISO(ep)) { |
| /* Workaround: Disarm the endpoint if there is any data buffered. */ |
| if (ep != NRF_USBD_COMMON_EPIN0) { |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x800)) = |
| 0x7B6 + (2u * (NRF_USBD_COMMON_EP_NUM(ep) - 1)); |
| uint8_t temp = |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)); |
| temp |= (1U << 1); |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)) |= temp; |
| (void)(*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804))); |
| } else { |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x800)) = 0x7B4; |
| uint8_t temp = |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)); |
| temp |= (1U << 2); |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)) |= temp; |
| (void)(*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804))); |
| } |
| } |
| if ((m_ep_dma_waiting | (~m_ep_ready)) & (1U << ep2bit(ep))) { |
| /* Device -> Host */ |
| m_ep_dma_waiting &= ~(1U << ep2bit(ep)); |
| m_ep_ready |= 1U << ep2bit(ep); |
| |
| p_state->more_transactions = false; |
| p_state->status = NRF_USBD_COMMON_EP_ABORTED; |
| NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_ABORTED); |
| m_event_handler(&evt); |
| } |
| } |
| |
| irq_unlock(irq_lock_key); |
| } |
| |
| void nrf_usbd_common_ep_abort(nrf_usbd_common_ep_t ep) |
| { |
| /* Only abort if there is no active DMA */ |
| k_sem_take(&dma_available, K_FOREVER); |
| usbd_ep_abort(ep); |
| k_sem_give(&dma_available); |
| |
| /* This function was holding DMA semaphore and could potentially prevent |
| * next DMA from executing. Fire IRQ handler to check if any DMA needs |
| * to be started. |
| */ |
| usbd_int_rise(); |
| } |
| |
| /** |
| * @brief Abort all pending endpoints. |
| * |
| * Function aborts all pending endpoint transfers. |
| */ |
| static void usbd_ep_abort_all(void) |
| { |
| uint32_t ep_waiting = m_ep_dma_waiting | (m_ep_ready & NRF_USBD_COMMON_EPOUT_BIT_MASK); |
| |
| while (ep_waiting != 0) { |
| uint8_t bitpos = NRF_CTZ(ep_waiting); |
| |
| if (!NRF_USBD_COMMON_EP_IS_ISO(bit2ep(bitpos))) { |
| usbd_ep_abort(bit2ep(bitpos)); |
| } |
| ep_waiting &= ~(1U << bitpos); |
| } |
| |
| m_ep_ready = (((1U << NRF_USBD_COMMON_EPIN_CNT) - 1U) << NRF_USBD_COMMON_EPIN_BITPOS_0); |
| } |
| |
| /** |
| * @brief Force the USBD interrupt into pending state. |
| * |
| * This function is used to force USBD interrupt to be processed right now. |
| * It makes it possible to process all EasyDMA access on one thread priority level. |
| */ |
| static inline void usbd_int_rise(void) |
| { |
| NVIC_SetPendingIRQ(USBD_IRQn); |
| } |
| |
| /** |
| * @name USBD interrupt runtimes. |
| * |
| * Interrupt runtimes that would be vectorized using @ref m_isr. |
| * @{ |
| */ |
| |
| static void ev_usbreset_handler(void) |
| { |
| m_bus_suspend = false; |
| m_last_setup_dir = NRF_USBD_COMMON_EPOUT0; |
| |
| const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_RESET}; |
| |
| m_event_handler(&evt); |
| } |
| |
| static void nrf_usbd_dma_finished(nrf_usbd_common_ep_t ep) |
| { |
| /* DMA finished, track if total bytes transferred is even or odd */ |
| m_dma_odd ^= usbd_ep_amount_get(ep) & 1; |
| usbd_dma_pending_clear(); |
| k_sem_give(&dma_available); |
| |
| usbd_ep_state_t *p_state = ep_state_access(ep); |
| |
| if (p_state->status == NRF_USBD_COMMON_EP_ABORTED) { |
| /* Clear transfer information just in case */ |
| m_ep_dma_waiting &= ~(1U << ep2bit(ep)); |
| } else if (!p_state->more_transactions) { |
| m_ep_dma_waiting &= ~(1U << ep2bit(ep)); |
| |
| if (NRF_USBD_COMMON_EP_IS_OUT(ep) || (ep == NRF_USBD_COMMON_EPIN8)) { |
| /* Send event to the user - for an ISO IN or any OUT endpoint, |
| * the whole transfer is finished in this moment |
| */ |
| NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_OK); |
| m_event_handler(&evt); |
| } |
| } else if (ep == NRF_USBD_COMMON_EPOUT0) { |
| nrf_usbd_common_setup_data_clear(); |
| } |
| } |
| |
| static void ev_sof_handler(void) |
| { |
| nrf_usbd_common_evt_t evt = { |
| NRF_USBD_COMMON_EVT_SOF, |
| .data = {.sof = {.framecnt = (uint16_t)NRF_USBD->FRAMECNTR}}}; |
| |
| /* Process isochronous endpoints */ |
| uint32_t iso_ready_mask = (1U << ep2bit(NRF_USBD_COMMON_EPIN8)); |
| |
| /* SIZE.ISOOUT is 0 only when no packet was received at all */ |
| if (NRF_USBD->SIZE.ISOOUT) { |
| iso_ready_mask |= (1U << ep2bit(NRF_USBD_COMMON_EPOUT8)); |
| } |
| m_ep_ready |= iso_ready_mask; |
| |
| m_event_handler(&evt); |
| } |
| |
| /** |
| * @brief React on data transfer finished. |
| * |
| * Auxiliary internal function. |
| * @param ep Endpoint number. |
| * @param bitpos Bit position for selected endpoint number. |
| */ |
| static void usbd_ep_data_handler(nrf_usbd_common_ep_t ep, uint8_t bitpos) |
| { |
| LOG_DBG("USBD event: EndpointData: %x", ep); |
| /* Mark endpoint ready for next DMA access */ |
| m_ep_ready |= (1U << bitpos); |
| |
| if (NRF_USBD_COMMON_EP_IS_IN(ep)) { |
| /* IN endpoint (Device -> Host) */ |
| if (0 == (m_ep_dma_waiting & (1U << bitpos))) { |
| LOG_DBG("USBD event: EndpointData: In finished"); |
| /* No more data to be send - transmission finished */ |
| NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_OK); |
| m_event_handler(&evt); |
| } |
| } else { |
| /* OUT endpoint (Host -> Device) */ |
| if (0 == (m_ep_dma_waiting & (1U << bitpos))) { |
| LOG_DBG("USBD event: EndpointData: Out waiting"); |
| /* No buffer prepared - send event to the application */ |
| NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_WAITING); |
| m_event_handler(&evt); |
| } |
| } |
| } |
| |
| static void ev_setup_handler(void) |
| { |
| LOG_DBG("USBD event: Setup (rt:%.2x r:%.2x v:%.4x i:%.4x l:%u )", |
| NRF_USBD->BMREQUESTTYPE, NRF_USBD->BREQUEST, |
| NRF_USBD->WVALUEL | (NRF_USBD->WVALUEH << 8), |
| NRF_USBD->WINDEXL | (NRF_USBD->WINDEXH << 8), |
| NRF_USBD->WLENGTHL | (NRF_USBD->WLENGTHH << 8)); |
| uint8_t bmRequestType = NRF_USBD->BMREQUESTTYPE; |
| |
| m_last_setup_dir = |
| ((bmRequestType & USBD_BMREQUESTTYPE_DIRECTION_Msk) == |
| (USBD_BMREQUESTTYPE_DIRECTION_HostToDevice << USBD_BMREQUESTTYPE_DIRECTION_Pos)) |
| ? NRF_USBD_COMMON_EPOUT0 |
| : NRF_USBD_COMMON_EPIN0; |
| |
| m_ep_dma_waiting &= ~((1U << ep2bit(NRF_USBD_COMMON_EPOUT0)) | |
| (1U << ep2bit(NRF_USBD_COMMON_EPIN0))); |
| m_ep_ready &= ~(1U << ep2bit(NRF_USBD_COMMON_EPOUT0)); |
| m_ep_ready |= 1U << ep2bit(NRF_USBD_COMMON_EPIN0); |
| |
| const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_SETUP}; |
| |
| m_event_handler(&evt); |
| } |
| |
| static void ev_usbevent_handler(void) |
| { |
| uint32_t event = NRF_USBD->EVENTCAUSE; |
| |
| /* Clear handled events */ |
| NRF_USBD->EVENTCAUSE = event; |
| |
| if (event & USBD_EVENTCAUSE_ISOOUTCRC_Msk) { |
| LOG_DBG("USBD event: ISOOUTCRC"); |
| /* Currently no support */ |
| } |
| if (event & USBD_EVENTCAUSE_SUSPEND_Msk) { |
| LOG_DBG("USBD event: SUSPEND"); |
| m_bus_suspend = true; |
| const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_SUSPEND}; |
| |
| m_event_handler(&evt); |
| } |
| if (event & USBD_EVENTCAUSE_RESUME_Msk) { |
| LOG_DBG("USBD event: RESUME"); |
| m_bus_suspend = false; |
| const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_RESUME}; |
| |
| m_event_handler(&evt); |
| } |
| if (event & USBD_EVENTCAUSE_USBWUALLOWED_Msk) { |
| LOG_DBG("USBD event: WUREQ (%s)", m_bus_suspend ? "In Suspend" : "Active"); |
| if (m_bus_suspend) { |
| __ASSERT_NO_MSG(!nrf_usbd_common_suspend_check()); |
| m_bus_suspend = false; |
| |
| NRF_USBD->DPDMVALUE = USBD_DPDMVALUE_STATE_Resume |
| << USBD_DPDMVALUE_STATE_Pos; |
| NRF_USBD->TASKS_DPDMDRIVE = 1; |
| |
| const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_WUREQ}; |
| |
| m_event_handler(&evt); |
| } |
| } |
| } |
| |
| static void ev_epdata_handler(uint32_t dataepstatus) |
| { |
| LOG_DBG("USBD event: EndpointEPStatus: %x", dataepstatus); |
| |
| /* All finished endpoint have to be marked as busy */ |
| while (dataepstatus) { |
| uint8_t bitpos = NRF_CTZ(dataepstatus); |
| nrf_usbd_common_ep_t ep = bit2ep(bitpos); |
| |
| dataepstatus &= ~(1UL << bitpos); |
| |
| (void)(usbd_ep_data_handler(ep, bitpos)); |
| } |
| } |
| |
| /** |
| * @brief Function to select the endpoint to start. |
| * |
| * Function that realizes algorithm to schedule right channel for EasyDMA transfer. |
| * It gets a variable with flags for the endpoints currently requiring transfer. |
| * |
| * @param[in] req Bit flags for channels currently requiring transfer. |
| * Bits 0...8 used for IN endpoints. |
| * Bits 16...24 used for OUT endpoints. |
| * @note |
| * This function would be never called with 0 as a @c req argument. |
| * @return The bit number of the endpoint that should be processed now. |
| */ |
| static uint8_t usbd_dma_scheduler_algorithm(uint32_t req) |
| { |
| /* Only prioritized scheduling mode is supported. */ |
| return NRF_CTZ(req); |
| } |
| |
| /** |
| * @brief Get the size of isochronous endpoint. |
| * |
| * The size of isochronous endpoint is configurable. |
| * This function returns the size of isochronous buffer taking into account |
| * current configuration. |
| * |
| * @param[in] ep Endpoint number. |
| * |
| * @return The size of endpoint buffer. |
| */ |
| static inline size_t usbd_ep_iso_capacity(nrf_usbd_common_ep_t ep) |
| { |
| (void)ep; |
| |
| if (NRF_USBD->ISOSPLIT == USBD_ISOSPLIT_SPLIT_HalfIN << USBD_ISOSPLIT_SPLIT_Pos) { |
| return NRF_USBD_COMMON_ISOSIZE / 2; |
| } |
| return NRF_USBD_COMMON_ISOSIZE; |
| } |
| |
| /** |
| * @brief Process all DMA requests. |
| * |
| * Function that have to be called from USBD interrupt handler. |
| * It have to be called when all the interrupts connected with endpoints transfer |
| * and DMA transfer are already handled. |
| */ |
| static void usbd_dmareq_process(void) |
| { |
| if ((m_ep_dma_waiting & m_ep_ready) && |
| (k_sem_take(&dma_available, K_NO_WAIT) == 0)) { |
| uint32_t req; |
| |
| while (0 != (req = m_ep_dma_waiting & m_ep_ready)) { |
| uint8_t pos; |
| |
| if (NRFX_USBD_CONFIG_DMASCHEDULER_ISO_BOOST && |
| ((req & USBD_EPISO_BIT_MASK) != 0)) { |
| pos = usbd_dma_scheduler_algorithm(req & USBD_EPISO_BIT_MASK); |
| } else { |
| pos = usbd_dma_scheduler_algorithm(req); |
| } |
| nrf_usbd_common_ep_t ep = bit2ep(pos); |
| usbd_ep_state_t *p_state = ep_state_access(ep); |
| |
| nrf_usbd_common_ep_transfer_t transfer; |
| bool continue_transfer; |
| |
| __ASSERT_NO_MSG(p_state->more_transactions); |
| |
| if (NRF_USBD_COMMON_EP_IS_IN(ep)) { |
| /* Device -> Host */ |
| continue_transfer = nrf_usbd_common_feeder( |
| &transfer, &p_state->transfer_state, |
| p_state->max_packet_size); |
| } else { |
| /* Host -> Device */ |
| const size_t rx_size = nrf_usbd_common_epout_size_get(ep); |
| |
| continue_transfer = nrf_usbd_common_consumer( |
| &transfer, &p_state->transfer_state, |
| p_state->max_packet_size, rx_size); |
| |
| if (transfer.p_data.rx == NULL) { |
| /* Dropping transfer - allow processing */ |
| __ASSERT_NO_MSG(transfer.size == 0); |
| } else if (transfer.size < rx_size) { |
| LOG_DBG("Endpoint %x overload (r: %u, e: %u)", ep, |
| rx_size, transfer.size); |
| p_state->status = NRF_USBD_COMMON_EP_OVERLOAD; |
| m_ep_dma_waiting &= ~(1U << pos); |
| NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, |
| NRF_USBD_COMMON_EP_OVERLOAD); |
| m_event_handler(&evt); |
| /* This endpoint will not be transmitted now, repeat the |
| * loop |
| */ |
| continue; |
| } else { |
| /* Nothing to do - only check integrity if assertions are |
| * enabled |
| */ |
| __ASSERT_NO_MSG(transfer.size == rx_size); |
| } |
| } |
| |
| if (!continue_transfer) { |
| p_state->more_transactions = false; |
| } |
| |
| usbd_dma_pending_set(); |
| m_ep_ready &= ~(1U << pos); |
| if (NRF_USBD_COMMON_ISO_DEBUG || (!NRF_USBD_COMMON_EP_IS_ISO(ep))) { |
| LOG_DBG("USB DMA process: Starting transfer on EP: %x, size: %u", |
| ep, transfer.size); |
| } |
| /* Update number of currently transferred bytes */ |
| p_state->transfer_cnt += transfer.size; |
| /* Start transfer to the endpoint buffer */ |
| dma_ep = ep; |
| usbd_ep_dma_start(ep, transfer.p_data.addr, transfer.size); |
| |
| /* Transfer started - exit the loop */ |
| return; |
| } |
| k_sem_give(&dma_available); |
| } else { |
| if (NRF_USBD_COMMON_DMAREQ_PROCESS_DEBUG) { |
| LOG_DBG("USB DMA process - EasyDMA busy"); |
| } |
| } |
| } |
| |
| /** |
| * @brief Begin errata 171. |
| */ |
| static inline void usbd_errata_171_begin(void) |
| { |
| unsigned int irq_lock_key = irq_lock(); |
| |
| if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) { |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| *((volatile uint32_t *)(0x4006EC14)) = 0x000000C0; |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| } else { |
| *((volatile uint32_t *)(0x4006EC14)) = 0x000000C0; |
| } |
| |
| irq_unlock(irq_lock_key); |
| } |
| |
| /** |
| * @brief End errata 171. |
| */ |
| static inline void usbd_errata_171_end(void) |
| { |
| unsigned int irq_lock_key = irq_lock(); |
| |
| if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) { |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| *((volatile uint32_t *)(0x4006EC14)) = 0x00000000; |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| } else { |
| *((volatile uint32_t *)(0x4006EC14)) = 0x00000000; |
| } |
| |
| irq_unlock(irq_lock_key); |
| } |
| |
| /** |
| * @brief Begin erratas 187 and 211. |
| */ |
| static inline void usbd_errata_187_211_begin(void) |
| { |
| unsigned int irq_lock_key = irq_lock(); |
| |
| if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) { |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| *((volatile uint32_t *)(0x4006ED14)) = 0x00000003; |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| } else { |
| *((volatile uint32_t *)(0x4006ED14)) = 0x00000003; |
| } |
| |
| irq_unlock(irq_lock_key); |
| } |
| |
| /** |
| * @brief End erratas 187 and 211. |
| */ |
| static inline void usbd_errata_187_211_end(void) |
| { |
| unsigned int irq_lock_key = irq_lock(); |
| |
| if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) { |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| *((volatile uint32_t *)(0x4006ED14)) = 0x00000000; |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| } else { |
| *((volatile uint32_t *)(0x4006ED14)) = 0x00000000; |
| } |
| |
| irq_unlock(irq_lock_key); |
| } |
| |
| /** |
| * @brief Enable USBD peripheral. |
| */ |
| static void usbd_enable(void) |
| { |
| if (nrf_usbd_common_errata_187()) { |
| usbd_errata_187_211_begin(); |
| } |
| |
| if (nrf_usbd_common_errata_171()) { |
| usbd_errata_171_begin(); |
| } |
| |
| /* Enable the peripheral */ |
| NRF_USBD->ENABLE = 1; |
| |
| /* Waiting for peripheral to enable, this should take a few us */ |
| while ((NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_READY_Msk) == 0) { |
| } |
| NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; |
| |
| if (nrf_usbd_common_errata_171()) { |
| usbd_errata_171_end(); |
| } |
| |
| if (nrf_usbd_common_errata_187()) { |
| usbd_errata_187_211_end(); |
| } |
| } |
| /** @} */ |
| |
| /** |
| * @name Interrupt handlers |
| * |
| * @{ |
| */ |
| void nrf_usbd_common_irq_handler(void) |
| { |
| volatile uint32_t *dma_endevent; |
| uint32_t epdatastatus = 0; |
| |
| /* Clear EPDATA event and only then get and clear EPDATASTATUS to make |
| * sure we don't miss any event. |
| */ |
| if (NRF_USBD->EVENTS_EPDATA) { |
| NRF_USBD->EVENTS_EPDATA = 0; |
| epdatastatus = NRF_USBD->EPDATASTATUS; |
| NRF_USBD->EPDATASTATUS = epdatastatus; |
| } |
| |
| /* Use common variable to store EP0DATADONE processing needed flag */ |
| if (NRF_USBD->EVENTS_EP0DATADONE) { |
| NRF_USBD->EVENTS_EP0DATADONE = 0; |
| epdatastatus |= BIT(ep2bit(m_last_setup_dir)); |
| } |
| |
| /* Check DMA end event only for last enabled DMA channel. Other channels |
| * cannot be active and there's no harm in rechecking the event multiple |
| * times (it is not a problem to check it even if DMA is not active). |
| * |
| * It is important to check DMA and handle DMA finished event before |
| * handling acknowledged data transfer bits (epdatastatus) to avoid |
| * a race condition between interrupt handler and host IN token. |
| */ |
| dma_endevent = usbd_ep_to_endevent(dma_ep); |
| if (*dma_endevent) { |
| *dma_endevent = 0; |
| nrf_usbd_dma_finished(dma_ep); |
| } |
| |
| /* Process acknowledged transfers so we can prepare next DMA (if any) */ |
| ev_epdata_handler(epdatastatus); |
| |
| if (NRF_USBD->EVENTS_USBRESET) { |
| NRF_USBD->EVENTS_USBRESET = 0; |
| ev_usbreset_handler(); |
| } |
| |
| /* Always check and clear SOF but call handler only if SOF interrupt |
| * is actually enabled. |
| */ |
| if (NRF_USBD->EVENTS_SOF) { |
| NRF_USBD->EVENTS_SOF = 0; |
| if (NRF_USBD->INTENSET & USBD_INTEN_SOF_Msk) { |
| ev_sof_handler(); |
| } |
| } |
| |
| if (NRF_USBD->EVENTS_USBEVENT) { |
| NRF_USBD->EVENTS_USBEVENT = 0; |
| ev_usbevent_handler(); |
| } |
| |
| /* Handle SETUP only if there is no active DMA on EP0 */ |
| if (unlikely(NRF_USBD->EVENTS_EP0SETUP) && |
| (k_sem_count_get(&dma_available) || |
| (dma_ep != NRF_USBD_COMMON_EPIN0 && dma_ep != NRF_USBD_COMMON_EPOUT0))) { |
| NRF_USBD->EVENTS_EP0SETUP = 0; |
| ev_setup_handler(); |
| } |
| |
| usbd_dmareq_process(); |
| } |
| |
| /** @} */ |
| /** @} */ |
| |
| nrfx_err_t nrf_usbd_common_init(nrf_usbd_common_event_handler_t event_handler) |
| { |
| __ASSERT_NO_MSG(event_handler); |
| |
| if (m_drv_state != NRFX_DRV_STATE_UNINITIALIZED) { |
| return NRFX_ERROR_INVALID_STATE; |
| } |
| |
| m_event_handler = event_handler; |
| m_drv_state = NRFX_DRV_STATE_INITIALIZED; |
| |
| uint8_t n; |
| |
| for (n = 0; n < NRF_USBD_COMMON_EPIN_CNT; ++n) { |
| nrf_usbd_common_ep_t ep = NRF_USBD_COMMON_EPIN(n); |
| |
| nrf_usbd_common_ep_max_packet_size_set(ep, NRF_USBD_COMMON_EP_IS_ISO(ep) ? |
| (NRF_USBD_COMMON_ISOSIZE / 2) : NRF_USBD_COMMON_EPSIZE); |
| usbd_ep_state_t *p_state = ep_state_access(ep); |
| |
| p_state->status = NRF_USBD_COMMON_EP_OK; |
| p_state->more_transactions = false; |
| p_state->transfer_cnt = 0; |
| } |
| for (n = 0; n < NRF_USBD_COMMON_EPOUT_CNT; ++n) { |
| nrf_usbd_common_ep_t ep = NRF_USBD_COMMON_EPOUT(n); |
| |
| nrf_usbd_common_ep_max_packet_size_set(ep, NRF_USBD_COMMON_EP_IS_ISO(ep) ? |
| (NRF_USBD_COMMON_ISOSIZE / 2) : NRF_USBD_COMMON_EPSIZE); |
| usbd_ep_state_t *p_state = ep_state_access(ep); |
| |
| p_state->status = NRF_USBD_COMMON_EP_OK; |
| p_state->more_transactions = false; |
| p_state->transfer_cnt = 0; |
| } |
| |
| return NRFX_SUCCESS; |
| } |
| |
| void nrf_usbd_common_uninit(void) |
| { |
| __ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_INITIALIZED); |
| |
| m_event_handler = NULL; |
| m_drv_state = NRFX_DRV_STATE_UNINITIALIZED; |
| } |
| |
| void nrf_usbd_common_enable(void) |
| { |
| __ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_INITIALIZED); |
| |
| /* Prepare for READY event receiving */ |
| NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; |
| |
| usbd_enable(); |
| |
| if (nrf_usbd_common_errata_223() && m_first_enable) { |
| NRF_USBD->ENABLE = 0; |
| |
| usbd_enable(); |
| |
| m_first_enable = false; |
| } |
| |
| #if NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211 |
| if (nrf_usbd_common_errata_187() || nrf_usbd_common_errata_211()) |
| #else |
| if (nrf_usbd_common_errata_187()) |
| #endif |
| { |
| usbd_errata_187_211_begin(); |
| } |
| |
| if (nrf_usbd_common_errata_166()) { |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x800)) = 0x7E3; |
| *((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)) = 0x40; |
| __ISB(); |
| __DSB(); |
| } |
| |
| NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN << USBD_ISOSPLIT_SPLIT_Pos; |
| |
| if (IS_ENABLED(CONFIG_NRF_USBD_ISO_IN_ZLP)) { |
| NRF_USBD->ISOINCONFIG = USBD_ISOINCONFIG_RESPONSE_ZeroData |
| << USBD_ISOINCONFIG_RESPONSE_Pos; |
| } else { |
| NRF_USBD->ISOINCONFIG = USBD_ISOINCONFIG_RESPONSE_NoResp |
| << USBD_ISOINCONFIG_RESPONSE_Pos; |
| } |
| |
| m_ep_ready = (((1U << NRF_USBD_COMMON_EPIN_CNT) - 1U) << NRF_USBD_COMMON_EPIN_BITPOS_0); |
| m_ep_dma_waiting = 0; |
| m_dma_odd = 0; |
| __ASSERT_NO_MSG(k_sem_count_get(&dma_available) == 1); |
| usbd_dma_pending_clear(); |
| m_last_setup_dir = NRF_USBD_COMMON_EPOUT0; |
| |
| m_drv_state = NRFX_DRV_STATE_POWERED_ON; |
| |
| #if NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211 |
| if (nrf_usbd_common_errata_187() && !nrf_usbd_common_errata_211()) |
| #else |
| if (nrf_usbd_common_errata_187()) |
| #endif |
| { |
| usbd_errata_187_211_end(); |
| } |
| } |
| |
| void nrf_usbd_common_disable(void) |
| { |
| __ASSERT_NO_MSG(m_drv_state != NRFX_DRV_STATE_UNINITIALIZED); |
| |
| /* Make sure DMA is not active */ |
| k_sem_take(&dma_available, K_FOREVER); |
| |
| /* Stop just in case */ |
| nrf_usbd_common_stop(); |
| |
| /* Disable all parts */ |
| if (m_dma_odd) { |
| /* Prevent invalid bus request after next USBD enable by ensuring |
| * that total number of bytes transferred by DMA is even. |
| */ |
| NRF_USBD->EVENTS_ENDEPIN[0] = 0; |
| usbd_ep_dma_start(NRF_USBD_COMMON_EPIN0, (uint32_t)&m_dma_odd, 1); |
| while (!NRF_USBD->EVENTS_ENDEPIN[0]) { |
| } |
| NRF_USBD->EVENTS_ENDEPIN[0] = 0; |
| m_dma_odd = 0; |
| } |
| NRF_USBD->ENABLE = 0; |
| usbd_dma_pending_clear(); |
| k_sem_give(&dma_available); |
| m_drv_state = NRFX_DRV_STATE_INITIALIZED; |
| |
| #if NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211 |
| if (nrf_usbd_common_errata_211()) { |
| usbd_errata_187_211_end(); |
| } |
| #endif |
| } |
| |
| void nrf_usbd_common_start(bool enable_sof) |
| { |
| __ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_POWERED_ON); |
| m_bus_suspend = false; |
| |
| uint32_t int_mask = USBD_INTEN_USBRESET_Msk | USBD_INTEN_ENDEPIN0_Msk | |
| USBD_INTEN_ENDEPIN1_Msk | USBD_INTEN_ENDEPIN2_Msk | |
| USBD_INTEN_ENDEPIN3_Msk | USBD_INTEN_ENDEPIN4_Msk | |
| USBD_INTEN_ENDEPIN5_Msk | USBD_INTEN_ENDEPIN6_Msk | |
| USBD_INTEN_ENDEPIN7_Msk | USBD_INTEN_EP0DATADONE_Msk | |
| USBD_INTEN_ENDISOIN_Msk | USBD_INTEN_ENDEPOUT0_Msk | |
| USBD_INTEN_ENDEPOUT1_Msk | USBD_INTEN_ENDEPOUT2_Msk | |
| USBD_INTEN_ENDEPOUT3_Msk | USBD_INTEN_ENDEPOUT4_Msk | |
| USBD_INTEN_ENDEPOUT5_Msk | USBD_INTEN_ENDEPOUT6_Msk | |
| USBD_INTEN_ENDEPOUT7_Msk | USBD_INTEN_ENDISOOUT_Msk | |
| USBD_INTEN_USBEVENT_Msk | USBD_INTEN_EP0SETUP_Msk | |
| USBD_INTEN_EPDATA_Msk; |
| |
| if (enable_sof) { |
| int_mask |= USBD_INTEN_SOF_Msk; |
| } |
| |
| /* Enable all required interrupts */ |
| NRF_USBD->INTEN = int_mask; |
| |
| /* Enable interrupt globally */ |
| irq_enable(USBD_IRQn); |
| |
| /* Enable pullups */ |
| NRF_USBD->USBPULLUP = 1; |
| } |
| |
| static void nrf_usbd_common_stop(void) |
| { |
| __ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_POWERED_ON); |
| |
| /* Clear interrupt */ |
| NVIC_ClearPendingIRQ(USBD_IRQn); |
| |
| if (irq_is_enabled(USBD_IRQn)) { |
| /* Abort transfers */ |
| usbd_ep_abort_all(); |
| |
| /* Disable pullups */ |
| NRF_USBD->USBPULLUP = 0; |
| |
| /* Disable interrupt globally */ |
| irq_disable(USBD_IRQn); |
| |
| /* Disable all interrupts */ |
| NRF_USBD->INTEN = 0; |
| } |
| } |
| |
| bool nrf_usbd_common_is_initialized(void) |
| { |
| return (m_drv_state >= NRFX_DRV_STATE_INITIALIZED); |
| } |
| |
| bool nrf_usbd_common_is_enabled(void) |
| { |
| return (m_drv_state >= NRFX_DRV_STATE_POWERED_ON); |
| } |
| |
| bool nrf_usbd_common_is_started(void) |
| { |
| return (nrf_usbd_common_is_enabled() && irq_is_enabled(USBD_IRQn)); |
| } |
| |
| bool nrf_usbd_common_suspend(void) |
| { |
| bool suspended = false; |
| unsigned int irq_lock_key = irq_lock(); |
| |
| if (m_bus_suspend) { |
| if (!(NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_RESUME_Msk)) { |
| NRF_USBD->LOWPOWER = USBD_LOWPOWER_LOWPOWER_LowPower |
| << USBD_LOWPOWER_LOWPOWER_Pos; |
| (void)NRF_USBD->LOWPOWER; |
| if (NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_RESUME_Msk) { |
| NRF_USBD->LOWPOWER = USBD_LOWPOWER_LOWPOWER_ForceNormal |
| << USBD_LOWPOWER_LOWPOWER_Pos; |
| } else { |
| suspended = true; |
| } |
| } |
| } |
| |
| irq_unlock(irq_lock_key); |
| |
| return suspended; |
| } |
| |
| bool nrf_usbd_common_wakeup_req(void) |
| { |
| bool started = false; |
| unsigned int irq_lock_key = irq_lock(); |
| |
| if (m_bus_suspend && nrf_usbd_common_suspend_check()) { |
| NRF_USBD->LOWPOWER = USBD_LOWPOWER_LOWPOWER_ForceNormal |
| << USBD_LOWPOWER_LOWPOWER_Pos; |
| started = true; |
| |
| if (nrf_usbd_common_errata_171()) { |
| if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) { |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| *((volatile uint32_t *)(0x4006EC14)) = 0x000000C0; |
| *((volatile uint32_t *)(0x4006EC00)) = 0x00009375; |
| } else { |
| *((volatile uint32_t *)(0x4006EC14)) = 0x000000C0; |
| } |
| } |
| } |
| |
| irq_unlock(irq_lock_key); |
| |
| return started; |
| } |
| |
| bool nrf_usbd_common_suspend_check(void) |
| { |
| return NRF_USBD->LOWPOWER != |
| (USBD_LOWPOWER_LOWPOWER_ForceNormal << USBD_LOWPOWER_LOWPOWER_Pos); |
| } |
| |
| bool nrf_usbd_common_bus_suspend_check(void) |
| { |
| return m_bus_suspend; |
| } |
| |
| void nrf_usbd_common_force_bus_wakeup(void) |
| { |
| m_bus_suspend = false; |
| } |
| |
| void nrf_usbd_common_ep_max_packet_size_set(nrf_usbd_common_ep_t ep, uint16_t size) |
| { |
| /* Only the power of 2 size allowed for Control Endpoints */ |
| __ASSERT_NO_MSG((((size & (size - 1)) == 0) || (NRF_USBD_COMMON_EP_NUM(ep) != 0))); |
| /* Only non zero size allowed for Control Endpoints */ |
| __ASSERT_NO_MSG((size != 0) || (NRF_USBD_COMMON_EP_NUM(ep) != 0)); |
| /* Packet size cannot be higher than maximum buffer size */ |
| __ASSERT_NO_MSG((NRF_USBD_COMMON_EP_IS_ISO(ep) && (size <= usbd_ep_iso_capacity(ep))) || |
| (!NRF_USBD_COMMON_EP_IS_ISO(ep) && (size <= NRF_USBD_COMMON_EPSIZE))); |
| |
| usbd_ep_state_t *p_state = ep_state_access(ep); |
| |
| p_state->max_packet_size = size; |
| } |
| |
| uint16_t nrf_usbd_common_ep_max_packet_size_get(nrf_usbd_common_ep_t ep) |
| { |
| usbd_ep_state_t const *p_state = ep_state_access(ep); |
| |
| return p_state->max_packet_size; |
| } |
| |
| bool nrf_usbd_common_ep_enable_check(nrf_usbd_common_ep_t ep) |
| { |
| int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep); |
| int ep_num = NRF_USBD_COMMON_EP_NUM(ep); |
| |
| NRF_USBD_COMMON_ASSERT_EP_VALID(ep); |
| |
| return (ep_in ? NRF_USBD->EPINEN : NRF_USBD->EPOUTEN) & BIT(ep_num); |
| } |
| |
| void nrf_usbd_common_ep_enable(nrf_usbd_common_ep_t ep) |
| { |
| int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep); |
| int ep_num = NRF_USBD_COMMON_EP_NUM(ep); |
| |
| if (nrf_usbd_common_ep_enable_check(ep)) { |
| return; |
| } |
| |
| if (ep_in) { |
| NRF_USBD->EPINEN |= BIT(ep_num); |
| } else { |
| NRF_USBD->EPOUTEN |= BIT(ep_num); |
| } |
| |
| if (ep >= NRF_USBD_COMMON_EPOUT1 && ep <= NRF_USBD_COMMON_EPOUT7) { |
| unsigned int irq_lock_key = irq_lock(); |
| |
| nrf_usbd_common_transfer_out_drop(ep); |
| m_ep_dma_waiting &= ~(1U << ep2bit(ep)); |
| |
| irq_unlock(irq_lock_key); |
| } |
| } |
| |
| void nrf_usbd_common_ep_disable(nrf_usbd_common_ep_t ep) |
| { |
| int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep); |
| int ep_num = NRF_USBD_COMMON_EP_NUM(ep); |
| |
| /* Only disable endpoint if there is no active DMA */ |
| k_sem_take(&dma_available, K_FOREVER); |
| usbd_ep_abort(ep); |
| if (ep_in) { |
| NRF_USBD->EPINEN &= ~BIT(ep_num); |
| } else { |
| NRF_USBD->EPOUTEN &= ~BIT(ep_num); |
| } |
| k_sem_give(&dma_available); |
| |
| /* This function was holding DMA semaphore and could potentially prevent |
| * next DMA from executing. Fire IRQ handler to check if any DMA needs |
| * to be started. |
| */ |
| usbd_int_rise(); |
| } |
| |
| nrfx_err_t nrf_usbd_common_ep_transfer(nrf_usbd_common_ep_t ep, |
| nrf_usbd_common_transfer_t const *p_transfer) |
| { |
| nrfx_err_t ret; |
| const uint8_t ep_bitpos = ep2bit(ep); |
| unsigned int irq_lock_key = irq_lock(); |
| |
| __ASSERT_NO_MSG(p_transfer != NULL); |
| |
| /* Setup data transaction can go only in one direction at a time */ |
| if ((NRF_USBD_COMMON_EP_NUM(ep) == 0) && (ep != m_last_setup_dir)) { |
| ret = NRFX_ERROR_INVALID_ADDR; |
| if (NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG && |
| (NRF_USBD_COMMON_ISO_DEBUG || (!NRF_USBD_COMMON_EP_IS_ISO(ep)))) { |
| LOG_DBG("Transfer failed: Invalid EPr\n"); |
| } |
| } else if ((m_ep_dma_waiting | ((~m_ep_ready) & NRF_USBD_COMMON_EPIN_BIT_MASK)) & |
| (1U << ep_bitpos)) { |
| /* IN (Device -> Host) transfer has to be transmitted out to allow new transmission |
| */ |
| ret = NRFX_ERROR_BUSY; |
| if (NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG) { |
| LOG_DBG("Transfer failed: EP is busy"); |
| } |
| } else { |
| usbd_ep_state_t *p_state = ep_state_access(ep); |
| |
| __ASSERT_NO_MSG(NRF_USBD_COMMON_EP_IS_IN(ep) || |
| (p_transfer->p_data.rx == NULL) || |
| (nrfx_is_in_ram(p_transfer->p_data.rx))); |
| p_state->more_transactions = true; |
| p_state->transfer_state = *p_transfer; |
| |
| p_state->transfer_cnt = 0; |
| p_state->status = NRF_USBD_COMMON_EP_OK; |
| m_ep_dma_waiting |= 1U << ep_bitpos; |
| ret = NRFX_SUCCESS; |
| usbd_int_rise(); |
| } |
| |
| irq_unlock(irq_lock_key); |
| |
| return ret; |
| } |
| |
| nrf_usbd_common_ep_status_t nrf_usbd_common_ep_status_get(nrf_usbd_common_ep_t ep, size_t *p_size) |
| { |
| nrf_usbd_common_ep_status_t ret; |
| usbd_ep_state_t const *p_state = ep_state_access(ep); |
| unsigned int irq_lock_key = irq_lock(); |
| |
| *p_size = p_state->transfer_cnt; |
| ret = (!p_state->more_transactions) ? p_state->status : NRF_USBD_COMMON_EP_BUSY; |
| |
| irq_unlock(irq_lock_key); |
| |
| return ret; |
| } |
| |
| size_t nrf_usbd_common_epout_size_get(nrf_usbd_common_ep_t ep) |
| { |
| if (NRF_USBD_COMMON_EP_IS_ISO(ep)) { |
| size_t size = NRF_USBD->SIZE.ISOOUT; |
| |
| if ((size & USBD_SIZE_ISOOUT_ZERO_Msk) == |
| (USBD_SIZE_ISOOUT_ZERO_ZeroData << USBD_SIZE_ISOOUT_ZERO_Pos)) { |
| size = 0; |
| } |
| return size; |
| } |
| |
| return NRF_USBD->SIZE.EPOUT[NRF_USBD_COMMON_EP_NUM(ep)]; |
| } |
| |
| bool nrf_usbd_common_ep_is_busy(nrf_usbd_common_ep_t ep) |
| { |
| return (0 != ((m_ep_dma_waiting | ((~m_ep_ready) & NRF_USBD_COMMON_EPIN_BIT_MASK)) & |
| (1U << ep2bit(ep)))); |
| } |
| |
| void nrf_usbd_common_ep_stall(nrf_usbd_common_ep_t ep) |
| { |
| __ASSERT_NO_MSG(!NRF_USBD_COMMON_EP_IS_ISO(ep)); |
| |
| LOG_DBG("USB: EP %x stalled.", ep); |
| NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_Stall << USBD_EPSTALL_STALL_Pos) | ep; |
| } |
| |
| void nrf_usbd_common_ep_stall_clear(nrf_usbd_common_ep_t ep) |
| { |
| __ASSERT_NO_MSG(!NRF_USBD_COMMON_EP_IS_ISO(ep)); |
| |
| if (NRF_USBD_COMMON_EP_IS_OUT(ep) && nrf_usbd_common_ep_stall_check(ep)) { |
| nrf_usbd_common_transfer_out_drop(ep); |
| } |
| NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep; |
| } |
| |
| bool nrf_usbd_common_ep_stall_check(nrf_usbd_common_ep_t ep) |
| { |
| int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep); |
| int ep_num = NRF_USBD_COMMON_EP_NUM(ep); |
| |
| if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) { |
| if (ep_in) { |
| return NRF_USBD->HALTED.EPIN[ep_num]; |
| } else { |
| return NRF_USBD->HALTED.EPOUT[ep_num]; |
| } |
| } |
| |
| return false; |
| } |
| |
| void nrf_usbd_common_ep_dtoggle_clear(nrf_usbd_common_ep_t ep) |
| { |
| __ASSERT_NO_MSG(!NRF_USBD_COMMON_EP_IS_ISO(ep)); |
| |
| NRF_USBD->DTOGGLE = ep | (USBD_DTOGGLE_VALUE_Nop << USBD_DTOGGLE_VALUE_Pos); |
| NRF_USBD->DTOGGLE = ep | (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos); |
| } |
| |
| void nrf_usbd_common_setup_get(nrf_usbd_common_setup_t *p_setup) |
| { |
| memset(p_setup, 0, sizeof(nrf_usbd_common_setup_t)); |
| p_setup->bmRequestType = NRF_USBD->BMREQUESTTYPE; |
| p_setup->bRequest = NRF_USBD->BREQUEST; |
| p_setup->wValue = NRF_USBD->WVALUEL | (NRF_USBD->WVALUEH << 8); |
| p_setup->wIndex = NRF_USBD->WINDEXL | (NRF_USBD->WINDEXH << 8); |
| p_setup->wLength = NRF_USBD->WLENGTHL | (NRF_USBD->WLENGTHH << 8); |
| } |
| |
| void nrf_usbd_common_setup_data_clear(void) |
| { |
| NRF_USBD->TASKS_EP0RCVOUT = 1; |
| } |
| |
| void nrf_usbd_common_setup_clear(void) |
| { |
| LOG_DBG(">> ep0status >>"); |
| NRF_USBD->TASKS_EP0STATUS = 1; |
| } |
| |
| void nrf_usbd_common_setup_stall(void) |
| { |
| LOG_DBG("Setup stalled."); |
| NRF_USBD->TASKS_EP0STALL = 1; |
| } |
| |
| nrf_usbd_common_ep_t nrf_usbd_common_last_setup_dir_get(void) |
| { |
| return m_last_setup_dir; |
| } |
| |
| void nrf_usbd_common_transfer_out_drop(nrf_usbd_common_ep_t ep) |
| { |
| unsigned int irq_lock_key = irq_lock(); |
| |
| __ASSERT_NO_MSG(NRF_USBD_COMMON_EP_IS_OUT(ep)); |
| |
| m_ep_ready &= ~(1U << ep2bit(ep)); |
| if (!NRF_USBD_COMMON_EP_IS_ISO(ep)) { |
| NRF_USBD->SIZE.EPOUT[NRF_USBD_COMMON_EP_NUM(ep)] = 0; |
| } |
| |
| irq_unlock(irq_lock_key); |
| } |
| |
| /** @endcond */ |