summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/portable/dialog/da146xx/dcd_da146xx.c258
1 files changed, 203 insertions, 55 deletions
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)
{
- EPx_REGS *regs = xfer->regs;
+ // 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 - 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;
-
- 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
+ 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
+ {
+ // 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;
-
- if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_ERR))
- {
- regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk;
- }
- else
+ do
{
- 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))
+ rxs = regs->rxs;
+
+ 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
{