Przeglądaj źródła

Merge pull request #380 from hathach/fix-usbnet-synopsys

synopsys turn off TX FIFO Empty for EPIN if all bytes are written
Ha Thach 5 lat temu
rodzic
commit
b52bc894f8

+ 2 - 0
src/class/net/net_device.c

@@ -89,6 +89,7 @@ static const struct ecm_notify_struct ecm_notify_csc =
   .uplink = 9728000,
   .uplink = 9728000,
 };
 };
 
 
+// TODO remove CFG_TUSB_MEM_SECTION, control internal buffer is already in this special section
 CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union
 CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union
 {
 {
   uint8_t rndis_buf[120];
   uint8_t rndis_buf[120];
@@ -98,6 +99,7 @@ CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
 // INTERNAL OBJECT & FUNCTION DECLARATION
 // INTERNAL OBJECT & FUNCTION DECLARATION
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
+// TODO remove CFG_TUSB_MEM_SECTION
 CFG_TUSB_MEM_SECTION static netd_interface_t _netd_itf;
 CFG_TUSB_MEM_SECTION static netd_interface_t _netd_itf;
 
 
 static bool can_xmit;
 static bool can_xmit;

+ 22 - 9
src/device/usbd.c

@@ -232,15 +232,15 @@ bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event,
 #if CFG_TUSB_DEBUG >= 2
 #if CFG_TUSB_DEBUG >= 2
 static char const* const _usbd_event_str[DCD_EVENT_COUNT] =
 static char const* const _usbd_event_str[DCD_EVENT_COUNT] =
 {
 {
-  "INVALID"        ,
-  "BUS_RESET"      ,
-  "UNPLUGGED"      ,
+  "Invalid"        ,
+  "Bus Reset"      ,
+  "Unplugged"      ,
   "SOF"            ,
   "SOF"            ,
-  "SUSPEND"        ,
-  "RESUME"         ,
-  "SETUP_RECEIVED" ,
-  "XFER_COMPLETE"  ,
-  "FUNC_CALL"
+  "Suspend"        ,
+  "Resume"         ,
+  "Setup Received" ,
+  "Xfer Complete"  ,
+  "Func Call"
 };
 };
 
 
 static char const* const _tusb_std_request_str[] =
 static char const* const _tusb_std_request_str[] =
@@ -260,6 +260,19 @@ static char const* const _tusb_std_request_str[] =
   "Synch Frame"
   "Synch Frame"
 };
 };
 
 
+// for usbd_control to print the name of control complete driver
+void usbd_driver_print_control_complete_name(bool (*control_complete) (uint8_t, tusb_control_request_t const * ))
+{
+  for (uint8_t i = 0; i < USBD_CLASS_DRIVER_COUNT; i++)
+  {
+    if (_usbd_driver[i].control_complete == control_complete )
+    {
+      TU_LOG2("  %s control complete\r\n", _usbd_driver[i].name);
+      return;
+    }
+  }
+}
+
 #endif
 #endif
 
 
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
@@ -356,7 +369,7 @@ void tud_task (void)
 
 
     if ( !osal_queue_receive(_usbd_q, &event) ) return;
     if ( !osal_queue_receive(_usbd_q, &event) ) return;
 
 
