DA146xx: Allow receiving of packets larger then 64 bytes

Internal FIFO for each endpoint is limited to 64 bytes.
It is possible to have longer packets if respective FIFO
is read during actual packet transmission.
This change updates receive data path to allow packets (and
endpoint size) larger then 64 bytes.
If DMA is not used yet DMA is setup for reception of big packets.
If DMA is already assigned to some transfer, code enables FIFO level
warning interrupts and tries to read data before FIFO is filled up.
This commit is contained in:
Jerzy Kasenberg 2020-10-01 11:49:14 +02:00
parent 8b2c822557
commit 9edf4334c4
1 changed files with 203 additions and 55 deletions

View File

@ -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
{