diff --git a/src/portable/dialog/da146xx/dcd_da146xx.c b/src/portable/dialog/da146xx/dcd_da146xx.c index 602836e3a..9cc02e821 100644 --- a/src/portable/dialog/da146xx/dcd_da146xx.c +++ b/src/portable/dialog/da146xx/dcd_da146xx.c @@ -40,6 +40,24 @@ // We disable SOF for now until needed later on #define USE_SOF 0 +// Size of RX or TX FIFO. +#define FIFO_SIZE 64 + +#ifndef TU_DA1469X_FIFO_READ_THRESHOLD +// RX FIFO is 64 bytes. When endpoint size is greater then 64, FIFO warning interrupt +// is enabled to allow read incoming data during frame reception. +// It is possible to stay in interrupt reading whole packet at once, but it may be +// more efficient for MCU to read as much data as possible and when FIFO is hardly +// filled exit interrupt handler waiting for next FIFO warning level interrupt +// or packet end. +// When running at 96MHz code that reads FIFO based on number of bytes stored in +// USB_RXSx_REG.USB_RXCOUNT takes enough time to fill FIFO with two additional bytes. +// Settings this threshold above this allows to leave interrupt handler and wait +// for more bytes to before next ISR. This allows reduce overall ISR time to 1/3 +// of time that would be needed if ISR read as fast as possible. +#define TU_DA1469X_FIFO_READ_THRESHOLD 4 +#endif + #define EP_MAX 4 #define NFSR_NODE_RESET 0 @@ -119,6 +137,54 @@ typedef struct #define EP_REGS(first_ep_reg) (EPx_REGS*)(&USB->first_ep_reg) +// DMA channel pair to use, channel 6 will be used for RX channel 7 for TX direction. +#ifndef TU_DA146XX_DMA_RX_CHANNEL +#define TU_DA146XX_DMA_RX_CHANNEL 6 +#endif +#define DA146XX_DMA_USB_MUX (0x6 << (TU_DA146XX_DMA_RX_CHANNEL * 2)) +#define DA146XX_DMA_USB_MUX_MASK (0xF << (TU_DA146XX_DMA_RX_CHANNEL * 2)) + +typedef struct +{ + __IOM uint32_t DMAx_A_START_REG; + __IOM uint32_t DMAx_B_START_REG; + __IOM uint32_t DMAx_INT_REG; + __IOM uint32_t DMAx_LEN_REG; + __IOM uint32_t DMAx_CTRL_REG; + __IOM uint32_t DMAx_IDX_REG; + __IM uint32_t RESERVED[2]; // Extend structure size for array like usage, registers for each channel are 0x20 bytes apart. +} da146xx_dma_channel_t; + +#define DMA_CHANNEL_REGS(n) ((da146xx_dma_channel_t *)(DMA) + n) +#define RX_DMA_REGS DMA_CHANNEL_REGS(TU_DA146XX_DMA_RX_CHANNEL) +#define TX_DMA_REGS DMA_CHANNEL_REGS((TU_DA146XX_DMA_RX_CHANNEL) + 1) + +#define RX_DMA_START ((1 << DMA_DMA0_CTRL_REG_DMA_ON_Pos) |\ + (0 << DMA_DMA0_CTRL_REG_BW_Pos) | \ + (1 << DMA_DMA0_CTRL_REG_DREQ_MODE_Pos) | \ + (1 << DMA_DMA0_CTRL_REG_BINC_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_AINC_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_CIRCULAR_Pos) | \ + (2 << DMA_DMA0_CTRL_REG_DMA_PRIO_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_DMA_IDLE_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_DMA_INIT_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_REQ_SENSE_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_BURST_MODE_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_BUS_ERROR_DETECT_Pos)) + +#define TX_DMA_START ((1 << DMA_DMA0_CTRL_REG_DMA_ON_Pos) |\ + (0 << DMA_DMA0_CTRL_REG_BW_Pos) | \ + (1 << DMA_DMA0_CTRL_REG_DREQ_MODE_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_BINC_Pos) | \ + (1 << DMA_DMA0_CTRL_REG_AINC_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_CIRCULAR_Pos) | \ + (2 << DMA_DMA0_CTRL_REG_DMA_PRIO_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_DMA_IDLE_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_DMA_INIT_Pos) | \ + (1 << DMA_DMA0_CTRL_REG_REQ_SENSE_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_BURST_MODE_Pos) | \ + (0 << DMA_DMA0_CTRL_REG_BUS_ERROR_DETECT_Pos)) + // Dialog register fields and bit mask are very long. Filed masks repeat register names. // Those convenience macros are a way to reduce complexity of register modification lines. #define GET_BIT(val, field) (val & field ## _Msk) >> field ## _Pos @@ -151,6 +217,8 @@ static struct bool vbus_present; bool in_reset; xfer_ctl_t xfer_status[EP_MAX][2]; + // Endpoints that use DMA, one for each direction + uint8_t dma_ep[2]; } _dcd = { .vbus_present = false, @@ -258,35 +326,93 @@ static void transmit_packet(xfer_ctl_t * xfer) regs->txc = txc; } -static void receive_packet(xfer_ctl_t *xfer, uint16_t bytes_in_fifo) +static bool try_allocate_dma(uint8_t epnum, uint8_t dir) +{ + // TODO: Disable interrupts while checking + if (_dcd.dma_ep[dir] == 0) + { + _dcd.dma_ep[dir] = epnum; + if (dir == TUSB_DIR_OUT) + USB->USB_DMA_CTRL_REG = (USB->USB_DMA_CTRL_REG & ~USB_USB_DMA_CTRL_REG_USB_DMA_RX_Msk) | + ((epnum - 1) << USB_USB_DMA_CTRL_REG_USB_DMA_RX_Pos); + else + USB->USB_DMA_CTRL_REG = (USB->USB_DMA_CTRL_REG & ~USB_USB_DMA_CTRL_REG_USB_DMA_TX_Msk) | + ((epnum - 1) << USB_USB_DMA_CTRL_REG_USB_DMA_TX_Pos); + USB->USB_DMA_CTRL_REG |= USB_USB_DMA_CTRL_REG_USB_DMA_EN_Msk; + } + return _dcd.dma_ep[dir] == epnum; +} + +static void start_rx_dma(volatile void *src, void *dst, uint16_t size) +{ + // Setup SRC and DST registers + RX_DMA_REGS->DMAx_A_START_REG = (uint32_t)src; + RX_DMA_REGS->DMAx_B_START_REG = (uint32_t)dst; + // Don't need DMA interrupt, read end is determined by RX_LAST or RX_ERR events. + RX_DMA_REGS->DMAx_INT_REG = size - 1; + RX_DMA_REGS->DMAx_LEN_REG = size - 1; + RX_DMA_REGS->DMAx_CTRL_REG = RX_DMA_START; +} + +static void start_rx_packet(xfer_ctl_t *xfer) +{ + uint8_t const epnum = tu_edpt_number(xfer->ep_addr); + uint16_t remaining = xfer->total_len - xfer->transferred; + uint16_t size = tu_min16(remaining, xfer->max_packet_size); + + xfer->last_packet_size = 0; + if (xfer->max_packet_size > FIFO_SIZE && remaining > FIFO_SIZE) + { + if (try_allocate_dma(epnum, TUSB_DIR_OUT)) + { + start_rx_dma(&xfer->regs->rxd, xfer->buffer + xfer->transferred, size); + } + else + { + // Other endpoint is using DMA in that direction, fall back to interrupts. + // For endpoint size greater then FIFO size enable FIFO level warning interrupt + // when FIFO has less then 17 bytes free. + xfer->regs->rxc |= USB_USB_RXC1_REG_USB_RFWL_Msk; + USB->USB_FWMSK_REG |= 1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos); + } + } + else if (epnum != 0) + { + // If max_packet_size would fit in FIFO no need for FIFO level warning interrupt. + xfer->regs->rxc &= ~USB_USB_RXC1_REG_USB_RFWL_Msk; + USB->USB_FWMSK_REG &= ~(1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos)); + } + xfer->regs->rxc |= USB_USB_RXC1_REG_USB_RX_EN_Msk; +} + +static void read_rx_fifo(xfer_ctl_t *xfer, uint16_t bytes_in_fifo) { EPx_REGS *regs = xfer->regs; - uint16_t remaining = xfer->total_len - xfer->transferred; + uint16_t remaining = xfer->total_len - xfer->transferred - xfer->last_packet_size; uint16_t receive_this_time = bytes_in_fifo; - if (remaining <= bytes_in_fifo) receive_this_time = remaining; + if (remaining < bytes_in_fifo) receive_this_time = remaining; uint8_t *buf = xfer->buffer + xfer->transferred + xfer->last_packet_size; for (int i = 0; i < receive_this_time; ++i) buf[i] = regs->rxd; - xfer->transferred += receive_this_time; xfer->last_packet_size += receive_this_time; } static void handle_ep0_rx(void) { - int packet_size; + int fifo_bytes; uint32_t rxs0 = USB->USB_RXS0_REG; xfer_ctl_t *xfer = XFER_CTL_BASE(0, TUSB_DIR_OUT); - packet_size = GET_BIT(rxs0, USB_USB_RXS0_REG_USB_RCOUNT); + fifo_bytes = GET_BIT(rxs0, USB_USB_RXS0_REG_USB_RCOUNT); if (rxs0 & USB_USB_RXS0_REG_USB_SETUP_Msk) { xfer_ctl_t *xfer_in = XFER_CTL_BASE(0, TUSB_DIR_IN); // Setup packet is in - for (int i = 0; i < packet_size; ++i) _setup_packet[i] = USB->USB_RXD0_REG; + for (int i = 0; i < fifo_bytes; ++i) _setup_packet[i] = USB->USB_RXD0_REG; xfer->stall = 0; xfer->data1 = 1; @@ -302,21 +428,26 @@ static void handle_ep0_rx(void) { // Toggle bit does not match discard packet REG_SET_BIT(USB_RXC0_REG, USB_FLUSH); + xfer->last_packet_size = 0; } else { - receive_packet(xfer, packet_size); - xfer->data1 ^= 1; + read_rx_fifo(xfer, fifo_bytes); + if (rxs0 & USB_USB_RXS0_REG_USB_RX_LAST_Msk) + { + xfer->transferred += xfer->last_packet_size; + xfer->data1 ^= 1; - if (xfer->total_len == xfer->transferred || xfer->last_packet_size < xfer->max_packet_size) - { - dcd_event_xfer_complete(0, 0, xfer->transferred, XFER_RESULT_SUCCESS, true); - } - else - { + if (xfer->total_len == xfer->transferred || xfer->last_packet_size < xfer->max_packet_size) + { + dcd_event_xfer_complete(0, 0, xfer->transferred, XFER_RESULT_SUCCESS, true); + } + else + { + // Re-enable reception + REG_SET_BIT(USB_RXC0_REG, USB_RX_EN); + } xfer->last_packet_size = 0; - // Re-enable reception - REG_SET_BIT(USB_RXC0_REG, USB_RX_EN); } } } @@ -357,44 +488,72 @@ static void handle_ep0_tx(void) static void handle_epx_rx_ev(uint8_t ep) { uint32_t rxs; - int packet_size; + int fifo_bytes; xfer_ctl_t *xfer = XFER_CTL_BASE(ep, TUSB_DIR_OUT); EPx_REGS *regs = xfer->regs; - rxs = regs->rxs; + do + { + rxs = regs->rxs; - if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_ERR)) - { - regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; - } - else - { - packet_size = GET_BIT(rxs, USB_USB_RXS1_REG_USB_RXCOUNT); - receive_packet(xfer, packet_size); - if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_LAST)) + if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_ERR)) { - if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_TOGGLE_RX) != xfer->data1) + regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; + xfer->last_packet_size = 0; + if (_dcd.dma_ep[TUSB_DIR_OUT] == ep) { - // Toggle bit does not match discard packet - regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; + // Stop DMA + RX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk; + // Restart DMA since packet was dropped, all parameters should still work. + RX_DMA_REGS->DMAx_CTRL_REG |= DMA_DMA0_CTRL_REG_DMA_ON_Msk; } - else + break; + } + else + { + if (_dcd.dma_ep[TUSB_DIR_OUT] == ep) { - xfer->data1 ^= 1; - if (xfer->total_len == xfer->transferred || xfer->last_packet_size < xfer->max_packet_size) + // Disable DMA and update last_packet_size with what DMA reported. + RX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk; + xfer->last_packet_size = RX_DMA_REGS->DMAx_IDX_REG; + // When DMA did not finished (packet was smaller then MPS), DMAx_IDX_REG holds exact number of bytes transmitted. + // When DMA finished value in DMAx_IDX_REG is one less then actual number of transmitted bytes. + if (xfer->last_packet_size == RX_DMA_REGS->DMAx_LEN_REG) xfer->last_packet_size++; + // Release DMA to use by other endpoints. + _dcd.dma_ep[TUSB_DIR_OUT] = 0; + } + fifo_bytes = GET_BIT(rxs, USB_USB_RXS1_REG_USB_RXCOUNT); + // FIFO maybe empty if DMA read it before or it's final iteration and function already read all that was to read. + if (fifo_bytes > 0) + { + read_rx_fifo(xfer, fifo_bytes); + } + if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_LAST)) + { + if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_TOGGLE_RX) != xfer->data1) { - dcd_event_xfer_complete(0, xfer->ep_addr, xfer->transferred, XFER_RESULT_SUCCESS, true); + // Toggle bit does not match discard packet + regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; } else { - xfer->last_packet_size = 0; - // Re-enable reception - regs->rxc |= USB_USB_RXC1_REG_USB_RX_EN_Msk; + xfer->data1 ^= 1; + xfer->transferred += xfer->last_packet_size; + if (xfer->total_len == xfer->transferred || xfer->last_packet_size < xfer->max_packet_size) + { + dcd_event_xfer_complete(0, xfer->ep_addr, xfer->transferred, XFER_RESULT_SUCCESS, true); + } + else + { + // Re-enable reception + start_rx_packet(xfer); + } } + xfer->last_packet_size = 0; } } - } + } while (fifo_bytes > TU_DA1469X_FIFO_READ_THRESHOLD); } static void handle_rx_ev(void) @@ -459,6 +618,7 @@ static void handle_bus_reset(void) _dcd.in_reset = true; dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); + USB->USB_DMA_CTRL_REG = 0; USB->USB_MAMSK_REG = USB_USB_MAMSK_REG_USB_M_INTR_Msk | #if USE_SOF @@ -595,6 +755,9 @@ void dcd_connect(uint8_t rhport) (void)rhport; REG_SET_BIT(USB_MCTRL_REG, USB_NAT); + + // Select chosen DMA to be triggered by USB. + DMA->DMA_REQ_MUX_REG = (DMA->DMA_REQ_MUX_REG & ~DA146XX_DMA_USB_MUX_MASK) | DA146XX_DMA_USB_MUX; } void dcd_disconnect(uint8_t rhport) @@ -666,22 +829,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t if (dir == TUSB_DIR_OUT) { - if (epnum != 0) - { - if (xfer->max_packet_size > 64) - { - // For endpoint size greater then FIFO size enable FIFO level warning interrupt - // when FIFO has less then 17 bytes free. - xfer->regs->rxc |= USB_USB_RXC1_REG_USB_RFWL_Msk; - } - else - { - // If max_packet_size would fit in FIFO no need for FIFO level warning interrupt. - xfer->regs->rxc &= ~USB_USB_RXC1_REG_USB_RFWL_Msk; - } - } - // USB_RX_EN bit is in same place for all endpoints. - xfer->regs->rxc = USB_USB_RXC0_REG_USB_RX_EN_Msk; + start_rx_packet(xfer); } else // IN {