mirror of
https://github.com/hathach/tinyusb.git
synced 2025-01-17 05:32:55 +08:00
hcd able to send setup packet
This commit is contained in:
parent
063661e3a3
commit
07abc722b6
@ -94,7 +94,7 @@ static void dma_setup_prepare(uint8_t rhport) {
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
|
||||
/* USB Data FIFO Layout
|
||||
/* Device Data FIFO scheme
|
||||
|
||||
The FIFO is split up into
|
||||
- EPInfo: for storing DMA metadata, only required when use DMA. Maximum size is called
|
||||
@ -109,7 +109,7 @@ static void dma_setup_prepare(uint8_t rhport) {
|
||||
possible since the free space is located between the RX and TX FIFOs.
|
||||
|
||||
---------------- ep_fifo_size
|
||||
| EPInfo DMA |
|
||||
| DxEPIDMAn |
|
||||
|-------------|-- gdfifocfg.EPINFOBASE (max is ghwcfg3.dfifo_depth)
|
||||
| IN FIFO 0 | control EP
|
||||
|-------------|
|
||||
@ -167,7 +167,7 @@ static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size) {
|
||||
}
|
||||
|
||||
// If The TXFELVL is configured as half empty, the fifo must be twice the max_size.
|
||||
if ((dwc2->gahbcfg & GAHBCFG_TXFELVL) == 0) {
|
||||
if ((dwc2->gahbcfg & GAHBCFG_TX_FIFO_EPMTY_LVL) == 0) {
|
||||
fifo_size *= 2;
|
||||
}
|
||||
|
||||
@ -205,59 +205,10 @@ static void dfifo_device_init(uint8_t rhport) {
|
||||
dfifo_alloc(rhport, 0x80, CFG_TUD_ENDPOINT0_SIZE);
|
||||
}
|
||||
|
||||
// Read a single data packet from receive FIFO
|
||||
static void dfifo_read_packet(uint8_t rhport, uint8_t* dst, uint16_t len) {
|
||||
(void) rhport;
|
||||
|
||||
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
|
||||
volatile const uint32_t* rx_fifo = dwc2->fifo[0];
|
||||
|
||||
// Reading full available 32 bit words from fifo
|
||||
uint16_t full_words = len >> 2;
|
||||
while (full_words--) {
|
||||
tu_unaligned_write32(dst, *rx_fifo);
|
||||
dst += 4;
|
||||
}
|
||||
|
||||
// Read the remaining 1-3 bytes from fifo
|
||||
uint8_t const bytes_rem = len & 0x03;
|
||||
if (bytes_rem != 0) {
|
||||
uint32_t const tmp = *rx_fifo;
|
||||
dst[0] = tu_u32_byte0(tmp);
|
||||
if (bytes_rem > 1) dst[1] = tu_u32_byte1(tmp);
|
||||
if (bytes_rem > 2) dst[2] = tu_u32_byte2(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// Write a single data packet to EPIN FIFO
|
||||
static void dfifo_write_packet(uint8_t rhport, uint8_t fifo_num, uint8_t const* src, uint16_t len) {
|
||||
(void) rhport;
|
||||
|
||||
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
|
||||
volatile uint32_t* tx_fifo = dwc2->fifo[fifo_num];
|
||||
|
||||
// Pushing full available 32 bit words to fifo
|
||||
uint16_t full_words = len >> 2;
|
||||
while (full_words--) {
|
||||
*tx_fifo = tu_unaligned_read32(src);
|
||||
src += 4;
|
||||
}
|
||||
|
||||
// Write the remaining 1-3 bytes into fifo
|
||||
uint8_t const bytes_rem = len & 0x03;
|
||||
if (bytes_rem) {
|
||||
uint32_t tmp_word = src[0];
|
||||
if (bytes_rem > 1) tmp_word |= (src[1] << 8);
|
||||
if (bytes_rem > 2) tmp_word |= (src[2] << 16);
|
||||
|
||||
*tx_fifo = tmp_word;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Endpoint
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
static void edpt_activate(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) {
|
||||
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
|
||||
uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress);
|
||||
@ -404,7 +355,7 @@ static void edpt_schedule_packets(uint8_t rhport, uint8_t const epnum, uint8_t c
|
||||
if (dir == TUSB_DIR_IN) {
|
||||
// A full IN transfer (multiple packets, possibly) triggers XFRC.
|
||||
dep->dieptsiz = (num_packets << DIEPTSIZ_PKTCNT_Pos) |
|
||||
((total_bytes << DIEPTSIZ_XFRSIZ_Pos) & DIEPTSIZ_XFRSIZ_Msk);
|
||||
((total_bytes << DIEPTSIZ_XFRSIZ_Pos) & DIEPTSIZ_XFRSIZ_Msk);
|
||||
|
||||
if(dma_device_enabled(dwc2)) {
|
||||
dep->diepdma = (uintptr_t)xfer->buffer;
|
||||
@ -648,11 +599,10 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to
|
||||
// Schedule the first transaction for EP0 transfer
|
||||
edpt_schedule_packets(rhport, epnum, dir, 1, ep0_pending[dir]);
|
||||
} else {
|
||||
uint16_t num_packets = (total_bytes / xfer->max_size);
|
||||
uint16_t const short_packet_size = total_bytes % xfer->max_size;
|
||||
|
||||
// Zero-size packet is special case.
|
||||
if ((short_packet_size > 0) || (total_bytes == 0)) num_packets++;
|
||||
uint16_t num_packets = tu_div_ceil(total_bytes, xfer->max_size);
|
||||
if (num_packets == 0) {
|
||||
num_packets = 1; // zero length packet still count as 1
|
||||
}
|
||||
|
||||
// Schedule packets to be sent within interrupt
|
||||
edpt_schedule_packets(rhport, epnum, dir, num_packets, total_bytes);
|
||||
@ -758,7 +708,7 @@ static void handle_rxflvl_irq(uint8_t rhport) {
|
||||
tu_fifo_write_n_const_addr_full_words(xfer->ff, (const void*) (uintptr_t) rx_fifo, bcnt);
|
||||
} else {
|
||||
// Linear buffer
|
||||
dfifo_read_packet(rhport, xfer->buffer, bcnt);
|
||||
dfifo_read_packet(dwc2, xfer->buffer, bcnt);
|
||||
|
||||
// Increment pointer to xfer data
|
||||
xfer->buffer += bcnt;
|
||||
@ -863,8 +813,7 @@ static void handle_epout_irq(uint8_t rhport) {
|
||||
|
||||
static void handle_epin_irq(uint8_t rhport) {
|
||||
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
|
||||
uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
|
||||
dwc2_epin_t* epin = dwc2->epin;
|
||||
const uint8_t ep_count = _dwc2_controller[rhport].ep_count;
|
||||
|
||||
// DAINT for a given EP clears when DIEPINTx is cleared.
|
||||
// IEPINT will be cleared when DAINT's out bits are cleared.
|
||||
@ -872,9 +821,10 @@ static void handle_epin_irq(uint8_t rhport) {
|
||||
if (dwc2->daint & TU_BIT(DAINT_IEPINT_Pos + n)) {
|
||||
// IN XFER complete (entire xfer).
|
||||
xfer_ctl_t* xfer = XFER_CTL_BASE(n, TUSB_DIR_IN);
|
||||
dwc2_epin_t* epin = &dwc2->epin[n];
|
||||
|
||||
if (epin[n].diepint & DIEPINT_XFRC) {
|
||||
epin[n].diepint = DIEPINT_XFRC;
|
||||
if (epin->diepint & DIEPINT_XFRC) {
|
||||
epin->diepint = DIEPINT_XFRC;
|
||||
|
||||
// EP0 can only handle one packet
|
||||
if ((n == 0) && ep0_pending[TUSB_DIR_IN]) {
|
||||
@ -889,39 +839,38 @@ static void handle_epin_irq(uint8_t rhport) {
|
||||
}
|
||||
|
||||
// XFER FIFO empty
|
||||
if ((epin[n].diepint & DIEPINT_TXFE) && (dwc2->diepempmsk & (1 << n))) {
|
||||
if ((epin->diepint & DIEPINT_TXFE) && (dwc2->diepempmsk & (1 << n))) {
|
||||
// diepint's TXFE bit is read-only, software cannot clear it.
|
||||
// It will only be cleared by hardware when written bytes is more than
|
||||
// - 64 bytes or
|
||||
// - Half of TX FIFO size (configured by DIEPTXF)
|
||||
|
||||
uint16_t remaining_packets = (epin[n].dieptsiz & DIEPTSIZ_PKTCNT_Msk) >> DIEPTSIZ_PKTCNT_Pos;
|
||||
// - Half/Empty of TX FIFO size (configured by GAHBCFG.TXFELVL)
|
||||
const uint16_t remain_packets = epin->dieptsiz_bm.packet_count;
|
||||
|
||||
// Process every single packet (only whole packets can be written to fifo)
|
||||
for (uint16_t i = 0; i < remaining_packets; i++) {
|
||||
uint16_t const remaining_bytes = (epin[n].dieptsiz & DIEPTSIZ_XFRSIZ_Msk) >> DIEPTSIZ_XFRSIZ_Pos;
|
||||
for (uint16_t i = 0; i < remain_packets; i++) {
|
||||
const uint16_t remain_bytes = epin->dieptsiz_bm.xfer_size;
|
||||
|
||||
// Packet can not be larger than ep max size
|
||||
uint16_t const packet_size = tu_min16(remaining_bytes, xfer->max_size);
|
||||
const uint16_t packet_size = tu_min16(remain_bytes, xfer->max_size);
|
||||
|
||||
// It's only possible to write full packets into FIFO. Therefore DTXFSTS register of current
|
||||
// EP has to be checked if the buffer can take another WHOLE packet
|
||||
if (packet_size > ((epin[n].dtxfsts & DTXFSTS_INEPTFSAV_Msk) << 2)) break;
|
||||
if (packet_size > ((epin->dtxfsts & DTXFSTS_INEPTFSAV_Msk) << 2)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Push packet to Tx-FIFO
|
||||
if (xfer->ff) {
|
||||
volatile uint32_t* tx_fifo = dwc2->fifo[n];
|
||||
tu_fifo_read_n_const_addr_full_words(xfer->ff, (void*) (uintptr_t) tx_fifo, packet_size);
|
||||
} else {
|
||||
dfifo_write_packet(rhport, n, xfer->buffer, packet_size);
|
||||
|
||||
// Increment pointer to xfer data
|
||||
dfifo_write_packet(dwc2, n, xfer->buffer, packet_size);
|
||||
xfer->buffer += packet_size;
|
||||
}
|
||||
}
|
||||
|
||||
// Turn off TXFE if all bytes are written.
|
||||
if (((epin[n].dieptsiz & DIEPTSIZ_XFRSIZ_Msk) >> DIEPTSIZ_XFRSIZ_Pos) == 0) {
|
||||
if (epin->dieptsiz_bm.xfer_size == 0) {
|
||||
dwc2->diepempmsk &= ~(1 << n);
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +158,8 @@ static bool check_dwc2(dwc2_regs_t* dwc2) {
|
||||
// For some reason: GD32VF103 gsnpsid and all hwcfg register are always zero (skip it)
|
||||
(void)dwc2;
|
||||
#if !TU_CHECK_MCU(OPT_MCU_GD32VF103)
|
||||
uint32_t const gsnpsid = dwc2->gsnpsid & GSNPSID_ID_MASK;
|
||||
enum { GSNPSID_ID_MASK = TU_GENMASK(31, 16) };
|
||||
const uint32_t gsnpsid = dwc2->gsnpsid & GSNPSID_ID_MASK;
|
||||
TU_ASSERT(gsnpsid == DWC2_OTG_ID || gsnpsid == DWC2_FS_IOT_ID || gsnpsid == DWC2_HS_IOT_ID);
|
||||
#endif
|
||||
|
||||
@ -200,7 +201,7 @@ bool dwc2_core_init(uint8_t rhport, bool is_highspeed, bool is_dma) {
|
||||
TU_ASSERT(check_dwc2(dwc2));
|
||||
|
||||
// disable global interrupt
|
||||
// dwc2->gahbcfg &= ~GAHBCFG_GINT;
|
||||
dwc2->gahbcfg &= ~GAHBCFG_GINT;
|
||||
|
||||
if (is_highspeed) {
|
||||
phy_hs_init(dwc2);
|
||||
@ -241,8 +242,8 @@ bool dwc2_core_init(uint8_t rhport, bool is_highspeed, bool is_dma) {
|
||||
dwc2->gintmsk |= GINTMSK_RXFLVLM;
|
||||
}
|
||||
|
||||
// Configure TX FIFO empty level for interrupt. Default is complete empty
|
||||
dwc2->gahbcfg |= GAHBCFG_TXFELVL;
|
||||
// (non-periodic) TX FIFO empty level for interrupt is complete empty
|
||||
dwc2->gahbcfg |= GAHBCFG_TX_FIFO_EPMTY_LVL;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -260,4 +261,58 @@ bool dwc2_core_init(uint8_t rhport, bool is_highspeed, bool is_dma) {
|
||||
//
|
||||
// }
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// DFIFO
|
||||
//--------------------------------------------------------------------
|
||||
// Read a single data packet from receive DFIFO
|
||||
void dfifo_read_packet(dwc2_regs_t* dwc2, uint8_t* dst, uint16_t len) {
|
||||
const volatile uint32_t* rx_fifo = dwc2->fifo[0];
|
||||
|
||||
// Reading full available 32 bit words from fifo
|
||||
uint16_t word_count = len >> 2;
|
||||
while (word_count--) {
|
||||
tu_unaligned_write32(dst, *rx_fifo);
|
||||
dst += 4;
|
||||
}
|
||||
|
||||
// Read the remaining 1-3 bytes from fifo
|
||||
const uint8_t bytes_rem = len & 0x03;
|
||||
if (bytes_rem != 0) {
|
||||
const uint32_t tmp = *rx_fifo;
|
||||
dst[0] = tu_u32_byte0(tmp);
|
||||
if (bytes_rem > 1) {
|
||||
dst[1] = tu_u32_byte1(tmp);
|
||||
}
|
||||
if (bytes_rem > 2) {
|
||||
dst[2] = tu_u32_byte2(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write a single data packet to DFIFO
|
||||
void dfifo_write_packet(dwc2_regs_t* dwc2, uint8_t fifo_num, const uint8_t* src, uint16_t len) {
|
||||
volatile uint32_t* tx_fifo = dwc2->fifo[fifo_num];
|
||||
|
||||
// Pushing full available 32 bit words to fifo
|
||||
uint16_t word_count = len >> 2;
|
||||
while (word_count--) {
|
||||
*tx_fifo = tu_unaligned_read32(src);
|
||||
src += 4;
|
||||
}
|
||||
|
||||
// Write the remaining 1-3 bytes into fifo
|
||||
const uint8_t bytes_rem = len & 0x03;
|
||||
if (bytes_rem) {
|
||||
uint32_t tmp_word = src[0];
|
||||
if (bytes_rem > 1) {
|
||||
tmp_word |= (src[1] << 8);
|
||||
}
|
||||
if (bytes_rem > 2) {
|
||||
tmp_word |= (src[2] << 16);
|
||||
}
|
||||
|
||||
*tx_fifo = tmp_word;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -84,12 +84,16 @@ TU_ATTR_ALWAYS_INLINE static inline void dfifo_flush_tx(dwc2_regs_t* dwc2, uint8
|
||||
dwc2->grstctl = GRSTCTL_TXFFLSH | (fnum << GRSTCTL_TXFNUM_Pos);
|
||||
while (dwc2->grstctl & GRSTCTL_TXFFLSH_Msk) {}
|
||||
}
|
||||
|
||||
TU_ATTR_ALWAYS_INLINE static inline void dfifo_flush_rx(dwc2_regs_t* dwc2) {
|
||||
// flush RX fifo and wait for it cleared
|
||||
dwc2->grstctl = GRSTCTL_RXFFLSH;
|
||||
while (dwc2->grstctl & GRSTCTL_RXFFLSH_Msk) {}
|
||||
}
|
||||
|
||||
void dfifo_read_packet(dwc2_regs_t* dwc2, uint8_t* dst, uint16_t len);
|
||||
void dfifo_write_packet(dwc2_regs_t* dwc2, uint8_t fifo_num, uint8_t const* src, uint16_t len);
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// DMA
|
||||
//--------------------------------------------------------------------+
|
||||
|
@ -146,7 +146,7 @@ enum {
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Register bitfield definitions
|
||||
// Common Register Bitfield
|
||||
//--------------------------------------------------------------------
|
||||
typedef struct TU_ATTR_PACKED {
|
||||
uint32_t ses_req_scs : 1; // 0 Session request success
|
||||
@ -324,6 +324,20 @@ typedef struct TU_ATTR_PACKED {
|
||||
}dwc2_ghwcfg4_t;
|
||||
TU_VERIFY_STATIC(sizeof(dwc2_ghwcfg4_t) == 4, "incorrect size");
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Register Bitfield
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
typedef struct TU_ATTR_PACKED {
|
||||
uint32_t fifo_available : 16; // 0..15 Number of words available in the Tx FIFO
|
||||
uint32_t queue_available : 8; // 16..23 Number of spaces available in the NPT transmit request queue for both IN and OUT
|
||||
// 24..31 is top entry in the request queue that is currently being processed by the MAC
|
||||
uint32_t qtop_terminate : 1; // 24 Last entry for selected channel
|
||||
uint32_t qtop_token : 2; // 25..26 Token 0: In/Out 1: ZLP, 2: Ping/cspit, 3: Channel halt command
|
||||
uint32_t qtop_ch_num : 4; // 27..30 Channel number
|
||||
} dwc2_hnptxsts_t;
|
||||
TU_VERIFY_STATIC(sizeof(dwc2_hnptxsts_t) == 4, "incorrect size");
|
||||
|
||||
typedef struct TU_ATTR_PACKED {
|
||||
uint32_t conn_status : 1; // 0 Port connect status
|
||||
uint32_t conn_detected : 1; // 1 Port connect detected
|
||||
@ -368,6 +382,13 @@ typedef struct TU_ATTR_PACKED {
|
||||
} dwc2_channel_split_t;
|
||||
TU_VERIFY_STATIC(sizeof(dwc2_channel_split_t) == 4, "incorrect size");
|
||||
|
||||
typedef struct TU_ATTR_PACKED {
|
||||
uint32_t xfer_size : 19; // 0..18 Transfer size in bytes
|
||||
uint32_t packet_count : 10; // 19..28 Number of packets
|
||||
uint32_t pid : 2; // 29..30 Packet ID
|
||||
} dwc2_channel_tsize_t;
|
||||
TU_VERIFY_STATIC(sizeof(dwc2_channel_tsize_t) == 4, "incorrect size");
|
||||
|
||||
// Host Channel
|
||||
typedef struct {
|
||||
union {
|
||||
@ -380,35 +401,55 @@ typedef struct {
|
||||
};
|
||||
volatile uint32_t hcint; // 508 + 20*ch Host Channel Interrupt
|
||||
volatile uint32_t hcintmsk; // 50C + 20*ch Host Channel Interrupt Mask
|
||||
union {
|
||||
volatile uint32_t hctsiz; // 510 + 20*ch Host Channel Transfer Size
|
||||
volatile dwc2_channel_tsize_t hctsiz_bm;
|
||||
};
|
||||
volatile uint32_t hcdma; // 514 + 20*ch Host Channel DMA Address
|
||||
uint32_t reserved518; // 518 + 20*ch
|
||||
volatile uint32_t hcdmab; // 51C + 20*ch Host Channel DMA Address
|
||||
} dwc2_channel_t;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Device Register Bitfield
|
||||
//--------------------------------------------------------------------
|
||||
typedef struct TU_ATTR_PACKED {
|
||||
uint32_t xfer_size : 19; // 0..18 Transfer size in bytes
|
||||
uint32_t packet_count : 10; // 19..28 Number of packets
|
||||
uint32_t mc_pid : 2; // 29..30 IN: Multi Count, OUT: PID
|
||||
} dwc2_ep_tsize_t;
|
||||
TU_VERIFY_STATIC(sizeof(dwc2_ep_tsize_t) == 4, "incorrect size");
|
||||
|
||||
// Endpoint IN
|
||||
typedef struct {
|
||||
volatile uint32_t diepctl; // 900 + 20*ep Device IN Endpoint Control
|
||||
uint32_t reserved04; // 904
|
||||
volatile uint32_t diepint; // 908 + 20*ep Device IN Endpoint Interrupt
|
||||
uint32_t reserved0c; // 90C
|
||||
volatile uint32_t dieptsiz; // 910 + 20*ep Device IN Endpoint Transfer Size
|
||||
volatile uint32_t diepdma; // 914 + 20*ep Device IN Endpoint DMA Address
|
||||
volatile uint32_t dtxfsts; // 918 + 20*ep Device IN Endpoint Tx FIFO Status
|
||||
uint32_t reserved1c; // 91C
|
||||
volatile uint32_t diepctl; // 900 + 20*ep Device IN Endpoint Control
|
||||
uint32_t reserved04; // 904
|
||||
volatile uint32_t diepint; // 908 + 20*ep Device IN Endpoint Interrupt
|
||||
uint32_t reserved0c; // 90C
|
||||
union {
|
||||
volatile uint32_t dieptsiz; // 910 + 20*ep Device IN Endpoint Transfer Size
|
||||
volatile dwc2_ep_tsize_t dieptsiz_bm;
|
||||
};
|
||||
volatile uint32_t diepdma; // 914 + 20*ep Device IN Endpoint DMA Address
|
||||
volatile uint32_t dtxfsts; // 918 + 20*ep Device IN Endpoint Tx FIFO Status
|
||||
uint32_t reserved1c; // 91C
|
||||
} dwc2_epin_t;
|
||||
|
||||
// Endpoint OUT
|
||||
typedef struct {
|
||||
volatile uint32_t doepctl; // B00 + 20*ep Device OUT Endpoint Control
|
||||
uint32_t reserved04; // B04
|
||||
volatile uint32_t doepint; // B08 + 20*ep Device OUT Endpoint Interrupt
|
||||
uint32_t reserved0c; // B0C
|
||||
volatile uint32_t doeptsiz; // B10 + 20*ep Device OUT Endpoint Transfer Size
|
||||
volatile uint32_t doepdma; // B14 + 20*ep Device OUT Endpoint DMA Address
|
||||
uint32_t reserved18[2]; // B18..B1C
|
||||
volatile uint32_t doepctl; // B00 + 20*ep Device OUT Endpoint Control
|
||||
uint32_t reserved04; // B04
|
||||
volatile uint32_t doepint; // B08 + 20*ep Device OUT Endpoint Interrupt
|
||||
uint32_t reserved0c; // B0C
|
||||
union {
|
||||
volatile uint32_t doeptsiz; // B10 + 20*ep Device OUT Endpoint Transfer Size
|
||||
volatile dwc2_ep_tsize_t doeptsiz_bm;
|
||||
};
|
||||
volatile uint32_t doepdma; // B14 + 20*ep Device OUT Endpoint DMA Address
|
||||
uint32_t reserved18[2]; // B18..B1C
|
||||
} dwc2_epout_t;
|
||||
|
||||
// Universal Endpoint
|
||||
typedef struct {
|
||||
union {
|
||||
volatile uint32_t diepctl;
|
||||
@ -424,6 +465,7 @@ typedef struct {
|
||||
union {
|
||||
volatile uint32_t dieptsiz;
|
||||
volatile uint32_t doeptsiz;
|
||||
volatile dwc2_ep_tsize_t deptsiz_bm;
|
||||
};
|
||||
union {
|
||||
volatile uint32_t diepdma;
|
||||
@ -460,7 +502,11 @@ typedef struct {
|
||||
volatile uint32_t dieptxf0; // 028 EP0 Tx FIFO Size
|
||||
volatile uint32_t gnptxfsiz; // 028 Non-periodic Transmit FIFO Size
|
||||
};
|
||||
volatile uint32_t gnptxsts; // 02c Non-periodic Transmit FIFO/Queue Status
|
||||
union {
|
||||
volatile uint32_t hnptxsts; // 02c Non-periodic Transmit FIFO/Queue Status
|
||||
volatile dwc2_hnptxsts_t hnptxsts_bm;
|
||||
volatile uint32_t gnptxsts;
|
||||
};
|
||||
volatile uint32_t gi2cctl; // 030 I2C Address
|
||||
volatile uint32_t gpvndctl; // 034 PHY Vendor Control
|
||||
union {
|
||||
@ -791,14 +837,12 @@ TU_VERIFY_STATIC(offsetof(dwc2_regs_t, fifo ) == 0x1000, "incorrect size");
|
||||
#define GAHBCFG_DMAEN_Pos (5U)
|
||||
#define GAHBCFG_DMAEN_Msk (0x1UL << GAHBCFG_DMAEN_Pos) // 0x00000020
|
||||
#define GAHBCFG_DMAEN GAHBCFG_DMAEN_Msk // DMA enable
|
||||
#define GAHBCFG_TXFELVL_Pos (7U)
|
||||
#define GAHBCFG_TXFELVL_Msk (0x1UL << GAHBCFG_TXFELVL_Pos) // 0x00000080
|
||||
#define GAHBCFG_TXFELVL GAHBCFG_TXFELVL_Msk // TxFIFO empty level
|
||||
#define GAHBCFG_PTXFELVL_Pos (8U)
|
||||
#define GAHBCFG_PTXFELVL_Msk (0x1UL << GAHBCFG_PTXFELVL_Pos) // 0x00000100
|
||||
#define GAHBCFG_PTXFELVL GAHBCFG_PTXFELVL_Msk // Periodic TxFIFO empty level
|
||||
|
||||
#define GSNPSID_ID_MASK TU_GENMASK(31, 16)
|
||||
#define GAHBCFG_TX_FIFO_EPMTY_LVL_Pos (7U)
|
||||
#define GAHBCFG_TX_FIFO_EPMTY_LVL_Msk (0x1UL << GAHBCFG_TX_FIFO_EPMTY_LVL_Pos) // 0x00000080
|
||||
#define GAHBCFG_TX_FIFO_EPMTY_LVL GAHBCFG_TX_FIFO_EPMTY_LVL_Msk // TxFIFO empty level
|
||||
#define GAHBCFG_PTX_FIFO_EPMTY_LVL_Pos (8U)
|
||||
#define GAHBCFG_PTX_FIFO_EPMTY_LVL_Msk (0x1UL << GAHBCFG_PTX_FIFO_EPMTY_LVL_Pos) // 0x00000100
|
||||
#define GAHBCFG_PTX_FIFO_EPMTY_LVL GAHBCFG_PTX_FIFO_EPMTY_LVL_Msk // Periodic TxFIFO empty level
|
||||
|
||||
/******************** Bit definition for GUSBCFG register ********************/
|
||||
#define GUSBCFG_TOCAL_Pos (0U)
|
||||
@ -1009,9 +1053,9 @@ TU_VERIFY_STATIC(offsetof(dwc2_regs_t, fifo ) == 0x1000, "incorrect size");
|
||||
#define GINTSTS_RXFLVL_Pos (4U)
|
||||
#define GINTSTS_RXFLVL_Msk (0x1UL << GINTSTS_RXFLVL_Pos) // 0x00000010
|
||||
#define GINTSTS_RXFLVL GINTSTS_RXFLVL_Msk // RxFIFO nonempty
|
||||
#define GINTSTS_NPTXFE_Pos (5U)
|
||||
#define GINTSTS_NPTXFE_Msk (0x1UL << GINTSTS_NPTXFE_Pos) // 0x00000020
|
||||
#define GINTSTS_NPTXFE GINTSTS_NPTXFE_Msk // Nonperiodic TxFIFO empty
|
||||
#define GINTSTS_NPTX_FIFO_EMPTY_Pos (5U)
|
||||
#define GINTSTS_NPTX_FIFO_EMPTY_Msk (0x1UL << GINTSTS_NPTX_FIFO_EMPTY_Pos) // 0x00000020
|
||||
#define GINTSTS_NPTX_FIFO_EMPTY GINTSTS_NPTX_FIFO_EMPTY_Msk // Nonperiodic TxFIFO empty
|
||||
#define GINTSTS_GINAKEFF_Pos (6U)
|
||||
#define GINTSTS_GINAKEFF_Msk (0x1UL << GINTSTS_GINAKEFF_Pos) // 0x00000040
|
||||
#define GINTSTS_GINAKEFF GINTSTS_GINAKEFF_Msk // Global IN nonperiodic NAK effective
|
||||
@ -1060,15 +1104,15 @@ TU_VERIFY_STATIC(offsetof(dwc2_regs_t, fifo ) == 0x1000, "incorrect size");
|
||||
#define GINTSTS_HCINT_Pos (25U)
|
||||
#define GINTSTS_HCINT_Msk (0x1UL << GINTSTS_HCINT_Pos) // 0x02000000
|
||||
#define GINTSTS_HCINT GINTSTS_HCINT_Msk // Host channels interrupt
|
||||
#define GINTSTS_PTXFE_Pos (26U)
|
||||
#define GINTSTS_PTXFE_Msk (0x1UL << GINTSTS_PTXFE_Pos) // 0x04000000
|
||||
#define GINTSTS_PTXFE GINTSTS_PTXFE_Msk // Periodic TxFIFO empty
|
||||
#define GINTSTS_PTX_FIFO_EMPTY_Pos (26U)
|
||||
#define GINTSTS_PTX_FIFO_EMPTY_Msk (0x1UL << GINTSTS_PTX_FIFO_EMPTY_Pos) // 0x04000000
|
||||
#define GINTSTS_PTX_FIFO_EMPTY GINTSTS_PTX_FIFO_EMPTY_Msk // Periodic TxFIFO empty
|
||||
#define GINTSTS_LPMINT_Pos (27U)
|
||||
#define GINTSTS_LPMINT_Msk (0x1UL << GINTSTS_LPMINT_Pos) // 0x08000000
|
||||
#define GINTSTS_LPMINT GINTSTS_LPMINT_Msk // LPM interrupt
|
||||
#define GINTSTS_CONIDSTSCHNG_Pos (28U)
|
||||
#define GINTSTS_CONIDSTSCHNG_Msk (0x1UL << GINTSTS_CONIDSTSCHNG_Pos) // 0x10000000
|
||||
#define GINTSTS_CONIDSTSCHNG GINTSTS_CONIDSTSCHNG_Msk // Connector ID status change
|
||||
#define GINTSTS_CONIDSTSCHNG_Msk (0x1UL << GINTSTS_CONIDSTSCHNG_Pos) // 0x10000000
|
||||
#define GINTSTS_CONIDSTSCHNG GINTSTS_CONIDSTSCHNG_Msk // Connector ID status change
|
||||
#define GINTSTS_DISCINT_Pos (29U)
|
||||
#define GINTSTS_DISCINT_Msk (0x1UL << GINTSTS_DISCINT_Pos) // 0x20000000
|
||||
#define GINTSTS_DISCINT GINTSTS_DISCINT_Msk // Disconnect detected interrupt
|
||||
|
@ -35,14 +35,15 @@
|
||||
#include "dwc2_common.h"
|
||||
|
||||
// DWC2 has limit number of channel, in order to support all endpoints we can store channel char/split to swap later on
|
||||
#ifndef CFG_TUH_DWC2_CHANNEL_MAX
|
||||
#define CFG_TUH_DWC2_CHANNEL_MAX (CFG_TUH_DEVICE_MAX*CFG_TUH_ENDPOINT_MAX + CFG_TUH_HUB)
|
||||
#ifndef CFG_TUH_DWC2_ENDPOINT_MAX
|
||||
#define CFG_TUH_DWC2_ENDPOINT_MAX (CFG_TUH_DEVICE_MAX*CFG_TUH_ENDPOINT_MAX + CFG_TUH_HUB)
|
||||
#endif
|
||||
|
||||
enum {
|
||||
HPRT_W1C_MASK = HPRT_CONN_DETECT | HPRT_ENABLE | HPRT_ENABLE_CHANGE | HPRT_OVER_CURRENT_CHANGE
|
||||
};
|
||||
|
||||
// Host driver for each opened endpoint
|
||||
typedef struct {
|
||||
union {
|
||||
uint32_t hcchar;
|
||||
@ -53,11 +54,14 @@ typedef struct {
|
||||
dwc2_channel_split_t hcsplt_bm;
|
||||
};
|
||||
|
||||
uint8_t* buf;
|
||||
uint16_t total_len;
|
||||
uint8_t next_data_toggle;
|
||||
bool pending_tx;
|
||||
} hcd_pipe_t;
|
||||
|
||||
typedef struct {
|
||||
hcd_pipe_t pipe[CFG_TUH_DWC2_CHANNEL_MAX];
|
||||
hcd_pipe_t pipe[CFG_TUH_DWC2_ENDPOINT_MAX];
|
||||
} dwc2_hcd_t;
|
||||
|
||||
dwc2_hcd_t _hcd_data;
|
||||
@ -94,7 +98,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool dma_host_enabled(const dwc2_regs_t* dwc
|
||||
possible since the free space is located between the RX and TX FIFOs.
|
||||
|
||||
----------------- ep_fifo_size
|
||||
| EPInfo DMA |
|
||||
| HCDMAn |
|
||||
|--------------|-- gdfifocfg.EPINFOBASE (max is ghwcfg3.dfifo_depth)
|
||||
| Non-Periodic |
|
||||
| TX FIFO |
|
||||
@ -214,8 +218,12 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
|
||||
dwc2->hprt = HPRT_POWER; // turn on VBUS
|
||||
|
||||
// Enable required interrupts
|
||||
dwc2->gintmsk |= GINTMSK_OTGINT | GINTSTS_CONIDSTSCHNG | GINTMSK_PRTIM; // | GINTMSK_WUIM;
|
||||
dwc2->gahbcfg |= GAHBCFG_GINT; // Enable global interrupt
|
||||
dwc2->gintmsk |= GINTMSK_OTGINT | GINTSTS_CONIDSTSCHNG | GINTMSK_PRTIM ; // | GINTMSK_WUIM;
|
||||
|
||||
// NPTX can hold at least 2 packet, change interrupt level to half-empty
|
||||
uint32_t gahbcfg = dwc2->gahbcfg & ~GAHBCFG_TX_FIFO_EPMTY_LVL;
|
||||
gahbcfg |= GAHBCFG_GINT; // Enable global interrupt
|
||||
dwc2->gahbcfg = gahbcfg;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -290,7 +298,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const
|
||||
hcd_devtree_get_info(dev_addr, &devtree_info);
|
||||
|
||||
// find a free pipe
|
||||
for (uint32_t i = 0; i < CFG_TUH_DWC2_CHANNEL_MAX; i++) {
|
||||
for (uint32_t i = 0; i < CFG_TUH_DWC2_ENDPOINT_MAX; i++) {
|
||||
hcd_pipe_t* pipe = &_hcd_data.pipe[i];
|
||||
dwc2_channel_char_t* hcchar_bm = &pipe->hcchar_bm;
|
||||
dwc2_channel_split_t* hcsplt_bm = &pipe->hcsplt_bm;
|
||||
@ -334,25 +342,40 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t find_free_channel(dwc2_regs_t* dwc2)
|
||||
return TUSB_INDEX_INVALID_8;
|
||||
}
|
||||
|
||||
TU_ATTR_ALWAYS_INLINE static inline uint8_t find_opened_pipe(uint8_t dev_addr, uint8_t ep_addr) {
|
||||
for (uint32_t i = 0; i < CFG_TUH_DWC2_CHANNEL_MAX; i++) {
|
||||
TU_ATTR_ALWAYS_INLINE static inline uint8_t find_opened_pipe(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) {
|
||||
for (uint8_t i = 0; i < (uint8_t) CFG_TUH_DWC2_ENDPOINT_MAX; i++) {
|
||||
const dwc2_channel_char_t* hcchar_bm = &_hcd_data.pipe[i].hcchar_bm;
|
||||
// find enabled pipe: note EP0 is bidirectional
|
||||
if (hcchar_bm->enable && hcchar_bm->dev_addr == dev_addr &&
|
||||
ep_addr == tu_edpt_addr(hcchar_bm->ep_num, hcchar_bm->ep_dir)) {
|
||||
hcchar_bm->ep_num == ep_num && (ep_num == 0 || hcchar_bm->ep_dir == ep_dir)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return TUSB_INDEX_INVALID_8;
|
||||
}
|
||||
|
||||
void schedule_out_packet(dwc2_regs_t* dwc2, uint8_t pipe_id, uint8_t ch_id) {
|
||||
// To prevent conflict with other channel, we will enable periodic/non-periodic FIFO empty interrupt accordingly.
|
||||
// And write packet in the interrupt handler
|
||||
hcd_pipe_t* pipe = &_hcd_data.pipe[pipe_id];
|
||||
dwc2_channel_t* channel = &dwc2->channel[ch_id];
|
||||
(void) channel;
|
||||
const uint8_t ep_type = pipe->hcchar_bm.ep_type;
|
||||
const bool is_periodic = ep_type == TUSB_XFER_INTERRUPT || ep_type == TUSB_XFER_ISOCHRONOUS;
|
||||
pipe->pending_tx = true;
|
||||
dwc2->gintmsk |= (is_periodic ? GINTSTS_PTX_FIFO_EMPTY : GINTSTS_NPTX_FIFO_EMPTY);
|
||||
}
|
||||
|
||||
// Submit a transfer, when complete hcd_event_xfer_complete() must be invoked
|
||||
bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) {
|
||||
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
|
||||
|
||||
uint8_t pipe_id = find_opened_pipe(dev_addr, ep_addr);
|
||||
TU_ASSERT(pipe_id < CFG_TUH_DWC2_CHANNEL_MAX); // no opened pipe
|
||||
const uint8_t ep_num = tu_edpt_number(ep_addr);
|
||||
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
|
||||
uint8_t pipe_id = find_opened_pipe(dev_addr, ep_num, ep_dir);
|
||||
TU_ASSERT(pipe_id < CFG_TUH_DWC2_ENDPOINT_MAX); // no opened pipe
|
||||
hcd_pipe_t* pipe = &_hcd_data.pipe[pipe_id];
|
||||
const dwc2_channel_char_t* hcchar_bm = &pipe->hcchar_bm;
|
||||
dwc2_channel_char_t* hcchar_bm = &pipe->hcchar_bm;
|
||||
|
||||
uint8_t ch_id = find_free_channel(dwc2);
|
||||
TU_ASSERT(ch_id < 16); // all channel are in use
|
||||
@ -362,25 +385,44 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
|
||||
channel->hcintmsk = HCINT_XFER_COMPLETE | HCINT_CHANNEL_HALTED | HCINT_STALL |
|
||||
HCINT_AHB_ERR | HCINT_XACT_ERR | HCINT_BABBLE_ERR | HCINT_DATATOGGLE_ERR;
|
||||
|
||||
const uint16_t packet_count = tu_div_ceil(buflen, hcchar_bm->ep_size);
|
||||
uint16_t packet_count = tu_div_ceil(buflen, hcchar_bm->ep_size);
|
||||
if (packet_count == 0) {
|
||||
packet_count = 1; // zero length packet still count as 1
|
||||
}
|
||||
channel->hctsiz = (pipe->next_data_toggle << HCTSIZ_PID_Pos) | (packet_count << HCTSIZ_PKTCNT_Pos) | buflen;
|
||||
|
||||
// Control transfer always start with DATA1 for data and status stage. May has issue with ZLP
|
||||
if (pipe->next_data_toggle == HCTSIZ_PID_DATA0 || tu_edpt_number(ep_addr) == 0) {
|
||||
if (pipe->next_data_toggle == HCTSIZ_PID_DATA0 || ep_num == 0) {
|
||||
pipe->next_data_toggle = HCTSIZ_PID_DATA1;
|
||||
} else {
|
||||
pipe->next_data_toggle = HCTSIZ_PID_DATA0;
|
||||
}
|
||||
|
||||
// TODO support split transaction
|
||||
channel->hcsplt = pipe->hcsplt;
|
||||
|
||||
hcchar_bm->odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame
|
||||
hcchar_bm->ep_dir = ep_dir; // control endpoint can switch direction
|
||||
channel->hcchar = pipe->hcchar & ~HCCHAR_CHENA; // restore hcchar but don't enable yet
|
||||
|
||||
if (dma_host_enabled(dwc2)) {
|
||||
channel->hcdma = (uint32_t) buffer;
|
||||
} else {
|
||||
TU_ASSERT(false); // not yet support
|
||||
}
|
||||
if (ep_dir == TUSB_DIR_IN) {
|
||||
TU_ASSERT(false); // not yet support
|
||||
} else {
|
||||
pipe->buf = buffer;
|
||||
pipe->total_len = buflen;
|
||||
|
||||
// TODO support split transaction
|
||||
channel->hcsplt = pipe->hcsplt;
|
||||
channel->hcchar = pipe->hcchar; // kick-off transfer
|
||||
channel->hcchar |= HCCHAR_CHENA; // enable channel before writing to FIFO
|
||||
|
||||
if (ep_dir == TUSB_DIR_OUT && buflen > 0) {
|
||||
schedule_out_packet(dwc2, pipe_id, ch_id);
|
||||
} else {
|
||||
TU_ASSERT(false); // not yet support
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -397,8 +439,8 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
|
||||
|
||||
// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked
|
||||
bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
|
||||
uint8_t pipe_id = find_opened_pipe(dev_addr, 0);
|
||||
TU_ASSERT(pipe_id < CFG_TUH_DWC2_CHANNEL_MAX); // no opened pipe
|
||||
uint8_t pipe_id = find_opened_pipe(dev_addr, 0, TUSB_DIR_OUT);
|
||||
TU_ASSERT(pipe_id < CFG_TUH_DWC2_ENDPOINT_MAX); // no opened pipe
|
||||
hcd_pipe_t* pipe = &_hcd_data.pipe[pipe_id];
|
||||
pipe->next_data_toggle = HCTSIZ_PID_SETUP;
|
||||
|
||||
@ -529,25 +571,57 @@ void handle_channel_irq(uint8_t rhport, bool in_isr) {
|
||||
}
|
||||
}
|
||||
|
||||
bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
|
||||
bool ff_written = false;
|
||||
volatile uint32_t* tx_sts = is_periodic ? &dwc2->hptxsts : &dwc2->hnptxsts;
|
||||
|
||||
// find which channel have pending packet
|
||||
for (uint8_t ch_id = 0; ch_id < 32; ch_id++) {
|
||||
if (tu_bit_test(dwc2->haintmsk, ch_id)) {
|
||||
dwc2_channel_t* channel = &dwc2->channel[ch_id];
|
||||
const dwc2_channel_char_t hcchar_bm = channel->hcchar_bm;
|
||||
if (hcchar_bm.ep_dir == TUSB_DIR_OUT) {
|
||||
uint8_t pipe_id = find_opened_pipe(hcchar_bm.dev_addr, hcchar_bm.ep_num, TUSB_DIR_OUT);
|
||||
if (pipe_id < CFG_TUH_DWC2_ENDPOINT_MAX) {
|
||||
hcd_pipe_t* pipe = &_hcd_data.pipe[pipe_id];
|
||||
if (pipe->pending_tx) {
|
||||
const uint16_t remain_packets = channel->hctsiz_bm.packet_count;
|
||||
for (uint16_t i = 0; i < remain_packets; i++) {
|
||||
const uint16_t remain_bytes = channel->hctsiz_bm.xfer_size;
|
||||
const uint16_t packet_bytes = tu_min16(remain_bytes, hcchar_bm.ep_size);
|
||||
|
||||
// check if there is enough space in FIFO
|
||||
if (packet_bytes > (*tx_sts & HPTXSTS_PTXQSAV)) {
|
||||
break;
|
||||
}
|
||||
|
||||
dfifo_write_packet(dwc2, ch_id, pipe->buf, packet_bytes);
|
||||
pipe->buf += packet_bytes;
|
||||
|
||||
if (channel->hctsiz_bm.xfer_size == 0) {
|
||||
pipe->pending_tx = false; // all data has been written
|
||||
}
|
||||
|
||||
ff_written = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ff_written;
|
||||
}
|
||||
|
||||
/* Interrupt Hierarchy
|
||||
HCINTn.XferCompl HCINTMSKn.XferComplMsk HPRT ConnDetect PrtEnChng OverCurChng
|
||||
| | | | |
|
||||
+---------- AND --------+ +------------ OR -----------+
|
||||
| |
|
||||
HAINT.CHn HAINTMSK.CHn |
|
||||
| | |
|
||||
+---------- AND --------+ |
|
||||
| |
|
||||
GINTSTS.HCInt GINTMSK.HCInt GINTSTS.PrtInt GINTMSK.PrtInt
|
||||
| | | |
|
||||
+---------- AND ---------+ +---------- AND ---------+
|
||||
| |
|
||||
+-------------------- OR ---------------------------+
|
||||
|
|
||||
GAHBCFG.GblIntrMsk
|
||||
|
|
||||
IRQn
|
||||
*/
|
||||
HCINTn HPRT
|
||||
| |
|
||||
HAINT.CHn |
|
||||
| |
|
||||
GINTSTS HCInt PrtInt NPTxFEmp PTxFEmpp RXFLVL
|
||||
|
||||
|
||||
*/
|
||||
void hcd_int_handler(uint8_t rhport, bool in_isr) {
|
||||
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
|
||||
const uint32_t int_mask = dwc2->gintmsk;
|
||||
@ -577,6 +651,15 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
|
||||
handle_channel_irq(rhport, in_isr);
|
||||
}
|
||||
|
||||
if (int_status & GINTSTS_NPTX_FIFO_EMPTY) {
|
||||
// NPTX FIFO empty interrupt, this is read-only and cleared by hardware when FIFO is written
|
||||
const bool ff_written = handle_txfifo_empty(dwc2, false);
|
||||
if (!ff_written) {
|
||||
// no more pending packet, disable interrupt
|
||||
dwc2->gintmsk &= ~GINTSTS_NPTX_FIFO_EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
// RxFIFO non-empty interrupt handling.
|
||||
if (int_status & GINTSTS_RXFLVL) {
|
||||
// RXFLVL bit is read-only
|
||||
|
Loading…
x
Reference in New Issue
Block a user