-    TU_LOG2("USBD: %s", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED");
+    TU_LOG2("USBD %s", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED");
     TU_LOG2("%s", (event.event_id != DCD_EVENT_XFER_COMPLETE && event.event_id != DCD_EVENT_SETUP_RECEIVED) ? "\r\n" : " ");
     TU_LOG2("%s", (event.event_id != DCD_EVENT_XFER_COMPLETE && event.event_id != DCD_EVENT_SETUP_RECEIVED) ? "\r\n" : " ");
 
 
     switch ( event.event_id )
     switch ( event.event_id )

+ 8 - 3
src/device/usbd_control.c

@@ -58,6 +58,7 @@ static uint8_t _usbd_ctrl_buf[CFG_TUD_ENDPOINT0_SIZE];
 // Application API
 // Application API
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
 
 
+// Queue ZLP status transaction
 static inline bool _status_stage_xact(uint8_t rhport, tusb_control_request_t const * request)
 static inline bool _status_stage_xact(uint8_t rhport, tusb_control_request_t const * request)
 {
 {
   // Opposite to endpoint in Data Phase
   // Opposite to endpoint in Data Phase
@@ -81,7 +82,7 @@ bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request)
   return _status_stage_xact(rhport, request);
   return _status_stage_xact(rhport, request);
 }
 }
 
 
-// Transfer an transaction in Data Stage
+// Queue a transaction in Data Stage
 // Each transaction has up to Endpoint0's max packet size.
 // Each transaction has up to Endpoint0's max packet size.
 // This function can also transfer an zero-length packet
 // This function can also transfer an zero-length packet
 static bool _data_stage_xact(uint8_t rhport)
 static bool _data_stage_xact(uint8_t rhport)
@@ -102,7 +103,6 @@ static bool _data_stage_xact(uint8_t rhport)
 }
 }
 
 
 // Transmit data to/from the control endpoint.
 // Transmit data to/from the control endpoint.
