/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * This file is part of the TinyUSB stack. */ #include "tusb_option.h" #if TUSB_OPT_DEVICE_ENABLED && CFG_TUSB_MCU == OPT_MCU_NRF5X #include "nrf.h" #include "nrf_clock.h" #include "nrf_power.h" #include "nrfx_usbd_errata.h" #ifdef SOFTDEVICE_PRESENT // For enable/disable hfclk with SoftDevice #include "nrf_sdm.h" #include "nrf_soc.h" #endif #include "device/dcd.h" // TODO remove later #include "device/usbd.h" #include "device/usbd_pvt.h" // to use defer function helper /*------------------------------------------------------------------*/ /* MACRO TYPEDEF CONSTANT ENUM *------------------------------------------------------------------*/ enum { // Max allowed by USB specs MAX_PACKET_SIZE = 64, // Mask of all END event (IN & OUT) for all endpoints. ENDEPIN0-7, ENDEPOUT0-7, ENDISOIN, ENDISOOUT EDPT_END_ALL_MASK = (0xff << USBD_INTEN_ENDEPIN0_Pos) | (0xff << USBD_INTEN_ENDEPOUT0_Pos) | USBD_INTENCLR_ENDISOIN_Msk | USBD_INTEN_ENDISOOUT_Msk }; // Transfer descriptor typedef struct { uint8_t* buffer; uint16_t total_len; volatile uint16_t actual_len; uint8_t mps; // max packet size // nrf52840 will auto ACK OUT packet after DMA is done // indicate packet is already ACK volatile bool data_received; } xfer_td_t; // Data for managing dcd static struct { // All 8 endpoints including control IN & OUT (offset 1) xfer_td_t xfer[8][2]; // Only one DMA can run at a time volatile bool dma_running; }_dcd; /*------------------------------------------------------------------*/ /* Control / Bulk / Interrupt (CBI) Transfer *------------------------------------------------------------------*/ // helper to start DMA static void edpt_dma_start(volatile uint32_t* reg_startep) { // Only one dma can be active if ( _dcd.dma_running ) { if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) { // If called within ISR, use usbd task to defer later usbd_defer_func( (osal_task_func_t) edpt_dma_start, (void*) reg_startep, true ); return; } else { // Otherwise simply block wait while ( _dcd.dma_running ) { } } } _dcd.dma_running = true; (*reg_startep) = 1; __ISB(); __DSB(); } // DMA is complete static void edpt_dma_end(void) { TU_ASSERT(_dcd.dma_running, ); _dcd.dma_running = false; } // helper getting td static inline xfer_td_t* get_td(uint8_t epnum, uint8_t dir) { return &_dcd.xfer[epnum][dir]; } /*------------- CBI OUT Transfer -------------*/ // Prepare for a CBI transaction OUT, call at the start // Allow ACK incoming data static void xact_out_prepare(uint8_t epnum) { if ( epnum == 0 ) { NRF_USBD->TASKS_EP0RCVOUT = 1; } else { // Write zero value to SIZE register will allow hw to ACK (accept data) // If it is not already done by DMA NRF_USBD->SIZE.EPOUT[epnum] = 0; } __ISB(); __DSB(); } // Start DMA to move data from Endpoint -> RAM static void xact_out_dma(uint8_t epnum) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); uint8_t const xact_len = NRF_USBD->SIZE.EPOUT[epnum]; // Trigger DMA move data from Endpoint -> SRAM NRF_USBD->EPOUT[epnum].PTR = (uint32_t) xfer->buffer; NRF_USBD->EPOUT[epnum].MAXCNT = xact_len; edpt_dma_start(&NRF_USBD->TASKS_STARTEPOUT[epnum]); xfer->buffer += xact_len; xfer->actual_len += xact_len; } /*------------- CBI IN Transfer -------------*/ // Prepare for a CBI transaction IN, call at the start // it start DMA to transfer data from RAM -> Endpoint static void xact_in_prepare(uint8_t epnum) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_IN); // Each transaction is up to Max Packet Size uint8_t const xact_len = tu_min16(xfer->total_len - xfer->actual_len, xfer->mps); NRF_USBD->EPIN[epnum].PTR = (uint32_t) xfer->buffer; NRF_USBD->EPIN[epnum].MAXCNT = xact_len; xfer->buffer += xact_len; edpt_dma_start(&NRF_USBD->TASKS_STARTEPIN[epnum]); } //--------------------------------------------------------------------+ // Controller API //--------------------------------------------------------------------+ void dcd_init (uint8_t rhport) { (void) rhport; } void dcd_int_enable(uint8_t rhport) { (void) rhport; NVIC_EnableIRQ(USBD_IRQn); } void dcd_int_disable(uint8_t rhport) { (void) rhport; NVIC_DisableIRQ(USBD_IRQn); } void dcd_set_address (uint8_t rhport, uint8_t dev_addr) { (void) rhport; (void) dev_addr; // Set Address is automatically update by hw controller, nothing to do // Enable usbevent for suspend and resume detection // Since the bus signal D+/D- are stable now. // Clear current pending first NRF_USBD->EVENTCAUSE |= NRF_USBD->EVENTCAUSE; NRF_USBD->EVENTS_USBEVENT = 0; NRF_USBD->INTENSET = USBD_INTEN_USBEVENT_Msk; } void dcd_remote_wakeup(uint8_t rhport) { (void) rhport; // Bring controller out of low power mode NRF_USBD->LOWPOWER = 0; // Initiate RESUME signal NRF_USBD->DPDMVALUE = USBD_DPDMVALUE_STATE_Resume; NRF_USBD->TASKS_DPDMDRIVE = 1; // TODO There is no USBEVENT Resume interrupt // We may manually raise DCD_EVENT_RESUME event here } // disconnect by disabling internal pull-up resistor on D+/D- void dcd_disconnect(uint8_t rhport) { (void) rhport; NRF_USBD->USBPULLUP = 0; } // connect by enabling internal pull-up resistor on D+/D- void dcd_connect(uint8_t rhport) { (void) rhport; NRF_USBD->USBPULLUP = 1; } //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) { (void) rhport; uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); _dcd.xfer[epnum][dir].mps = desc_edpt->wMaxPacketSize.size; if ( dir == TUSB_DIR_OUT ) { NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); NRF_USBD->EPOUTEN |= TU_BIT(epnum); }else { NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); NRF_USBD->EPINEN |= TU_BIT(epnum); } __ISB(); __DSB(); return true; } bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) { (void) rhport; uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); xfer_td_t* xfer = get_td(epnum, dir); xfer->buffer = buffer; xfer->total_len = total_bytes; xfer->actual_len = 0; // Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir(NRF_USBD->BMREQUESTTYPE)); if ( control_status ) { // Status Phase also require Easy DMA has to be free as well !!!! edpt_dma_start(&NRF_USBD->TASKS_EP0STATUS); edpt_dma_end(); // The nRF doesn't interrupt on status transmit so we queue up a success response. dcd_event_xfer_complete(0, ep_addr, 0, XFER_RESULT_SUCCESS, false); } else if ( dir == TUSB_DIR_OUT ) { if ( xfer->data_received ) { // nrf52840 auto ACK OUT packet after DMA is done // Data already received previously --> trigger DMA to copy to SRAM xact_out_dma(epnum); } else { xact_out_prepare(epnum); } } else { xact_in_prepare(epnum); } return true; } void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) { (void) rhport; if ( tu_edpt_number(ep_addr) == 0 ) { NRF_USBD->TASKS_EP0STALL = 1; }else { NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_Stall << USBD_EPSTALL_STALL_Pos) | ep_addr; } __ISB(); __DSB(); } void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) { (void) rhport; if ( tu_edpt_number(ep_addr) ) { // clear stall NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep_addr; // reset data toggle to DATA0 NRF_USBD->DTOGGLE = (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos) | ep_addr; __ISB(); __DSB(); } } /*------------------------------------------------------------------*/ /* Interrupt Handler *------------------------------------------------------------------*/ void bus_reset(void) { for(int i=0; i<8; i++) { NRF_USBD->TASKS_STARTEPIN[i] = 0; NRF_USBD->TASKS_STARTEPOUT[i] = 0; } NRF_USBD->TASKS_STARTISOIN = 0; NRF_USBD->TASKS_STARTISOOUT = 0; tu_varclr(&_dcd); _dcd.xfer[0][TUSB_DIR_IN].mps = MAX_PACKET_SIZE; _dcd.xfer[0][TUSB_DIR_OUT].mps = MAX_PACKET_SIZE; } void dcd_int_handler(uint8_t rhport) { (void) rhport; uint32_t const inten = NRF_USBD->INTEN; uint32_t int_status = 0; volatile uint32_t* regevt = &NRF_USBD->EVENTS_USBRESET; for(uint8_t i=0; iEVENTCAUSE & (USBD_EVENTCAUSE_SUSPEND_Msk | USBD_EVENTCAUSE_RESUME_Msk); NRF_USBD->EVENTCAUSE = evt_cause; // clear interrupt if ( evt_cause & USBD_EVENTCAUSE_SUSPEND_Msk ) { dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); // Put controller into low power mode NRF_USBD->LOWPOWER = 1; // Leave HFXO disable to application, since it may be used by other } if ( evt_cause & USBD_EVENTCAUSE_RESUME_Msk ) { dcd_event_bus_signal(0, DCD_EVENT_RESUME , true); } } if ( int_status & EDPT_END_ALL_MASK ) { // DMA complete move data from SRAM -> Endpoint edpt_dma_end(); } // Setup tokens are specific to the Control endpoint. if ( int_status & USBD_INTEN_EP0SETUP_Msk ) { uint8_t const setup[8] = { NRF_USBD->BMREQUESTTYPE , NRF_USBD->BREQUEST, NRF_USBD->WVALUEL , NRF_USBD->WVALUEH, NRF_USBD->WINDEXL , NRF_USBD->WINDEXH , NRF_USBD->WLENGTHL, NRF_USBD->WLENGTHH }; // nrf5x hw auto handle set address, there is no need to inform usb stack tusb_control_request_t const * request = (tusb_control_request_t const *) setup; if ( !(TUSB_REQ_RCPT_DEVICE == request->bmRequestType_bit.recipient && TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && TUSB_REQ_SET_ADDRESS == request->bRequest) ) { dcd_event_setup_received(0, setup, true); } } //--------------------------------------------------------------------+ /* Control/Bulk/Interrupt (CBI) Transfer * * Data flow is: * (bus) (dma) * Host <-------> Endpoint <-------> RAM * * For CBI OUT: * - Host -> Endpoint * EPDATA (or EP0DATADONE) interrupted, check EPDATASTATUS.EPOUT[i] * to start DMA. This step can occur automatically (without sw), * which means data may or may not ready (data_received flag). * - Endpoint -> RAM * ENDEPOUT[i] interrupted, transaction complete, sw prepare next transaction * * For CBI IN: * - RAM -> Endpoint * ENDEPIN[i] interrupted indicate DMA is complete. HW will start * to move daat to host * - Endpoint -> Host * EPDATA (or EP0DATADONE) interrupted, check EPDATASTATUS.EPIN[i]. * Transaction is complete, sw prepare next transaction * * Note: in both Control In and Out of Data stage from Host <-> Endpoint * EP0DATADONE will be set as interrupt source */ //--------------------------------------------------------------------+ /* CBI OUT: Endpoint -> SRAM (aka transaction complete) * Note: Since nRF controller auto ACK next packet without SW awareness * We must handle this stage before Host -> Endpoint just in case * 2 event happens at once */ for(uint8_t epnum=0; epnum<8; epnum++) { if ( tu_bit_test(int_status, USBD_INTEN_ENDEPOUT0_Pos+epnum)) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); uint8_t const xact_len = NRF_USBD->EPOUT[epnum].AMOUNT; // Data in endpoint has been consumed xfer->data_received = false; // Transfer complete if transaction len < Max Packet Size or total len is transferred if ( (xact_len == xfer->mps) && (xfer->actual_len < xfer->total_len) ) { // Prepare for next transaction xact_out_prepare(epnum); }else { xfer->total_len = xfer->actual_len; // CBI OUT complete dcd_event_xfer_complete(0, epnum, xfer->actual_len, XFER_RESULT_SUCCESS, true); } } // Ended event for CBI IN : nothing to do } // Endpoint <-> Host if ( int_status & (USBD_INTEN_EPDATA_Msk | USBD_INTEN_EP0DATADONE_Msk) ) { uint32_t data_status = NRF_USBD->EPDATASTATUS; NRF_USBD->EPDATASTATUS = data_status; __ISB(); __DSB(); // EP0DATADONE is set with either Control Out on IN Data // Since EPDATASTATUS cannot be used to determine whether it is control OUT or IN. // We will use BMREQUESTTYPE in setup packet to determine the direction bool const is_control_in = (int_status & USBD_INTEN_EP0DATADONE_Msk) && (NRF_USBD->BMREQUESTTYPE & TUSB_DIR_IN_MASK); bool const is_control_out = (int_status & USBD_INTEN_EP0DATADONE_Msk) && !(NRF_USBD->BMREQUESTTYPE & TUSB_DIR_IN_MASK); // CBI In: Endpoint -> Host (transaction complete) for(uint8_t epnum=0; epnum<8; epnum++) { if ( tu_bit_test(data_status, epnum ) || ( epnum == 0 && is_control_in) ) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_IN); xfer->actual_len += NRF_USBD->EPIN[epnum].MAXCNT; if ( xfer->actual_len < xfer->total_len ) { // prepare next transaction xact_in_prepare(epnum); } else { // CBI IN complete dcd_event_xfer_complete(0, epnum | TUSB_DIR_IN_MASK, xfer->actual_len, XFER_RESULT_SUCCESS, true); } } } // CBI OUT: Host -> Endpoint for(uint8_t epnum=0; epnum<8; epnum++) { if ( tu_bit_test(data_status, 16+epnum ) || ( epnum == 0 && is_control_out) ) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); if (xfer->actual_len < xfer->total_len) { xact_out_dma(epnum); }else { // Data overflow !!! Nah, nrf52840 will auto ACK OUT packet after DMA is done // Mark this endpoint with data received xfer->data_received = true; } } } } } //--------------------------------------------------------------------+ // HFCLK helper //--------------------------------------------------------------------+ #ifdef SOFTDEVICE_PRESENT // check if SD is present and enabled static bool is_sd_enabled(void) { uint8_t sd_en = false; (void) sd_softdevice_is_enabled(&sd_en); return sd_en; } #endif static bool hfclk_running(void) { #ifdef SOFTDEVICE_PRESENT if ( is_sd_enabled() ) { uint32_t is_running; (void) sd_clock_hfclk_is_running(&is_running); return (is_running ? true : false); } #endif return nrf_clock_hf_is_running(NRF_CLOCK, NRF_CLOCK_HFCLK_HIGH_ACCURACY); } static void hfclk_enable(void) { // already running, nothing to do if ( hfclk_running() ) return; #ifdef SOFTDEVICE_PRESENT if ( is_sd_enabled() ) { (void)sd_clock_hfclk_request(); return; } #endif nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_HFCLKSTARTED); nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTART); } static void hfclk_disable(void) { #ifdef SOFTDEVICE_PRESENT if ( is_sd_enabled() ) { (void)sd_clock_hfclk_release(); return; } #endif nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTOP); } // Power & Clock Peripheral on nRF5x to manage USB // // USB Bus power is managed by Power module, there are 3 VBUS power events: // Detected, Ready, Removed. Upon these power events, This function will // enable ( or disable ) usb & hfclk peripheral, set the usb pin pull up // accordingly to the controller Startup/Standby Sequence in USBD 51.4 specs. // // Therefore this function must be called to handle USB power event by // - nrfx_power_usbevt_init() : if Softdevice is not used or enabled // - SoftDevice SOC event : if SD is used and enabled void tusb_hal_nrf_power_event (uint32_t event) { // Value is chosen to be as same as NRFX_POWER_USB_EVT_* in nrfx_power.h enum { USB_EVT_DETECTED = 0, USB_EVT_REMOVED = 1, USB_EVT_READY = 2 }; switch ( event ) { case USB_EVT_DETECTED: if ( !NRF_USBD->ENABLE ) { /* Prepare for READY event receiving */ NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; __ISB(); __DSB(); // for sync /* Enable the peripheral */ // ERRATA 171, 187, 166 if ( nrfx_usbd_errata_187() ) { // CRITICAL_REGION_ENTER(); 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; } // CRITICAL_REGION_EXIT(); } if ( nrfx_usbd_errata_171() ) { // CRITICAL_REGION_ENTER(); 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; } // CRITICAL_REGION_EXIT(); } NRF_USBD->ENABLE = 1; __ISB(); __DSB(); // for sync // Enable HFCLK hfclk_enable(); } break; case USB_EVT_READY: /* Waiting for USBD peripheral enabled */ while ( !(USBD_EVENTCAUSE_READY_Msk & NRF_USBD->EVENTCAUSE) ) { } NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; __ISB(); __DSB(); // for sync if ( nrfx_usbd_errata_171() ) { // CRITICAL_REGION_ENTER(); 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; } // CRITICAL_REGION_EXIT(); } if ( nrfx_usbd_errata_187() ) { // CRITICAL_REGION_ENTER(); 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; } // CRITICAL_REGION_EXIT(); } if ( nrfx_usbd_errata_166() ) { *((volatile uint32_t *) (NRF_USBD_BASE + 0x800)) = 0x7E3; *((volatile uint32_t *) (NRF_USBD_BASE + 0x804)) = 0x40; __ISB(); __DSB(); } // ISO buffer Lower half for IN, upper half for OUT NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; // Enable interrupt NRF_USBD->INTENSET = USBD_INTEN_USBRESET_Msk | USBD_INTEN_EPDATA_Msk | USBD_INTEN_EP0SETUP_Msk | USBD_INTEN_EP0DATADONE_Msk | USBD_INTEN_ENDEPIN0_Msk | USBD_INTEN_ENDEPOUT0_Msk; // Enable interrupt, priorities should be set by application NVIC_ClearPendingIRQ(USBD_IRQn); NVIC_EnableIRQ(USBD_IRQn); // Wait for HFCLK while ( !hfclk_running() ) { } // Enable pull up NRF_USBD->USBPULLUP = 1; __ISB(); __DSB(); // for sync break; case USB_EVT_REMOVED: if ( NRF_USBD->ENABLE ) { // Abort all transfers // Disable pull up NRF_USBD->USBPULLUP = 0; __ISB(); __DSB(); // for sync // Disable Interrupt NVIC_DisableIRQ(USBD_IRQn); // disable all interrupt NRF_USBD->INTENCLR = NRF_USBD->INTEN; NRF_USBD->ENABLE = 0; __ISB(); __DSB(); // for sync hfclk_disable(); dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); } break; default: break; } } #endif