From 96da1ca4b86f58074b741c8a2437c2fd8be2b867 Mon Sep 17 00:00:00 2001 From: Jerzy Kasenberg Date: Mon, 21 Sep 2020 15:53:15 +0200 Subject: [PATCH] nrf5x: Add support for ISO endpoints ISO endpoints were not covered so far by the driver code. This adds support for ISO IN and OUT endpoint handling. Registers for ISO IN(OUT) endpoints are placed just after normal IN(OUT) so in some cases common code could be used for handling all type of transfers. Generally code synchronizes ISO endpoint handling to SOF interrupt. This code does not change the way of how non-ISO endpoints are treated. Code uses strategy outlined in nRF52840 Produce Specification v1.0 sections 6.35.11.1 and 6.35.11.2. --- src/portable/nordic/nrf5x/dcd_nrf5x.c | 152 ++++++++++++++++++++++---- 1 file changed, 129 insertions(+), 23 deletions(-) diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c index a78b74d62..b85d3776f 100644 --- a/src/portable/nordic/nrf5x/dcd_nrf5x.c +++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c @@ -53,6 +53,9 @@ enum enum { + // Endpoint number is fixed (8) for ISOOUT and ISOIN. + EP_ISO_NUM = 8, + // CBI endpoints count EP_COUNT = 8 }; @@ -67,6 +70,9 @@ typedef struct // nrf52840 will auto ACK OUT packet after DMA is done // indicate packet is already ACK volatile bool data_received; + // Set to true when data was transferred from RAM to ISO IN output buffer. + // New data can be put in ISO IN output buffer after SOF. + bool iso_in_transfer_ready; } xfer_td_t; @@ -74,7 +80,8 @@ typedef struct static struct { // All 8 endpoints including control IN & OUT (offset 1) - xfer_td_t xfer[EP_COUNT][2]; + // +1 for ISO endpoints + xfer_td_t xfer[EP_COUNT + 1][2]; // Number of pending DMA that is started but not handled yet by dcd_int_handler(). // Since nRF can only carry one DMA can run at a time, this value is normally be either 0 or 1. @@ -173,6 +180,7 @@ static void xact_out_prepare(uint8_t epnum) { // Write zero value to SIZE register will allow hw to ACK (accept data) // If it is not already done by DMA + // SIZE.ISOOUT can also be accessed this way NRF_USBD->SIZE.EPOUT[epnum] = 0; } @@ -183,15 +191,32 @@ static void xact_out_prepare(uint8_t epnum) static void xact_out_dma(uint8_t epnum) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); + uint32_t xact_len; - uint8_t const xact_len = NRF_USBD->SIZE.EPOUT[epnum]; + if (epnum == EP_ISO_NUM) + { + xact_len = NRF_USBD->SIZE.ISOOUT; + // If ZERO bit is set, ignore ISOOUT length + if (xact_len & USBD_SIZE_ISOOUT_ZERO_Msk) xact_len = 0; + else + { + // Trigger DMA move data from Endpoint -> SRAM + NRF_USBD->ISOOUT.PTR = (uint32_t) xfer->buffer; + NRF_USBD->ISOOUT.MAXCNT = xact_len; - // 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_STARTISOOUT); + } + } + else + { + xact_len = (uint8_t)NRF_USBD->SIZE.EPOUT[epnum]; - edpt_dma_start(&NRF_USBD->TASKS_STARTEPOUT[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; } @@ -296,14 +321,44 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) _dcd.xfer[epnum][dir].mps = desc_edpt->wMaxPacketSize.size; - if ( dir == TUSB_DIR_OUT ) + if (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS) { - NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); - NRF_USBD->EPOUTEN |= TU_BIT(epnum); - }else + 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); + } + } + else { - NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); - NRF_USBD->EPINEN |= TU_BIT(epnum); + TU_ASSERT(epnum == EP_ISO_NUM); + if (dir == TUSB_DIR_OUT) + { + // SPLIT ISO buffer when ISO IN endpoint is already opened. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + // Clear old events + NRF_USBD->EVENTS_ENDISOOUT = 0; + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + // Enable SOF and ISOOUT interrupts, and ISOOUT endpoint. + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOOUT_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPOUTEN |= USBD_EPOUTEN_ISOOUT_Msk; + } + else + { + NRF_USBD->EVENTS_ENDISOIN = 0; + // SPLIT ISO buffer when ISO OUT endpoint is already opened. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + // Enable SOF and ISOIN interrupts, and ISOIN endpoint. + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOIN_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPINEN |= USBD_EPINEN_ISOIN_Msk; + } } __ISB(); __DSB(); @@ -317,16 +372,39 @@ void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - // CBI - if (dir == TUSB_DIR_OUT) + if (epnum != EP_ISO_NUM) { - NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); - NRF_USBD->EPOUTEN &= ~TU_BIT(epnum); + // CBI + if (dir == TUSB_DIR_OUT) + { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); + NRF_USBD->EPOUTEN &= ~TU_BIT(epnum); + } + else + { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); + NRF_USBD->EPINEN &= ~TU_BIT(epnum); + } } else { - NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); - NRF_USBD->EPINEN &= ~TU_BIT(epnum); + _dcd.xfer[EP_ISO_NUM][dir].mps = 0; + // ISO + if (dir == TUSB_DIR_OUT) + { + NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOOUT_Msk; + NRF_USBD->EPOUTEN &= ~USBD_EPOUTEN_ISOOUT_Msk; + NRF_USBD->EVENTS_ENDISOOUT = 0; + } + else + { + NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOIN_Msk; + NRF_USBD->EPINEN &= ~USBD_EPINEN_ISOIN_Msk; + } + // One of the ISO endpoints closed, no need to split buffers any more. + NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_OneDir; + // When both ISO endpoint are close there is no need for SOF any more. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps + _dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps == 0) NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; } __ISB(); __DSB(); } @@ -382,11 +460,12 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) { (void) rhport; + uint8_t const epnum = tu_edpt_number(ep_addr); - if ( tu_edpt_number(ep_addr) == 0 ) + if ( epnum == 0 ) { NRF_USBD->TASKS_EP0STALL = 1; - }else + }else if (epnum != EP_ISO_NUM) { NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_Stall << USBD_EPSTALL_STALL_Pos) | ep_addr; } @@ -397,8 +476,9 @@ void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) { (void) rhport; + uint8_t const epnum = tu_edpt_number(ep_addr); - if ( tu_edpt_number(ep_addr) ) + if ( epnum != 0 && epnum != EP_ISO_NUM ) { // clear stall NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep_addr; @@ -456,8 +536,31 @@ void dcd_int_handler(uint8_t rhport) dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); } + // ISOIN: Data was moved to endpoint buffer, client will be notified in SOF + if ( int_status & USBD_INTEN_ENDISOIN_Msk ) + { + xfer_td_t* xfer = get_td(EP_ISO_NUM, TUSB_DIR_IN); + + xfer->actual_len = NRF_USBD->ISOIN.AMOUNT; + // Data transferred from RAM to endpoint output buffer. + // Next transfer can be scheduled after SOF. + xfer->iso_in_transfer_ready = true; + } + if ( int_status & USBD_INTEN_SOF_Msk ) { + // ISOOUT: Transfer data gathered in previous frame from buffer to RAM + if (NRF_USBD->EPOUTEN & USBD_EPOUTEN_ISOOUT_Msk) + { + xact_out_dma(EP_ISO_NUM); + } + // ISOIN: Notify client that data was transferred + xfer_td_t* xfer = get_td(EP_ISO_NUM, TUSB_DIR_IN); + if ( xfer->iso_in_transfer_ready ) + { + xfer->iso_in_transfer_ready = false; + dcd_event_xfer_complete(0, EP_ISO_NUM | TUSB_DIR_IN_MASK, xfer->actual_len, XFER_RESULT_SUCCESS, true); + } dcd_event_bus_signal(0, DCD_EVENT_SOF, true); } @@ -539,8 +642,11 @@ void dcd_int_handler(uint8_t rhport) * 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 + * ISO OUT: Transaction must fit in single packed, it can be shorter then total + * len if Host decides to sent fewer bytes, it this case transaction is also + * complete and next transfer is not initiated here like for CBI. */ - for(uint8_t epnum=0; epnum<8; epnum++) + for(uint8_t epnum=0; epnumdata_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) ) + if ( (epnum != EP_ISO_NUM) && (xact_len == xfer->mps) && (xfer->actual_len < xfer->total_len) ) { // Prepare for next transaction xact_out_prepare(epnum);