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.
This commit is contained in:
Jerzy Kasenberg 2020-09-21 15:53:15 +02:00
parent 6f5ee09511
commit 96da1ca4b8
1 changed files with 129 additions and 23 deletions

View File

@ -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; epnum<EP_COUNT+1; epnum++)
{
if ( tu_bit_test(int_status, USBD_INTEN_ENDEPOUT0_Pos+epnum))
{
@ -551,7 +657,7 @@ void dcd_int_handler(uint8_t rhport)
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) )
if ( (epnum != EP_ISO_NUM) && (xact_len == xfer->mps) && (xfer->actual_len < xfer->total_len) )
{
// Prepare for next transaction
xact_out_prepare(epnum);