diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 0731a88c1..b389b0f74 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -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); } } diff --git a/src/portable/synopsys/dwc2/dwc2_common.c b/src/portable/synopsys/dwc2/dwc2_common.c index c5990c53f..1527932a5 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.c +++ b/src/portable/synopsys/dwc2/dwc2_common.c @@ -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 diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h index fff21ca5c..18b93894f 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.h +++ b/src/portable/synopsys/dwc2/dwc2_common.h @@ -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 //--------------------------------------------------------------------+ diff --git a/src/portable/synopsys/dwc2/dwc2_type.h b/src/portable/synopsys/dwc2/dwc2_type.h index ed335f1a3..36e917d43 100644 --- a/src/portable/synopsys/dwc2/dwc2_type.h +++ b/src/portable/synopsys/dwc2/dwc2_type.h @@ -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 diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index f37bf717d..f388776c5 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -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