-// 
 // If the request's wLength is zero, a status packet is sent instead.
 // If the request's wLength is zero, a status packet is sent instead.
 bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, void* buffer, uint16_t len)
 bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, void* buffer, uint16_t len)
 {
 {
@@ -182,7 +182,7 @@ bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result
 
 
   // Data Stage is complete when all request's length are transferred or
   // Data Stage is complete when all request's length are transferred or
   // a short packet is sent including zero-length packet.
   // a short packet is sent including zero-length packet.
-  if ( (_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || xferred_bytes < CFG_TUD_ENDPOINT0_SIZE )
+  if ( (_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || (xferred_bytes < CFG_TUD_ENDPOINT0_SIZE) )
   {
   {
     // DATA stage is complete
     // DATA stage is complete
     bool is_ok = true;
     bool is_ok = true;
@@ -191,6 +191,11 @@ bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result
     // callback can still stall control in status phase e.g out data does not make sense
     // callback can still stall control in status phase e.g out data does not make sense
     if ( _ctrl_xfer.complete_cb )
     if ( _ctrl_xfer.complete_cb )
     {
     {
+      #if CFG_TUSB_DEBUG >= 2
+      extern void usbd_driver_print_control_complete_name(bool (*control_complete) (uint8_t, tusb_control_request_t const *));
+      usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb);
+      #endif
+
       is_ok = _ctrl_xfer.complete_cb(rhport, &_ctrl_xfer.request);
       is_ok = _ctrl_xfer.complete_cb(rhport, &_ctrl_xfer.request);
     }
     }
 
 

+ 23 - 13
src/portable/espressif/esp32s2/dcd_esp32s2.c

@@ -244,8 +244,8 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt)
 
 
   if (dir == TUSB_DIR_OUT) {
   if (dir == TUSB_DIR_OUT) {
     out_ep[epnum].doepctl |= USB_USBACTEP0_M |
     out_ep[epnum].doepctl |= USB_USBACTEP0_M |
-        desc_edpt->bmAttributes.xfer << USB_EPTYPE0_S |
-        desc_edpt->wMaxPacketSize.size << USB_MPS0_S;
+                             desc_edpt->bmAttributes.xfer << USB_EPTYPE0_S |
+                             desc_edpt->wMaxPacketSize.size << USB_MPS0_S;
     USB0.daintmsk |= (1 << (16 + epnum));
     USB0.daintmsk |= (1 << (16 + epnum));
   } else {
   } else {
     // "USB Data FIFOs" section in reference manual
     // "USB Data FIFOs" section in reference manual
@@ -272,10 +272,11 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt)
     // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n".
     // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n".
 
 
     in_ep[epnum].diepctl |= USB_D_USBACTEP1_M |
     in_ep[epnum].diepctl |= USB_D_USBACTEP1_M |
-        epnum << USB_D_TXFNUM1_S |
-        desc_edpt->bmAttributes.xfer << USB_D_EPTYPE1_S |
-        (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? (1 << USB_DI_SETD0PID1_S) : 0) |
-        desc_edpt->wMaxPacketSize.size << 0;
+                            epnum << USB_D_TXFNUM1_S |
+                            desc_edpt->bmAttributes.xfer << USB_D_EPTYPE1_S |
+                            (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? (1 << USB_DI_SETD0PID1_S) : 0) |
+                            desc_edpt->wMaxPacketSize.size << 0;
+
     USB0.daintmsk |= (1 << (0 + epnum));
     USB0.daintmsk |= (1 << (0 + epnum));
 
 
     // Both TXFD and TXSA are in unit of 32-bit words.
     // Both TXFD and TXSA are in unit of 32-bit words.
@@ -295,12 +296,12 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
   (void)rhport;
   (void)rhport;
 
 
   uint8_t const epnum = tu_edpt_number(ep_addr);
   uint8_t const epnum = tu_edpt_number(ep_addr);
-  uint8_t const dir = tu_edpt_dir(ep_addr);
+  uint8_t const dir   = tu_edpt_dir(ep_addr);
 
 
-  xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir);
-  xfer->buffer = buffer;
-  xfer->total_len = total_bytes;
-  xfer->queued_len = 0;
+  xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir);
+  xfer->buffer       = buffer;
+  xfer->total_len    = total_bytes;
+  xfer->queued_len   = 0;
   xfer->short_packet = false;
   xfer->short_packet = false;
 
 
   uint16_t num_packets = (total_bytes / xfer->max_size);
   uint16_t num_packets = (total_bytes / xfer->max_size);
@@ -321,7 +322,11 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
     // A full IN transfer (multiple packets, possibly) triggers XFRC.
     // A full IN transfer (multiple packets, possibly) triggers XFRC.
     USB0.in_ep_reg[epnum].dieptsiz = (num_packets << USB_D_PKTCNT0_S) | total_bytes;
     USB0.in_ep_reg[epnum].dieptsiz = (num_packets << USB_D_PKTCNT0_S) | total_bytes;
     USB0.in_ep_reg[epnum].diepctl |= USB_D_EPENA1_M | USB_D_CNAK1_M; // Enable | CNAK
     USB0.in_ep_reg[epnum].diepctl |= USB_D_EPENA1_M | USB_D_CNAK1_M; // Enable | CNAK
-    USB0.dtknqr4_fifoemptymsk |= (1 << epnum);
+
+    // Enable fifo empty interrupt only if there are something to put in the fifo.
+    if(total_bytes != 0) {
+      USB0.dtknqr4_fifoemptymsk |= (1 << epnum);
+    }
   } else {
   } else {
     // Each complete packet for OUT xfers triggers XFRC.
     // Each complete packet for OUT xfers triggers XFRC.
     USB0.out_ep_reg[epnum].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S);
     USB0.out_ep_reg[epnum].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S);
@@ -617,7 +622,6 @@ static void handle_epin_ints(void)
       if (USB0.in_ep_reg[n].diepint & USB_D_XFERCOMPL0_M) {
       if (USB0.in_ep_reg[n].diepint & USB_D_XFERCOMPL0_M) {
         ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER complete!");
         ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER complete!");
         USB0.in_ep_reg[n].diepint = USB_D_XFERCOMPL0_M;
         USB0.in_ep_reg[n].diepint = USB_D_XFERCOMPL0_M;
-        USB0.dtknqr4_fifoemptymsk &= ~(1 << n); // Turn off TXFE b/c xfer inactive.
         dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
         dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
       }
       }
 
 
@@ -626,6 +630,12 @@ static void handle_epin_ints(void)
         ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER FIFO empty!");
         ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER FIFO empty!");
         USB0.in_ep_reg[n].diepint = USB_D_TXFEMP0_M;
         USB0.in_ep_reg[n].diepint = USB_D_TXFEMP0_M;
         transmit_packet(xfer, &USB0.in_ep_reg[n], n);
         transmit_packet(xfer, &USB0.in_ep_reg[n], n);
+
+        // Turn off TXFE if all bytes are written.
+        if (xfer->queued_len == xfer->total_len)
+        {
+          USB0.dtknqr4_fifoemptymsk &= ~(1 << n);
+        }
       }
       }
     }
     }
   }
   }

+ 42 - 24
src/portable/st/synopsys/dcd_synopsys.c

@@ -291,9 +291,10 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt)
 
 
   if(dir == TUSB_DIR_OUT)
   if(dir == TUSB_DIR_OUT)
   {
   {
-    out_ep[epnum].DOEPCTL |= (1 << USB_OTG_DOEPCTL_USBAEP_Pos) | \
-      desc_edpt->bmAttributes.xfer << USB_OTG_DOEPCTL_EPTYP_Pos | \
-      desc_edpt->wMaxPacketSize.size << USB_OTG_DOEPCTL_MPSIZ_Pos;
+    out_ep[epnum].DOEPCTL |= (1 << USB_OTG_DOEPCTL_USBAEP_Pos) |
+                             (desc_edpt->bmAttributes.xfer << USB_OTG_DOEPCTL_EPTYP_Pos) |
+                             (desc_edpt->wMaxPacketSize.size << USB_OTG_DOEPCTL_MPSIZ_Pos);
+
     dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_OEPM_Pos + epnum));
     dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_OEPM_Pos + epnum));
   }
   }
   else
   else
@@ -321,11 +322,12 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt)
     // - Offset: GRXFSIZ + 16 + Size*(epnum-1)
     // - Offset: GRXFSIZ + 16 + Size*(epnum-1)
     // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n".
     // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n".
 
 
-    in_ep[epnum].DIEPCTL |= (1 << USB_OTG_DIEPCTL_USBAEP_Pos) | \
-      epnum << USB_OTG_DIEPCTL_TXFNUM_Pos | \
-      desc_edpt->bmAttributes.xfer << USB_OTG_DIEPCTL_EPTYP_Pos | \
-      (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM : 0) | \
-      desc_edpt->wMaxPacketSize.size << USB_OTG_DIEPCTL_MPSIZ_Pos;
+    in_ep[epnum].DIEPCTL |= (1 << USB_OTG_DIEPCTL_USBAEP_Pos) |
+                            (epnum << USB_OTG_DIEPCTL_TXFNUM_Pos) |
+                            (desc_edpt->bmAttributes.xfer << USB_OTG_DIEPCTL_EPTYP_Pos) |
+                            (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM : 0) |
+                            (desc_edpt->wMaxPacketSize.size << USB_OTG_DIEPCTL_MPSIZ_Pos);
+
     dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_IEPM_Pos + epnum));
     dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_IEPM_Pos + epnum));
 
 
     // Both TXFD and TXSA are in unit of 32-bit words.
     // Both TXFD and TXSA are in unit of 32-bit words.
@@ -352,9 +354,9 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t
   uint8_t const dir   = tu_edpt_dir(ep_addr);
   uint8_t const dir   = tu_edpt_dir(ep_addr);
 
 
   xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir);
   xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir);
-  xfer->buffer = buffer;
-  xfer->total_len = total_bytes;
-  xfer->queued_len = 0;
+  xfer->buffer       = buffer;
+  xfer->total_len    = total_bytes;
+  xfer->queued_len   = 0;
   xfer->short_packet = false;
   xfer->short_packet = false;
 
 
   uint16_t num_packets = (total_bytes / xfer->max_size);
   uint16_t num_packets = (total_bytes / xfer->max_size);
@@ -365,21 +367,23 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t
     num_packets++;
     num_packets++;
   }
   }
 
 
-  // IN and OUT endpoint xfers are interrupt-driven, we just schedule them
-  // here.
+  // IN and OUT endpoint xfers are interrupt-driven, we just schedule them here.
   if(dir == TUSB_DIR_IN) {
   if(dir == TUSB_DIR_IN) {
     // A full IN transfer (multiple packets, possibly) triggers XFRC.
     // A full IN transfer (multiple packets, possibly) triggers XFRC.
-    in_ep[epnum].DIEPTSIZ = (num_packets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | \
-        ((total_bytes & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) << USB_OTG_DIEPTSIZ_XFRSIZ_Pos);
+    in_ep[epnum].DIEPTSIZ = (num_packets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) |
+                            ((total_bytes & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) << USB_OTG_DIEPTSIZ_XFRSIZ_Pos);
+
     in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK;
     in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK;
+
     // Enable fifo empty interrupt only if there are something to put in the fifo.
     // Enable fifo empty interrupt only if there are something to put in the fifo.
     if(total_bytes != 0) {
     if(total_bytes != 0) {
       dev->DIEPEMPMSK |= (1 << epnum);
       dev->DIEPEMPMSK |= (1 << epnum);
     }
     }
   } else {
   } else {
     // Each complete packet for OUT xfers triggers XFRC.
     // Each complete packet for OUT xfers triggers XFRC.
-    out_ep[epnum].DOEPTSIZ |= (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos) | \
-        ((xfer->max_size & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos);
+    out_ep[epnum].DOEPTSIZ |= (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos) |
+                              ((xfer->max_size & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos);
+
     out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
     out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
   }
   }
 
 
@@ -535,6 +539,7 @@ static void receive_packet(xfer_ctl_t * xfer, /* USB_OTG_OUTEndpointTypeDef * ou
   xfer->short_packet = (xfer_size < xfer->max_size);
   xfer->short_packet = (xfer_size < xfer->max_size);
 }
 }
 
 
+// Write a data packet to EPIN FIFO
 static void transmit_packet(xfer_ctl_t * xfer, USB_OTG_INEndpointTypeDef * in_ep, uint8_t fifo_num) {
 static void transmit_packet(xfer_ctl_t * xfer, USB_OTG_INEndpointTypeDef * in_ep, uint8_t fifo_num) {
   usb_fifo_t tx_fifo = FIFO_BASE(fifo_num);
   usb_fifo_t tx_fifo = FIFO_BASE(fifo_num);
 
 
@@ -658,21 +663,34 @@ static void handle_epout_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_OUTEndpointTy
 static void handle_epin_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_INEndpointTypeDef * in_ep) {
 static void handle_epin_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_INEndpointTypeDef * in_ep) {
   // DAINT for a given EP clears when DIEPINTx is cleared.
   // DAINT for a given EP clears when DIEPINTx is cleared.
   // IEPINT will be cleared when DAINT's out bits are cleared.
   // IEPINT will be cleared when DAINT's out bits are cleared.
-  for(uint8_t n = 0; n < EP_MAX; n++) {
-    xfer_ctl_t * xfer = XFER_CTL_BASE(n, TUSB_DIR_IN);
+  for ( uint8_t n = 0; n < EP_MAX; n++ )
+  {
+    xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_IN);
 
 
-    if(dev->DAINT & (1 << (USB_OTG_DAINT_IEPINT_Pos + n))) {
+    if ( dev->DAINT & (1 << (USB_OTG_DAINT_IEPINT_Pos + n)) )
+    {
       // IN XFER complete (entire xfer).
       // IN XFER complete (entire xfer).
-      if(in_ep[n].DIEPINT & USB_OTG_DIEPINT_XFRC) {
+      if ( in_ep[n].DIEPINT & USB_OTG_DIEPINT_XFRC )
+      {
         in_ep[n].DIEPINT = USB_OTG_DIEPINT_XFRC;
         in_ep[n].DIEPINT = USB_OTG_DIEPINT_XFRC;
-        dev->DIEPEMPMSK &= ~(1 << n); // Turn off TXFE b/c xfer inactive.
         dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
         dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
       }
       }
 
 
       // XFER FIFO empty
       // XFER FIFO empty
-      if(in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE) {
-        in_ep[n].DIEPINT = USB_OTG_DIEPINT_TXFE;
+      if ( in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE )
+      {
+        // 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)
+
         transmit_packet(xfer, &in_ep[n], n);
         transmit_packet(xfer, &in_ep[n], n);
+
+        // Turn off TXFE if all bytes are written.
+        if (xfer->queued_len == xfer->total_len)
+        {
+          dev->DIEPEMPMSK &= ~(1 << n);
+        }
       }
       }
     }
     }
   }
   }