summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorZixun LI <[email protected]>2026-03-12 10:55:18 +0100
committerZixun LI <[email protected]>2026-03-12 10:55:18 +0100
commite2884a1b88ce89b6e724e3d3b441b8ee1f83c06c (patch)
treec322110908d4282ca7e37fe52dc9ce42be15b073 /src
parentc06dc871d651ddaf756afdc59ed148c4b1af314d (diff)
parentac61a5b176b44db503d8bcc287a622463b65e48e (diff)
Merge branch 'master' into ncm_restart
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/class/audio/audio_device.c34
-rw-r--r--src/class/cdc/cdc_device.c22
-rw-r--r--src/class/cdc/cdc_device.h64
-rw-r--r--src/class/cdc/cdc_host.c8
-rw-r--r--src/class/cdc/cdc_host.h8
-rw-r--r--src/class/dfu/dfu_device.c25
-rw-r--r--src/class/midi/midi_device.c114
-rw-r--r--src/class/midi/midi_device.h29
-rw-r--r--src/class/midi/midi_host.c10
-rw-r--r--src/class/midi/midi_host.h6
-rw-r--r--src/class/printer/printer.h62
-rw-r--r--src/class/printer/printer_device.c329
-rw-r--r--src/class/printer/printer_device.h151
-rw-r--r--src/class/vendor/vendor_device.c45
-rw-r--r--src/class/vendor/vendor_device.h25
-rw-r--r--src/class/video/video_device.c13
-rw-r--r--src/common/tusb_fifo.c40
-rw-r--r--src/common/tusb_fifo.h16
-rw-r--r--src/common/tusb_mcu.h15
-rw-r--r--src/common/tusb_private.h25
-rw-r--r--src/common/tusb_types.h6
-rw-r--r--src/device/usbd.c39
-rw-r--r--src/device/usbd.h13
-rw-r--r--src/device/usbd_control.c10
-rw-r--r--src/device/usbd_pvt.h2
-rw-r--r--src/host/hcd.h3
-rw-r--r--src/host/hub.c71
-rw-r--r--src/host/hub.h7
-rw-r--r--src/host/usbh.c543
-rw-r--r--src/host/usbh.h3
-rw-r--r--src/host/usbh_pvt.h4
-rw-r--r--src/osal/osal.h4
-rw-r--r--src/osal/osal_freertos.h22
-rw-r--r--src/osal/osal_mynewt.h4
-rw-r--r--src/osal/osal_none.h6
-rw-r--r--src/osal/osal_pico.h4
-rw-r--r--src/osal/osal_rtthread.h4
-rw-r--r--src/osal/osal_rtx4.h4
-rw-r--r--src/osal/osal_threadx.h201
-rw-r--r--src/osal/osal_zephyr.h4
-rw-r--r--src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h8
-rw-r--r--src/portable/chipidea/ci_hs/dcd_ci_hs.c19
-rw-r--r--src/portable/chipidea/ci_hs/hcd_ci_hs.c14
-rw-r--r--src/portable/ehci/ehci.c11
-rw-r--r--src/portable/ehci/ehci_api.h3
-rw-r--r--src/portable/microchip/samx7x/dcd_samx7x.c679
-rw-r--r--src/portable/microchip/samx7x/samx7x_common.h (renamed from src/portable/microchip/samx7x/common_usb_regs.h)69
-rw-r--r--src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c821
-rw-r--r--src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h188
-rw-r--r--src/portable/ohci/ohci.c3
-rw-r--r--src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c8
-rw-r--r--src/portable/sunxi/dcd_sunxi_musb.c3
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c42
-rw-r--r--src/portable/synopsys/dwc2/dwc2_at32.h6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_bcm.h7
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.c30
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.h2
-rw-r--r--src/portable/synopsys/dwc2/dwc2_efm32.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_esp32.h7
-rw-r--r--src/portable/synopsys/dwc2/dwc2_gd32.h7
-rw-r--r--src/portable/synopsys/dwc2/dwc2_nrf.h6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_stm32.h16
-rw-r--r--src/portable/synopsys/dwc2/dwc2_xmc.h7
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c46
-rw-r--r--src/tinyusb.mk1
-rw-r--r--src/tusb.c33
-rw-r--r--src/tusb.h6
-rw-r--r--src/tusb_option.h28
69 files changed, 3149 insertions, 925 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 48dc75e50..00f466007 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -19,6 +19,7 @@ function(tinyusb_sources_get OUTPUT_VAR)
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/mtp/mtp_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ecm_rndis_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ncm_device.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/printer/printer_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/usbtmc/usbtmc_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/video/video_device.c
diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c
index be0224811..995bf8a3e 100644
--- a/src/class/audio/audio_device.c
+++ b/src/class/audio/audio_device.c
@@ -147,7 +147,9 @@ tu_static CFG_TUD_MEM_SECTION struct {
#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_EDPT_DEDICATED_HWFIFO
// Control buffer
-CFG_TUD_MEM_ALIGN uint8_t ctrl_buf[CFG_TUD_AUDIO_CTRL_BUF_SZ];
+#if CFG_TUD_AUDIO_CTRL_BUF_SZ > CFG_TUD_ENDPOINT0_BUFSIZE
+tu_static CFG_TUD_MEM_ALIGN uint8_t ctrl_buf[CFG_TUD_AUDIO_CTRL_BUF_SZ];
+#endif
// Aligned buffer for feedback EP
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
@@ -423,6 +425,15 @@ bool tud_audio_n_mounted(uint8_t func_id) {
return audio->mounted;
}
+static inline uint8_t* get_ctrl_buffer(void) {
+ // Use EP0 buffer if it is large enough, otherwise use dedicated buffer
+ #if CFG_TUD_AUDIO_CTRL_BUF_SZ > CFG_TUD_ENDPOINT0_BUFSIZE
+ return ctrl_buf;
+ #else
+ return usbd_get_ctrl_buf();
+ #endif
+}
+
//--------------------------------------------------------------------+
// READ API
//--------------------------------------------------------------------+
@@ -1296,20 +1307,20 @@ static bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const
if (tud_audio_n_version(func_id) == 2) {
uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
if (_audiod_fct[func_id].bclock_id_tx == entityID && ctrlSel == AUDIO20_CS_CTRL_SAM_FREQ && p_request->bRequest == AUDIO20_CS_REQ_CUR) {
- _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(ctrl_buf);
+ _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(get_ctrl_buffer());
audiod_calc_tx_packet_sz(&_audiod_fct[func_id]);
}
}
#endif
// Invoke callback
- return tud_audio_set_req_entity_cb(rhport, p_request, ctrl_buf);
+ return tud_audio_set_req_entity_cb(rhport, p_request, get_ctrl_buffer());
} else {
// Find index of audio driver structure and verify interface really exists
TU_VERIFY(audiod_verify_itf_exists(itf, &func_id));
// Invoke callback
- return tud_audio_set_req_itf_cb(rhport, p_request, ctrl_buf);
+ return tud_audio_set_req_itf_cb(rhport, p_request, get_ctrl_buffer());
}
} break;
@@ -1324,7 +1335,7 @@ static bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const
if (_audiod_fct[func_id].ep_in == ep) {
uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
if (ctrlSel == AUDIO10_EP_CTRL_SAMPLING_FREQ && p_request->bRequest == AUDIO10_CS_REQ_SET_CUR) {
- _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(ctrl_buf) & 0x00FFFFFF;
+ _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(get_ctrl_buffer()) & 0x00FFFFFF;
audiod_calc_tx_packet_sz(&_audiod_fct[func_id]);
}
}
@@ -1332,7 +1343,7 @@ static bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const
#endif
// Invoke callback
- bool ret = tud_audio_set_req_ep_cb(rhport, p_request, ctrl_buf);
+ bool ret = tud_audio_set_req_ep_cb(rhport, p_request, get_ctrl_buffer());
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
if (ret && tud_audio_n_version(func_id) == 1) {
@@ -1429,7 +1440,7 @@ static bool audiod_control_request(uint8_t rhport, tusb_control_request_t const
}
// If we end here, the received request is a set request - we schedule a receive for the data stage and return true here. We handle the rest later in audiod_control_complete() once the data stage was finished
- TU_VERIFY(tud_control_xfer(rhport, p_request, ctrl_buf, sizeof(ctrl_buf)));
+ TU_VERIFY(tud_control_xfer(rhport, p_request, get_ctrl_buffer(), CFG_TUD_AUDIO_CTRL_BUF_SZ));
return true;
}
@@ -1695,11 +1706,8 @@ bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_req
return false;
}
- // Crop length
- if (len > sizeof(ctrl_buf)) len = sizeof(ctrl_buf);
-
// Copy into buffer
- TU_VERIFY(0 == tu_memcpy_s(ctrl_buf, sizeof(ctrl_buf), data, (size_t) len));
+ TU_VERIFY(0 == tu_memcpy_s(get_ctrl_buffer(), CFG_TUD_AUDIO_CTRL_BUF_SZ, data, (size_t) len));
#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL
if (tud_audio_n_version(func_id) == 2) {
@@ -1708,7 +1716,7 @@ bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_req
uint8_t entityID = TU_U16_HIGH(p_request->wIndex);
uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
if (_audiod_fct[func_id].bclock_id_tx == entityID && ctrlSel == AUDIO20_CS_CTRL_SAM_FREQ && p_request->bRequest == AUDIO20_CS_REQ_CUR) {
- _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(ctrl_buf);
+ _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(get_ctrl_buffer());
audiod_calc_tx_packet_sz(&_audiod_fct[func_id]);
}
}
@@ -1716,7 +1724,7 @@ bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_req
#endif
// Schedule transmit
- return tud_control_xfer(rhport, p_request, ctrl_buf, len);
+ return tud_control_xfer(rhport, p_request, get_ctrl_buffer(), len);
}
// Verify an entity with the given ID exists and returns also the corresponding driver index
diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c
index e2819ae4b..c7547c92b 100644
--- a/src/class/cdc/cdc_device.c
+++ b/src/class/cdc/cdc_device.c
@@ -63,10 +63,10 @@ typedef struct {
#define ITF_MEM_RESET_SIZE offsetof(cdcd_interface_t, line_coding)
// Skip local EP buffer if dedicated hw FIFO is supported
- #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
typedef struct {
- TUD_EPBUF_DEF(epout, CFG_TUD_CDC_EP_BUFSIZE);
- TUD_EPBUF_DEF(epin, CFG_TUD_CDC_EP_BUFSIZE);
+ TUD_EPBUF_DEF(epout, CFG_TUD_CDC_RX_EPSIZE);
+ TUD_EPBUF_DEF(epin, CFG_TUD_CDC_TX_EPSIZE);
#if CFG_TUD_CDC_NOTIFY
TUD_EPBUF_TYPE_DEF(cdc_notify_msg_t, epnotify);
@@ -116,7 +116,7 @@ TU_ATTR_WEAK void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) {
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
static cdcd_interface_t _cdcd_itf[CFG_TUD_CDC];
-static tud_cdc_configure_t _cdcd_cfg = TUD_CDC_CONFIGURE_DEFAULT();
+static tud_cdc_configure_t _cdcd_cfg = CFG_TUD_CDC_CONFIGURE_DEFAULT();
TU_ATTR_ALWAYS_INLINE static inline uint8_t find_cdc_itf(uint8_t ep_addr) {
for (uint8_t idx = 0; idx < CFG_TUD_CDC; idx++) {
@@ -267,14 +267,13 @@ void cdcd_init(void) {
uint8_t *epin_buf = _cdcd_epbuf[i].epin;
#endif
- tu_edpt_stream_init(&p_cdc->rx_stream, false, false, false, p_cdc->rx_ff_buf, CFG_TUD_CDC_RX_BUFSIZE, epout_buf,
- CFG_TUD_CDC_EP_BUFSIZE);
+ tu_edpt_stream_init(&p_cdc->rx_stream, false, false, false, p_cdc->rx_ff_buf, CFG_TUD_CDC_RX_BUFSIZE, epout_buf);
// TX fifo can be configured to change to overwritable if not connected (DTR bit not set). Without DTR we do not
// know if data is actually polled by terminal. This way the most current data is prioritized.
// Default: is overwritable
tu_edpt_stream_init(&p_cdc->tx_stream, false, true, _cdcd_cfg.tx_overwritabe_if_not_connected, p_cdc->tx_ff_buf,
- CFG_TUD_CDC_TX_BUFSIZE, epin_buf, CFG_TUD_CDC_EP_BUFSIZE);
+ CFG_TUD_CDC_TX_BUFSIZE, epin_buf);
}
}
@@ -348,8 +347,7 @@ uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16
TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
tu_edpt_stream_t *stream_tx = &p_cdc->tx_stream;
-
- tu_edpt_stream_open(stream_tx, rhport, desc_ep);
+ tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_CDC_TX_EPSIZE);
if (_cdcd_cfg.tx_persistent) {
tu_edpt_stream_write_xfer(stream_tx); // flush pending data
} else {
@@ -357,8 +355,8 @@ uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16
}
} else {
tu_edpt_stream_t *stream_rx = &p_cdc->rx_stream;
-
- tu_edpt_stream_open(stream_rx, rhport, desc_ep);
+ tu_edpt_stream_open(stream_rx, rhport, desc_ep,
+ CFG_TUD_CDC_RX_NEED_ZLP ? CFG_TUD_CDC_RX_EPSIZE : tu_edpt_packet_size(desc_ep));
if (!_cdcd_cfg.rx_persistent) {
tu_edpt_stream_clear(stream_rx);
}
@@ -513,7 +511,7 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
}
// Data sent to host, we continue to fetch from tx fifo to send.
- // Note: This will cause incorrect baudrate set in line coding. Though maybe the baudrate is not really important !
+ // Note: This will cause incorrect baudrate set in line coding. Though maybe the baudrate is not really important!
if (ep_addr == stream_tx->ep_addr) {
tud_cdc_tx_complete_cb(itf); // invoke callback to possibly refill tx fifo
diff --git a/src/class/cdc/cdc_device.h b/src/class/cdc/cdc_device.h
index 0809b578f..0348bd2ec 100644
--- a/src/class/cdc/cdc_device.h
+++ b/src/class/cdc/cdc_device.h
@@ -29,6 +29,10 @@
#include "cdc.h"
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
//--------------------------------------------------------------------+
// Class Driver Configuration
//--------------------------------------------------------------------+
@@ -37,41 +41,52 @@
#endif
#ifndef CFG_TUD_CDC_TX_BUFSIZE
- #define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+ #define CFG_TUD_CDC_TX_BUFSIZE TUD_EPSIZE_BULK_MAX
#endif
#ifndef CFG_TUD_CDC_RX_BUFSIZE
- #define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+ #define CFG_TUD_CDC_RX_BUFSIZE TUD_EPSIZE_BULK_MAX
#endif
-#if !defined(CFG_TUD_CDC_EP_BUFSIZE) && defined(CFG_TUD_CDC_EPSIZE)
- #warning CFG_TUD_CDC_EPSIZE is renamed to CFG_TUD_CDC_EP_BUFSIZE, please update to use the new name
- #define CFG_TUD_CDC_EP_BUFSIZE CFG_TUD_CDC_EPSIZE
+// EP_BUFSIZE is separated to RX_EPSIZE and TX_EPSIZE
+#ifndef CFG_TUD_CDC_RX_EPSIZE
+ #ifdef CFG_TUD_CDC_EP_BUFSIZE
+ #define CFG_TUD_CDC_RX_EPSIZE CFG_TUD_CDC_EP_BUFSIZE
+ #else
+ #define CFG_TUD_CDC_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
#endif
-#ifndef CFG_TUD_CDC_EP_BUFSIZE
- #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#ifndef CFG_TUD_CDC_TX_EPSIZE
+ #ifdef CFG_TUD_CDC_EP_BUFSIZE
+ #define CFG_TUD_CDC_TX_EPSIZE CFG_TUD_CDC_EP_BUFSIZE
+ #else
+ #define CFG_TUD_CDC_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
#endif
-#ifdef __cplusplus
- extern "C" {
+// Enable multi-packet RX transfer with ZLP termination for better throughput. Requires host support for ZLP.
+#ifndef CFG_TUD_CDC_RX_NEED_ZLP
+ #define CFG_TUD_CDC_RX_NEED_ZLP 0
+#endif
+
+#ifndef CFG_TUD_CDC_CONFIGURE_DEFAULT
+ #define CFG_TUD_CDC_CONFIGURE_DEFAULT() \
+ { \
+ .rx_persistent = false, \
+ .tx_persistent = false, \
+ .tx_overwritabe_if_not_connected = true, \
+ }
#endif
//--------------------------------------------------------------------+
// Driver Configuration
//--------------------------------------------------------------------+
-typedef struct TU_ATTR_PACKED {
- bool rx_persistent : 1; // keep rx fifo data even with bus reset or disconnect
- bool tx_persistent : 1; // keep tx fifo data even with reset or disconnect
- bool tx_overwritabe_if_not_connected : 1; // if not connected, tx fifo can be overwritten
+typedef struct {
+ bool rx_persistent; // keep rx fifo data even with bus reset or disconnect
+ bool tx_persistent; // keep tx fifo data even with reset or disconnect
+ bool tx_overwritabe_if_not_connected; // if not connected, tx fifo can be overwritten
} tud_cdc_configure_t;
-TU_VERIFY_STATIC(sizeof(tud_cdc_configure_t) == 1, "size is not correct");
-
-#define TUD_CDC_CONFIGURE_DEFAULT() { \
- .rx_persistent = false, \
- .tx_persistent = false, \
- .tx_overwritabe_if_not_connected = true, \
-}
// Configure CDC driver behavior
bool tud_cdc_configure(const tud_cdc_configure_t* driver_cfg);
@@ -84,13 +99,13 @@ bool tud_cdc_configure(const tud_cdc_configure_t* driver_cfg);
// Application API (Multiple Ports) i.e. CFG_TUD_CDC > 1
//--------------------------------------------------------------------+
-// Check if interface is ready
+// Check if the interface is ready
bool tud_cdc_n_ready(uint8_t itf);
-// Check if terminal is connected to this port
+// Check if the terminal is connected to this port
bool tud_cdc_n_connected(uint8_t itf);
-// Get current line state. Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send)
+// Get the current line state. Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send)
uint8_t tud_cdc_n_get_line_state(uint8_t itf);
// Get current line encoding: bit rate, stop bits parity etc ..
@@ -136,10 +151,9 @@ uint32_t tud_cdc_n_write_flush(uint8_t itf);
// Return the number of bytes (characters) available for writing to TX FIFO buffer in a single n_write operation.
uint32_t tud_cdc_n_write_available(uint8_t itf);
-// Clear the transmit FIFO
+// Clear the TX FIFO
bool tud_cdc_n_write_clear(uint8_t itf);
-
#if CFG_TUD_CDC_NOTIFY
bool tud_cdc_n_notify_msg(uint8_t itf, cdc_notify_msg_t *msg);
diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c
index 8f6dd7200..62c313b83 100644
--- a/src/class/cdc/cdc_host.c
+++ b/src/class/cdc/cdc_host.c
@@ -650,10 +650,10 @@ bool cdch_init(void) {
for (size_t i = 0; i < CFG_TUH_CDC; i++) {
cdch_interface_t *p_cdc = &cdch_data[i];
cdch_epbuf_t *epbuf = &cdch_epbuf[i];
- TU_ASSERT(tu_edpt_stream_init(&p_cdc->stream.tx, true, true, false, p_cdc->stream.tx_ff_buf, CFG_TUH_CDC_TX_BUFSIZE,
- epbuf->tx, CFG_TUH_CDC_TX_EPSIZE));
+ TU_ASSERT(tu_edpt_stream_init(&p_cdc->stream.tx, true, true, false, p_cdc->stream.tx_ff_buf,
+ CFG_TUH_CDC_TX_BUFSIZE, epbuf->tx));
TU_ASSERT(tu_edpt_stream_init(&p_cdc->stream.rx, true, false, false, p_cdc->stream.rx_ff_buf,
- CFG_TUH_CDC_RX_BUFSIZE, epbuf->rx, CFG_TUH_CDC_RX_EPSIZE));
+ CFG_TUH_CDC_RX_BUFSIZE, epbuf->rx));
}
return true;
@@ -737,7 +737,7 @@ static bool open_ep_stream_pair(cdch_interface_t *p_cdc, tusb_desc_endpoint_t co
TU_ASSERT(tuh_edpt_open(p_cdc->daddr, desc_ep));
const uint8_t ep_dir = tu_edpt_dir(desc_ep->bEndpointAddress);
tu_edpt_stream_t *stream = (ep_dir == TUSB_DIR_IN) ? &p_cdc->stream.rx : &p_cdc->stream.tx;
- tu_edpt_stream_open(stream, p_cdc->daddr, desc_ep);
+ tu_edpt_stream_open(stream, p_cdc->daddr, desc_ep, tu_edpt_packet_size(desc_ep));
tu_edpt_stream_clear(stream);
desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(desc_ep);
diff --git a/src/class/cdc/cdc_host.h b/src/class/cdc/cdc_host.h
index 57919c7ff..1b1709b18 100644
--- a/src/class/cdc/cdc_host.h
+++ b/src/class/cdc/cdc_host.h
@@ -39,22 +39,22 @@ extern "C" {
// RX FIFO size
#ifndef CFG_TUH_CDC_RX_BUFSIZE
- #define CFG_TUH_CDC_RX_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_CDC_RX_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
// RX Endpoint size
#ifndef CFG_TUH_CDC_RX_EPSIZE
- #define CFG_TUH_CDC_RX_EPSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_CDC_RX_EPSIZE TUH_EPSIZE_BULK_MAX
#endif
// TX FIFO size
#ifndef CFG_TUH_CDC_TX_BUFSIZE
- #define CFG_TUH_CDC_TX_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_CDC_TX_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
// TX Endpoint size
#ifndef CFG_TUH_CDC_TX_EPSIZE
- #define CFG_TUH_CDC_TX_EPSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_CDC_TX_EPSIZE TUH_EPSIZE_BULK_MAX
#endif
//--------------------------------------------------------------------+
diff --git a/src/class/dfu/dfu_device.c b/src/class/dfu/dfu_device.c
index d3cc53918..ee57621b8 100644
--- a/src/class/dfu/dfu_device.c
+++ b/src/class/dfu/dfu_device.c
@@ -60,7 +60,9 @@ typedef struct {
static dfu_state_ctx_t _dfu_ctx;
+#if CFG_TUD_DFU_XFER_BUFSIZE > CFG_TUD_ENDPOINT0_BUFSIZE
TU_ATTR_ALIGNED(4) uint8_t _transfer_buf[CFG_TUD_DFU_XFER_BUFSIZE];
+#endif
static void reset_state(void) {
_dfu_ctx.state = DFU_IDLE;
@@ -68,6 +70,15 @@ static void reset_state(void) {
_dfu_ctx.flashing_in_progress = false;
}
+static inline uint8_t* get_xfer_buffer(void) {
+ // Use EP0 buffer if it is large enough, otherwise use dedicated buffer
+ #if CFG_TUD_DFU_XFER_BUFSIZE > CFG_TUD_ENDPOINT0_BUFSIZE
+ return _transfer_buf;
+ #else
+ return usbd_get_ctrl_buf();
+ #endif
+}
+
static bool reply_getstatus(uint8_t rhport, const tusb_control_request_t* request, dfu_state_t state, dfu_status_t status, uint32_t timeout);
static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request);
static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request);
@@ -283,10 +294,10 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control
TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_UPLOAD);
TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE);
- const uint16_t xfer_len = tud_dfu_upload_cb(_dfu_ctx.alt, request->wValue, _transfer_buf,
+ const uint16_t xfer_len = tud_dfu_upload_cb(_dfu_ctx.alt, request->wValue, get_xfer_buffer(),
request->wLength);
- return tud_control_xfer(rhport, request, _transfer_buf, xfer_len);
+ return tud_control_xfer(rhport, request, get_xfer_buffer(), xfer_len);
}
break;
@@ -306,7 +317,7 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control
if (request->wLength > 0) {
// Download with payload -> transition to DOWNLOAD SYNC
_dfu_ctx.state = DFU_DNLOAD_SYNC;
- return tud_control_xfer(rhport, request, _transfer_buf, request->wLength);
+ return tud_control_xfer(rhport, request, get_xfer_buffer(), request->wLength);
} else {
// Download is complete -> transition to MANIFEST SYNC
_dfu_ctx.state = DFU_MANIFEST_SYNC;
@@ -327,7 +338,7 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control
default:
if (stage == CONTROL_STAGE_SETUP) {
- return reply_getstatus(rhport, request, _dfu_ctx.state, _dfu_ctx.status, 0);
+ return reply_getstatus(rhport, request, (dfu_state_t) _dfu_ctx.state, (dfu_status_t) _dfu_ctx.status, 0);
}
break;
}
@@ -376,11 +387,11 @@ static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tus
timeout = 0;
}
- return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout);
+ return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout);
} else if (stage == CONTROL_STAGE_ACK) {
if (_dfu_ctx.flashing_in_progress) {
_dfu_ctx.state = DFU_DNBUSY;
- tud_dfu_download_cb(_dfu_ctx.alt, _dfu_ctx.block, _transfer_buf, _dfu_ctx.length);
+ tud_dfu_download_cb(_dfu_ctx.alt, _dfu_ctx.block, get_xfer_buffer(), _dfu_ctx.length);
} else {
_dfu_ctx.state = DFU_DNLOAD_IDLE;
}
@@ -405,7 +416,7 @@ static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tus
timeout = 0;
}
- return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout);
+ return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout);
} else if (stage == CONTROL_STAGE_ACK) {
if (_dfu_ctx.flashing_in_progress) {
_dfu_ctx.state = DFU_MANIFEST;
diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c
index 023a81595..de4ff5dd8 100644
--- a/src/class/midi/midi_device.c
+++ b/src/class/midi/midi_device.c
@@ -74,8 +74,8 @@ static midid_interface_t _midid_itf[CFG_TUD_MIDI];
#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
// Endpoint Transfer buffer: not used if dedicated hw FIFO is available
typedef struct {
- TUD_EPBUF_DEF(epin, CFG_TUD_MIDI_EP_BUFSIZE);
- TUD_EPBUF_DEF(epout, CFG_TUD_MIDI_EP_BUFSIZE);
+ TUD_EPBUF_DEF(epin, CFG_TUD_MIDI_TX_EPSIZE);
+ TUD_EPBUF_DEF(epout, CFG_TUD_MIDI_RX_EPSIZE);
} midid_epbuf_t;
CFG_TUD_MEM_SECTION static midid_epbuf_t _midid_epbuf[CFG_TUD_MIDI];
@@ -168,6 +168,108 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, ui
return total_read;
}
+// Note: this function shares stream->buffer with tud_midi_n_stream_read().
+// Do not mix calls to both functions on the same interface.
+uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize) {
+ TU_VERIFY(p_cable_num != NULL && buffer != NULL && bufsize > 0, 0);
+
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ midi_driver_stream_t *stream = &p_midi->stream_read;
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
+
+ uint8_t *buf8 = (uint8_t *)buffer;
+ uint32_t total_read = 0;
+
+ // Initialize to invalid cable so callers can detect "no data" even when
+ // the return value is 0.
+ *p_cable_num = 0xff;
+
+ // If there are leftover bytes from a previous partial read, return them first
+ if (stream->total > 0) {
+ *p_cable_num = (stream->buffer[0] >> 4) & 0x0f;
+ const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize);
+ TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count));
+
+ total_read += count;
+ stream->index += count;
+ buf8 += count;
+ bufsize -= count;
+
+ if (stream->total == stream->index) {
+ stream->index = 0;
+ stream->total = 0;
+ }
+
+ if (bufsize == 0) {
+ return total_read;
+ }
+ }
+
+ while (bufsize > 0) {
+ // Peek at next packet header to get cable number without consuming
+ uint8_t one_byte;
+ if (!tu_edpt_stream_peek(ep_str, &one_byte)) {
+ break;
+ }
+
+ const uint8_t next_cable = (one_byte >> 4) & 0x0f;
+
+ // Stop if cable changed (covers both leftover-originated reads and
+ // freshly consumed packets — total_read > 0 in either case)
+ if (total_read > 0 && next_cable != *p_cable_num) {
+ break;
+ }
+ *p_cable_num = next_cable;
+
+ // Consume the packet
+ if (!tud_midi_n_packet_read(itf, stream->buffer)) {
+ break;
+ }
+
+ const uint8_t code_index = stream->buffer[0] & 0x0f;
+ uint8_t msg_bytes;
+
+ // MIDI 1.0 Table 4-1: Code Index Number Classifications
+ switch (code_index) {
+ case MIDI_CIN_MISC:
+ case MIDI_CIN_CABLE_EVENT:
+ // Reserved and unused, skip this packet
+ continue;
+
+ case MIDI_CIN_SYSEX_END_1BYTE:
+ case MIDI_CIN_1BYTE_DATA:
+ msg_bytes = 1;
+ break;
+
+ case MIDI_CIN_SYSCOM_2BYTE:
+ case MIDI_CIN_SYSEX_END_2BYTE:
+ case MIDI_CIN_PROGRAM_CHANGE:
+ case MIDI_CIN_CHANNEL_PRESSURE:
+ msg_bytes = 2;
+ break;
+
+ default:
+ msg_bytes = 3;
+ break;
+ }
+
+ const uint8_t count = (uint8_t)tu_min32((uint32_t)msg_bytes, bufsize);
+ TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1, count));
+
+ total_read += count;
+ buf8 += count;
+ bufsize -= count;
+
+ if (count < msg_bytes) {
+ // Output buffer full, save remaining for next call
+ stream->total = msg_bytes;
+ stream->index = count;
+ }
+ }
+
+ return total_read;
+}
+
bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]) {
midid_interface_t *p_midi = &_midid_itf[itf];
tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
@@ -324,10 +426,10 @@ void midid_init(void) {
#endif
tu_edpt_stream_init(&p_midi->ep_stream.rx, false, false, false, p_midi->ep_stream.rx_ff_buf,
- CFG_TUD_MIDI_RX_BUFSIZE, epout_buf, CFG_TUD_MIDI_EP_BUFSIZE);
+ CFG_TUD_MIDI_RX_BUFSIZE, epout_buf);
tu_edpt_stream_init(&p_midi->ep_stream.tx, false, true, false, p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI_TX_BUFSIZE,
- epin_buf, CFG_TUD_MIDI_EP_BUFSIZE);
+ epin_buf);
}
}
@@ -408,11 +510,11 @@ uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uint1
if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
tu_edpt_stream_t *stream_tx = &p_midi->ep_stream.tx;
- tu_edpt_stream_open(stream_tx, rhport, desc_ep);
+ tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_MIDI_TX_EPSIZE);
tu_edpt_stream_clear(stream_tx);
} else {
tu_edpt_stream_t *stream_rx = &p_midi->ep_stream.rx;
- tu_edpt_stream_open(stream_rx, rhport, desc_ep);
+ tu_edpt_stream_open(stream_rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep));
tu_edpt_stream_clear(stream_rx);
TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare to receive data
}
diff --git a/src/class/midi/midi_device.h b/src/class/midi/midi_device.h
index ddbc2f9f0..57eabec1f 100644
--- a/src/class/midi/midi_device.h
+++ b/src/class/midi/midi_device.h
@@ -34,13 +34,20 @@
// Class Driver Configuration
//--------------------------------------------------------------------+
-#if !defined(CFG_TUD_MIDI_EP_BUFSIZE) && defined(CFG_TUD_MIDI_EPSIZE)
- #warning CFG_TUD_MIDI_EPSIZE is renamed to CFG_TUD_MIDI_EP_BUFSIZE, please update to use the new name
- #define CFG_TUD_MIDI_EP_BUFSIZE CFG_TUD_MIDI_EPSIZE
+#ifndef CFG_TUD_MIDI_RX_EPSIZE
+ #ifdef CFG_TUD_MIDI_EP_BUFSIZE
+ #define CFG_TUD_MIDI_RX_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE
+ #else
+ #define CFG_TUD_MIDI_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
#endif
-#ifndef CFG_TUD_MIDI_EP_BUFSIZE
- #define CFG_TUD_MIDI_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#ifndef CFG_TUD_MIDI_TX_EPSIZE
+ #ifdef CFG_TUD_MIDI_EP_BUFSIZE
+ #define CFG_TUD_MIDI_TX_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE
+ #else
+ #define CFG_TUD_MIDI_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
#endif
#ifdef __cplusplus
@@ -66,6 +73,13 @@ uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num);
// Read byte stream (legacy)
uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize);
+// Read byte stream with cable demultiplexing: returns the cable number of the
+// data that was read. Reads from a single cable per call; stops when the next
+// packet belongs to a different cable so the caller can dispatch per-cable.
+// Note: shares internal state with tud_midi_n_stream_read(); do not mix both
+// on the same interface.
+uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize);
+
// Write byte Stream (legacy)
uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize);
@@ -97,6 +111,11 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_read(void *buffer,
}
TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi_demux_stream_read(uint8_t *p_cable_num, void *buffer, uint32_t bufsize) {
+ return tud_midi_n_demux_stream_read(0, p_cable_num, buffer, bufsize);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
tud_midi_stream_write(uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) {
return tud_midi_n_stream_write(0, cable_num, buffer, bufsize);
}
diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c
index 5548a0ba8..0feb5106d 100644
--- a/src/class/midi/midi_host.c
+++ b/src/class/midi/midi_host.c
@@ -83,8 +83,8 @@ typedef struct {
}midih_interface_t;
typedef struct {
- TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MPS);
- TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MPS);
+ TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MAX);
+ TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MAX);
} midih_epbuf_t;
static midih_interface_t _midi_host[CFG_TUH_MIDI];
@@ -121,9 +121,9 @@ bool midih_init(void) {
for (int inst = 0; inst < CFG_TUH_MIDI; inst++) {
midih_interface_t *p_midi_host = &_midi_host[inst];
tu_edpt_stream_init(&p_midi_host->ep_stream.rx, true, false, false,
- p_midi_host->ep_stream.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, _midi_epbuf->rx, TUH_EPSIZE_BULK_MPS);
+ p_midi_host->ep_stream.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, _midi_epbuf[inst].rx);
tu_edpt_stream_init(&p_midi_host->ep_stream.tx, true, true, false,
- p_midi_host->ep_stream.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, _midi_epbuf->tx, TUH_EPSIZE_BULK_MPS);
+ p_midi_host->ep_stream.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, _midi_epbuf[inst].tx);
}
return true;
}
@@ -306,7 +306,7 @@ uint16_t midih_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_
ep_stream = &p_midi->ep_stream.rx;
}
TU_ASSERT(tuh_edpt_open(dev_addr, p_ep), 0);
- tu_edpt_stream_open(ep_stream, dev_addr, p_ep);
+ tu_edpt_stream_open(ep_stream, dev_addr, p_ep, tu_edpt_packet_size(p_ep));
tu_edpt_stream_clear(ep_stream);
break;
diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h
index 8a8dccab4..b9ab0130d 100644
--- a/src/class/midi/midi_host.h
+++ b/src/class/midi/midi_host.h
@@ -38,15 +38,15 @@ extern "C" {
// Class Driver Configuration
//--------------------------------------------------------------------+
#ifndef CFG_TUH_MIDI_RX_BUFSIZE
- #define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
#ifndef CFG_TUH_MIDI_TX_BUFSIZE
- #define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
#ifndef CFG_TUH_MIDI_EP_BUFSIZE
- #define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
// Enable the MIDI stream read/write API. Some library can work with raw USB MIDI packet
diff --git a/src/class/printer/printer.h b/src/class/printer/printer.h
new file mode 100644
index 000000000..09d1a8956
--- /dev/null
+++ b/src/class/printer/printer.h
@@ -0,0 +1,62 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_PRINTER_H_
+#define TUSB_PRINTER_H_
+
+#include "common/tusb_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Printer Class Specific Control Request
+typedef enum {
+ TUSB_PRINTER_REQUEST_GET_DEVICE_ID = 0x00, ///< Get device ID
+ TUSB_PRINTER_REQUEST_GET_PORT_STATUS = 0x01, ///< Get port status
+ TUSB_PRINTER_REQUEST_SOFT_RESET = 0x02, ///< Soft reset
+} tusb_printer_request_type_t;
+
+/// Printer Port Status (returned by GET_PORT_STATUS request)
+/// USB Printer Class spec 1.1, Section 4.2
+typedef union TU_ATTR_PACKED {
+ uint8_t status;
+ struct TU_ATTR_PACKED {
+ uint8_t reserved0 : 3; ///< Reserved (bits 0-2)
+ uint8_t not_error : 1; ///< 1 = no error, 0 = error
+ uint8_t selected : 1; ///< 1 = selected (online), 0 = not selected
+ uint8_t paper_empty : 1; ///< 1 = paper empty, 0 = paper not empty
+ uint8_t reserved6 : 2; ///< Reserved (bits 6-7)
+ } status_bm;
+} tusb_printer_port_status_t;
+
+TU_VERIFY_STATIC(sizeof(tusb_printer_port_status_t) == 1, "size is not correct");
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/printer/printer_device.c b/src/class/printer/printer_device.c
new file mode 100644
index 000000000..d2dc9b163
--- /dev/null
+++ b/src/class/printer/printer_device.c
@@ -0,0 +1,329 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (CFG_TUD_ENABLED && CFG_TUD_PRINTER)
+
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+
+#include "printer_device.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t itf_num;
+
+ /*------------- From this point, data is not cleared by bus reset -------------*/
+
+ tu_edpt_stream_t rx_stream;
+ tu_edpt_stream_t tx_stream;
+
+ uint8_t rx_ff_buf[CFG_TUD_PRINTER_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUD_PRINTER_TX_BUFSIZE];
+} printer_interface_t;
+
+#define ITF_MEM_RESET_SIZE offsetof(printer_interface_t, rx_stream)
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+typedef struct {
+ TUD_EPBUF_DEF(epout, CFG_TUD_PRINTER_RX_EPSIZE);
+ TUD_EPBUF_DEF(epin, CFG_TUD_PRINTER_TX_EPSIZE);
+} printer_epbuf_t;
+
+CFG_TUD_MEM_SECTION static printer_epbuf_t _printer_epbuf[CFG_TUD_PRINTER];
+#endif
+
+static printer_interface_t _printer_itf[CFG_TUD_PRINTER];
+
+//--------------------------------------------------------------------+
+// INTERNAL HELPERS
+//--------------------------------------------------------------------+
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t _find_itf(uint8_t ep_addr) {
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ const printer_interface_t *p = &_printer_itf[i];
+ if (ep_addr == p->rx_stream.ep_addr || ep_addr == p->tx_stream.ep_addr) {
+ return i;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_printer_rx_cb(uint8_t itf) {
+ (void)itf;
+}
+
+TU_ATTR_WEAK void tud_printer_tx_complete_cb(uint8_t itf) {
+ (void)itf;
+}
+
+TU_ATTR_WEAK void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request) {
+ (void)itf;
+ (void)request;
+}
+
+TU_ATTR_WEAK uint8_t const *tud_printer_get_device_id_cb(uint8_t itf) {
+ (void)itf;
+ return NULL;
+}
+
+TU_ATTR_WEAK uint8_t tud_printer_get_port_status_cb(uint8_t itf) {
+ (void)itf;
+ return 0x18; // not error, selected, paper not empty
+}
+
+TU_ATTR_WEAK void tud_printer_soft_reset_cb(uint8_t itf) {
+ (void)itf;
+}
+
+//--------------------------------------------------------------------+
+// READ API
+//--------------------------------------------------------------------+
+uint32_t tud_printer_n_read_available(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_read_available(&_printer_itf[itf].rx_stream);
+}
+
+uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_read(&_printer_itf[itf].rx_stream, buffer, bufsize);
+}
+
+bool tud_printer_n_peek(uint8_t itf, uint8_t *chr) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER);
+ return tu_edpt_stream_peek(&_printer_itf[itf].rx_stream, chr);
+}
+
+void tud_printer_n_read_flush(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, );
+ printer_interface_t *p = &_printer_itf[itf];
+ tu_edpt_stream_clear(&p->rx_stream);
+ tu_edpt_stream_read_xfer(&p->rx_stream);
+}
+
+//--------------------------------------------------------------------+
+// WRITE API
+//--------------------------------------------------------------------+
+uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_write(&_printer_itf[itf].tx_stream, buffer, bufsize);
+}
+
+uint32_t tud_printer_n_write_flush(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_write_xfer(&_printer_itf[itf].tx_stream);
+}
+
+uint32_t tud_printer_n_write_available(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_write_available(&_printer_itf[itf].tx_stream);
+}
+
+bool tud_printer_n_write_clear(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER);
+ tu_edpt_stream_clear(&_printer_itf[itf].tx_stream);
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// USBD-CLASS API
+//--------------------------------------------------------------------+
+void printerd_init(void) {
+ tu_memclr(_printer_itf, sizeof(_printer_itf));
+
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ printer_interface_t *p = &_printer_itf[i];
+
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ uint8_t *epout_buf = NULL;
+ uint8_t *epin_buf = NULL;
+ #else
+ uint8_t *epout_buf = _printer_epbuf[i].epout;
+ uint8_t *epin_buf = _printer_epbuf[i].epin;
+ #endif
+
+ tu_edpt_stream_init(&p->rx_stream, false, false, false,
+ p->rx_ff_buf, CFG_TUD_PRINTER_RX_BUFSIZE, epout_buf);
+
+ tu_edpt_stream_init(&p->tx_stream, false, true, true,
+ p->tx_ff_buf, CFG_TUD_PRINTER_TX_BUFSIZE, epin_buf);
+ }
+}
+
+bool printerd_deinit(void) {
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ printer_interface_t *p = &_printer_itf[i];
+ tu_edpt_stream_deinit(&p->rx_stream);
+ tu_edpt_stream_deinit(&p->tx_stream);
+ }
+ return true;
+}
+
+void printerd_reset(uint8_t rhport) {
+ (void)rhport;
+
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ printer_interface_t *p = &_printer_itf[i];
+ tu_memclr(p, ITF_MEM_RESET_SIZE);
+ tu_edpt_stream_close(&p->rx_stream);
+ tu_edpt_stream_close(&p->tx_stream);
+ }
+}
+
+uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len) {
+ TU_VERIFY(TUSB_CLASS_PRINTER == itf_desc->bInterfaceClass, 0);
+
+ // Find available interface slot
+ uint8_t const printer_id = _find_itf(0);
+ TU_ASSERT(printer_id < CFG_TUD_PRINTER, 0);
+ printer_interface_t *p = &_printer_itf[printer_id];
+
+ p->itf_num = itf_desc->bInterfaceNumber;
+
+ //------------- Endpoints -------------//
+ const uint8_t *p_desc = (const uint8_t *)itf_desc;
+ const uint8_t *desc_end = p_desc + max_len;
+ uint16_t drv_len = sizeof(tusb_desc_interface_t);
+
+ p_desc = tu_desc_next(itf_desc);
+ for (uint8_t e = 0; e < itf_desc->bNumEndpoints; e++) {
+ TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0);
+ const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc;
+ TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0);
+
+ TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
+
+ if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
+ tu_edpt_stream_t *stream_tx = &p->tx_stream;
+ tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_PRINTER_TX_EPSIZE);
+ tu_edpt_stream_clear(stream_tx);
+ } else {
+ tu_edpt_stream_t *stream_rx = &p->rx_stream;
+ tu_edpt_stream_open(stream_rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep));
+ tu_edpt_stream_clear(stream_rx);
+ TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0);
+ }
+
+ drv_len += sizeof(tusb_desc_endpoint_t);
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ return drv_len;
+}
+
+bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request) {
+ TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE &&
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS);
+
+ // GET_DEVICE_ID: wIndex = (interface_number << 8) | alt_setting
+ // GET_PORT_STATUS / SOFT_RESET: wIndex = interface_number
+ uint8_t itf_num;
+ if (TUSB_PRINTER_REQUEST_GET_DEVICE_ID == request->bRequest) {
+ itf_num = tu_u16_high(request->wIndex);
+ } else {
+ itf_num = tu_u16_low(request->wIndex);
+ }
+
+ // Find the printer instance index from the USB interface number
+ uint8_t itf = TUSB_INDEX_INVALID_8;
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ if (_printer_itf[i].itf_num == itf_num) {
+ itf = i;
+ break;
+ }
+ }
+ TU_VERIFY(itf < CFG_TUD_PRINTER);
+
+ // https://www.usb.org/sites/default/files/usbprint11a021811.pdf
+ if (stage == CONTROL_STAGE_SETUP) {
+ switch (request->bRequest) {
+ case TUSB_PRINTER_REQUEST_GET_DEVICE_ID: {
+ const uint8_t *device_id = tud_printer_get_device_id_cb(itf);
+ TU_VERIFY(device_id);
+ const uint16_t total_len = (uint16_t)((device_id[0] << 8) | device_id[1]);
+ return tud_control_xfer(rhport, request, (void *)(uintptr_t)device_id, total_len);
+ }
+
+ case TUSB_PRINTER_REQUEST_GET_PORT_STATUS: {
+ static uint8_t port_status;
+ port_status = tud_printer_get_port_status_cb(itf);
+ return tud_control_xfer(rhport, request, &port_status, sizeof(port_status));
+ }
+
+ case TUSB_PRINTER_REQUEST_SOFT_RESET:
+ tud_printer_soft_reset_cb(itf);
+ tud_control_status(rhport, request);
+ return true;
+
+ default:
+ return false;
+ }
+ } else if (stage == CONTROL_STAGE_ACK) {
+ tud_printer_request_complete_cb(itf, request);
+ }
+
+ return true;
+}
+
+bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void)rhport;
+ (void)result;
+
+ uint8_t const itf = _find_itf(ep_addr);
+ TU_ASSERT(itf < CFG_TUD_PRINTER);
+ printer_interface_t *p = &_printer_itf[itf];
+
+ // Received new data
+ if (ep_addr == p->rx_stream.ep_addr) {
+ tu_edpt_stream_read_xfer_complete(&p->rx_stream, xferred_bytes);
+
+ if (!tu_edpt_stream_empty(&p->rx_stream)) {
+ tud_printer_rx_cb(itf);
+ }
+
+ tu_edpt_stream_read_xfer(&p->rx_stream);
+ }
+
+ // Data sent to host
+ if (ep_addr == p->tx_stream.ep_addr) {
+ tud_printer_tx_complete_cb(itf);
+
+ if (0 == tu_edpt_stream_write_xfer(&p->tx_stream)) {
+ tu_edpt_stream_write_zlp_if_needed(&p->tx_stream, xferred_bytes);
+ }
+ }
+
+ return true;
+}
+
+#endif
diff --git a/src/class/printer/printer_device.h b/src/class/printer/printer_device.h
new file mode 100644
index 000000000..afde1f022
--- /dev/null
+++ b/src/class/printer/printer_device.h
@@ -0,0 +1,151 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_PRINTER_DEVICE_H_
+#define TUSB_PRINTER_DEVICE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "printer.h"
+
+//--------------------------------------------------------------------+
+// Configuration
+//--------------------------------------------------------------------+
+#ifndef CFG_TUD_PRINTER_RX_EPSIZE
+ #define CFG_TUD_PRINTER_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUD_PRINTER_TX_EPSIZE
+ #define CFG_TUD_PRINTER_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+//--------------------------------------------------------------------+
+// Application API (Multiple Ports) i.e. CFG_TUD_PRINTER > 1
+//--------------------------------------------------------------------+
+
+// Get the number of bytes available for reading
+uint32_t tud_printer_n_read_available(uint8_t itf);
+
+// Read received bytes
+uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize);
+
+// Get the number of bytes available for writing
+uint32_t tud_printer_n_write_available(uint8_t itf);
+
+// Clear the received FIFO
+void tud_printer_n_read_flush(uint8_t itf);
+
+// Get a byte from FIFO without removing it
+bool tud_printer_n_peek(uint8_t itf, uint8_t *ui8);
+
+// Write data to host
+uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize);
+
+// Force sending data in the TX FIFO
+uint32_t tud_printer_n_write_flush(uint8_t itf);
+
+// Clear the transmit FIFO
+bool tud_printer_n_write_clear(uint8_t itf);
+
+//--------------------------------------------------------------------+
+// Application API (Single Port)
+//--------------------------------------------------------------------+
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read_available(void) {
+ return tud_printer_n_read_available(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_available(void) {
+ return tud_printer_n_write_available(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read(void *buffer, uint32_t bufsize) {
+ return tud_printer_n_read(0, buffer, bufsize);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void tud_printer_read_flush(void) {
+ tud_printer_n_read_flush(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_peek(uint8_t *ui8) {
+ return tud_printer_n_peek(0, ui8);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write(const void *buffer, uint32_t bufsize) {
+ return tud_printer_n_write(0, buffer, bufsize);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_flush(void) {
+ return tud_printer_n_write_flush(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_write_clear(void) {
+ return tud_printer_n_write_clear(0);
+}
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak is optional)
+//--------------------------------------------------------------------+
+
+// Invoked when received new data
+void tud_printer_rx_cb(uint8_t itf);
+
+// Invoked when last write transfer is completed
+void tud_printer_tx_complete_cb(uint8_t itf);
+
+// Invoked when host requests device ID string (IEEE 1284).
+// Application returns pointer to device ID buffer (must remain valid until transfer completes).
+// First 2 bytes of returned buffer must contain big-endian length (including the 2 length bytes).
+const uint8_t *tud_printer_get_device_id_cb(uint8_t itf);
+
+// Invoked when host requests port status.
+uint8_t tud_printer_get_port_status_cb(uint8_t itf);
+
+// Invoked when host requests soft reset.
+void tud_printer_soft_reset_cb(uint8_t itf);
+
+// Invoked when a control request is completed (GET_DEVICE_ID, GET_PORT_STATUS, etc.)
+void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request);
+
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void printerd_init(void);
+bool printerd_deinit(void);
+void printerd_reset(uint8_t rhport);
+uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len);
+bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request);
+bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c
index b917c8dc7..e1017ba48 100644
--- a/src/class/vendor/vendor_device.c
+++ b/src/class/vendor/vendor_device.c
@@ -49,8 +49,7 @@ typedef struct {
#else
uint8_t ep_in;
uint8_t ep_out;
- uint16_t ep_in_mps;
- uint16_t ep_out_mps;
+ uint16_t rx_xfer_len;
#endif
} vendord_interface_t;
@@ -62,15 +61,15 @@ typedef struct {
static vendord_interface_t _vendord_itf[CFG_TUD_VENDOR];
- // Skip local EP buffer if dedicated hw FIFO is supported or no fifo mode
- #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || !CFG_TUD_VENDOR_TXRX_BUFFERED
+// Skip local EP buffer if dedicated hw FIFO is supported or no fifo mode
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || !CFG_TUD_VENDOR_TXRX_BUFFERED
typedef struct {
- TUD_EPBUF_DEF(epout, CFG_TUD_VENDOR_EPSIZE);
- TUD_EPBUF_DEF(epin, CFG_TUD_VENDOR_EPSIZE);
+ TUD_EPBUF_DEF(epout, CFG_TUD_VENDOR_RX_EPSIZE);
+ TUD_EPBUF_DEF(epin, CFG_TUD_VENDOR_TX_EPSIZE);
} vendord_epbuf_t;
CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR];
- #endif
+#endif
//--------------------------------------------------------------------+
// Weak stubs: invoked if no strong implementation is available
@@ -86,9 +85,6 @@ TU_ATTR_WEAK void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes) {
(void) sent_bytes;
}
-//--------------------------------------------------------------------
-// Application API
-//--------------------------------------------------------------------
bool tud_vendor_n_mounted(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR);
vendord_interface_t *p_itf = &_vendord_itf[idx];
@@ -128,9 +124,9 @@ void tud_vendor_n_read_flush(uint8_t idx) {
tu_edpt_stream_clear(&p_itf->rx_stream);
tu_edpt_stream_read_xfer(&p_itf->rx_stream);
}
-#endif
+ #endif
-#if CFG_TUD_VENDOR_RX_MANUAL_XFER
+ #if CFG_TUD_VENDOR_RX_MANUAL_XFER
bool tud_vendor_n_read_xfer(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR);
vendord_interface_t *p_itf = &_vendord_itf[idx];
@@ -141,7 +137,7 @@ bool tud_vendor_n_read_xfer(uint8_t idx) {
#else
// Non-FIFO mode
TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_out));
- return usbd_edpt_xfer(p_itf->rhport, p_itf->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false);
+ return usbd_edpt_xfer(p_itf->rhport, p_itf->ep_out, _vendord_epbuf[idx].epout, p_itf->rx_xfer_len, false);
#endif
}
#endif
@@ -160,7 +156,7 @@ uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize) {
#else
// non-fifo mode: direct transfer
TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_in), 0);
- const uint32_t xact_len = tu_min32(bufsize, CFG_TUD_VENDOR_EPSIZE);
+ const uint32_t xact_len = tu_min32(bufsize, CFG_TUD_VENDOR_TX_EPSIZE);
memcpy(_vendord_epbuf[idx].epin, buffer, xact_len);
TU_ASSERT(usbd_edpt_xfer(p_itf->rhport, p_itf->ep_in, _vendord_epbuf[idx].epin, (uint16_t)xact_len, false), 0);
return xact_len;
@@ -177,7 +173,7 @@ uint32_t tud_vendor_n_write_available(uint8_t idx) {
#else
// Non-FIFO mode
TU_VERIFY(p_itf->ep_in > 0, 0); // must be opened
- return usbd_edpt_busy(p_itf->rhport, p_itf->ep_in) ? 0 : CFG_TUD_VENDOR_EPSIZE;
+ return usbd_edpt_busy(p_itf->rhport, p_itf->ep_in) ? 0 : CFG_TUD_VENDOR_TX_EPSIZE;
#endif
}
@@ -215,12 +211,10 @@ void vendord_init(void) {
#endif
uint8_t *rx_ff_buf = p_itf->rx_ff_buf;
- tu_edpt_stream_init(&p_itf->rx_stream, false, false, false, rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, epout_buf,
- CFG_TUD_VENDOR_EPSIZE);
+ tu_edpt_stream_init(&p_itf->rx_stream, false, false, false, rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, epout_buf);
uint8_t *tx_ff_buf = p_itf->tx_ff_buf;
- tu_edpt_stream_init(&p_itf->tx_stream, false, true, false, tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, epin_buf,
- CFG_TUD_VENDOR_EPSIZE);
+ tu_edpt_stream_init(&p_itf->tx_stream, false, true, false, tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, epin_buf);
}
#endif
}
@@ -302,30 +296,31 @@ uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uin
const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc;
TU_ASSERT(usbd_edpt_open(rhport, desc_ep));
+ uint16_t rx_xfer_len = CFG_TUD_VENDOR_RX_NEED_ZLP ? CFG_TUD_VENDOR_RX_EPSIZE : tu_edpt_packet_size(desc_ep);
+
#if CFG_TUD_VENDOR_TXRX_BUFFERED
// open endpoint stream
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
tu_edpt_stream_t *tx_stream = &p_vendor->tx_stream;
- tu_edpt_stream_open(tx_stream, rhport, desc_ep);
+ tu_edpt_stream_open(tx_stream, rhport, desc_ep, CFG_TUD_VENDOR_TX_EPSIZE);
tu_edpt_stream_write_xfer(tx_stream); // flush pending data
} else {
tu_edpt_stream_t *rx_stream = &p_vendor->rx_stream;
- tu_edpt_stream_open(rx_stream, rhport, desc_ep);
+ tu_edpt_stream_open(rx_stream, rhport, desc_ep, rx_xfer_len);
#if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
TU_ASSERT(tu_edpt_stream_read_xfer(rx_stream) > 0, 0); // prepare for incoming data
#endif
}
#else
+ p_vendor->rx_xfer_len = rx_xfer_len;
// Non-FIFO mode: store endpoint info
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
p_vendor->ep_in = desc_ep->bEndpointAddress;
- p_vendor->ep_in_mps = tu_edpt_packet_size(desc_ep);
} else {
p_vendor->ep_out = desc_ep->bEndpointAddress;
- p_vendor->ep_out_mps = tu_edpt_packet_size(desc_ep);
#if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
// Prepare for incoming data
- TU_ASSERT(usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false), 0);
+ TU_ASSERT(usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, rx_xfer_len, false), 0);
#endif
}
#endif
@@ -367,7 +362,7 @@ bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
// Non-FIFO mode: invoke callback with buffer
tud_vendor_rx_cb(idx, _vendord_epbuf[idx].epout, xferred_bytes);
#if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
- usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false);
+ usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, p_vendor->rx_xfer_len, false);
#endif
} else if (ep_addr == p_vendor->ep_in) {
// Send complete
diff --git a/src/class/vendor/vendor_device.h b/src/class/vendor/vendor_device.h
index 101765bb1..cdca9fd15 100644
--- a/src/class/vendor/vendor_device.h
+++ b/src/class/vendor/vendor_device.h
@@ -36,18 +36,30 @@ extern "C" {
//--------------------------------------------------------------------+
// Configuration
//--------------------------------------------------------------------+
-#ifndef CFG_TUD_VENDOR_EPSIZE
- #define CFG_TUD_VENDOR_EPSIZE 64
+#ifndef CFG_TUD_VENDOR_RX_EPSIZE
+ #ifdef CFG_TUD_VENDOR_EPSIZE
+ #define CFG_TUD_VENDOR_RX_EPSIZE CFG_TUD_VENDOR_EPSIZE
+ #else
+ #define CFG_TUD_VENDOR_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
+#endif
+
+#ifndef CFG_TUD_VENDOR_TX_EPSIZE
+ #ifdef CFG_TUD_VENDOR_EPSIZE
+ #define CFG_TUD_VENDOR_TX_EPSIZE CFG_TUD_VENDOR_EPSIZE
+ #else
+ #define CFG_TUD_VENDOR_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
#endif
// RX FIFO can be disabled by setting this value to 0
#ifndef CFG_TUD_VENDOR_RX_BUFSIZE
- #define CFG_TUD_VENDOR_RX_BUFSIZE 64
+ #define CFG_TUD_VENDOR_RX_BUFSIZE TUD_EPSIZE_BULK_MAX
#endif
// TX FIFO can be disabled by setting this value to 0
#ifndef CFG_TUD_VENDOR_TX_BUFSIZE
- #define CFG_TUD_VENDOR_TX_BUFSIZE 64
+ #define CFG_TUD_VENDOR_TX_BUFSIZE TUD_EPSIZE_BULK_MAX
#endif
// Vendor is buffered (FIFO mode) if both TX and RX buffers are configured
@@ -62,6 +74,11 @@ extern "C" {
#define CFG_TUD_VENDOR_RX_MANUAL_XFER 0
#endif
+// Enable multi-packet RX transfer with ZLP termination for better throughput. Requires host support for ZLP.
+#ifndef CFG_TUD_VENDOR_RX_NEED_ZLP
+ #define CFG_TUD_VENDOR_RX_NEED_ZLP 0
+#endif
+
//--------------------------------------------------------------------+
// Application API (Multiple Interfaces) i.e CFG_TUD_VENDOR > 1
//--------------------------------------------------------------------+
diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c
index b2375def9..bbcfe45d5 100644
--- a/src/class/video/video_device.c
+++ b/src/class/video/video_device.c
@@ -70,6 +70,13 @@ typedef struct TU_ATTR_PACKED {
uint8_t bEntityId;
} tusb_desc_cs_video_entity_itf_t;
+typedef struct TU_ATTR_PACKED {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint16_t wMaxTransferSize;
+} tusb_desc_cs_video_vc_ep_t;
+
typedef union {
struct TU_ATTR_PACKED {
uint8_t bLength;
@@ -740,6 +747,9 @@ static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self)
/* The end of the video control interface descriptor. */
void const *end = _end_of_control_descriptor(vc);
if (vc->std.bNumEndpoints != 0) {
+ /* Extend end to cover the standard endpoint and class-specific endpoint descriptors
+ * that follow wTotalLength */
+ end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t);
/* Find the notification endpoint descriptor. */
cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT);
TU_ASSERT(cur < end);
@@ -780,6 +790,9 @@ static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, uint_fast8_t
if (vc->std.bNumEndpoints != 0) {
/* Support for 1 endpoint only. */
TU_VERIFY(1 == vc->std.bNumEndpoints);
+ /* Extend end to cover the standard endpoint and class-specific endpoint descriptors
+ * that follow wTotalLength */
+ end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t);
/* Find the notification endpoint descriptor. */
cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT);
TU_VERIFY(cur < end);
diff --git a/src/common/tusb_fifo.c b/src/common/tusb_fifo.c
index 9f188f296..06d25d131 100644
--- a/src/common/tusb_fifo.c
+++ b/src/common/tusb_fifo.c
@@ -30,11 +30,6 @@
#define TU_FIFO_DBG 0
-// Suppress IAR warning
-// Warning[Pa082]: undefined behavior: the order of volatile accesses is undefined in this statement
-#if defined(__ICCARM__)
- #pragma diag_suppress = Pa082
-#endif
#if OSAL_MUTEX_REQUIRED
@@ -115,7 +110,7 @@ void tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) {
// Can support multiple i.e both 16 and 32-bit data access if needed
//--------------------------------------------------------------------+
#if CFG_TUSB_FIFO_HWFIFO_API
- #if CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE
+ #if CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE > 0
#define HWFIFO_ADDR_NEXT_N(_hwfifo, _const, _n) _hwfifo = (_const volatile void *)((uintptr_t)(_hwfifo) + _n)
#else
#define HWFIFO_ADDR_NEXT_N(_hwfifo, _const, _n)
@@ -123,6 +118,9 @@ void tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) {
#define HWFIFO_ADDR_NEXT(_hwfifo, _const) HWFIFO_ADDR_NEXT_N(_hwfifo, _const, CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE)
+ // the fixed ratio works since in the only case of dynamic/multiple data_stride (rusb2): addr_stride is 0
+ #define HWFIFO_ADDR_DATA_RATIO (CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE / CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE)
+
//------------- Write -------------//
#ifndef CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE
TU_ATTR_ALWAYS_INLINE static inline void stride_write(volatile void *hwfifo, const void *src, uint8_t data_stride) {
@@ -240,16 +238,16 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co
len -= 2;
HWFIFO_ADDR_NEXT_N(hwfifo, const, 2);
}
- #endif
+ #endif
- #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS
+ #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS
// 8-bit access is allowed for odd bytes
while (len > 0) {
*dest++ = *((const volatile uint8_t *)hwfifo);
len--;
HWFIFO_ADDR_NEXT_N(hwfifo, const, 1);
}
- #else
+ #else
// Read odd bytes i.e 1 byte for 16 bit or 1-3 bytes for 32 bit
if (len > 0) {
uint32_t tmp;
@@ -257,7 +255,7 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co
memcpy(dest, &tmp, len);
HWFIFO_ADDR_NEXT(hwfifo, const);
}
- #endif
+ #endif
#endif
}
#endif
@@ -280,15 +278,15 @@ static void hwff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uin
HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_bytes);
tu_hwfifo_read(hwfifo, f->buffer, wrap_bytes, access_mode); // wrapped part
#else
- // Write full words to linear part of buffer
+ // Write full words to the linear part of the buffer
const uint8_t data_stride = access_mode->data_stride;
const uint32_t odd_mask = data_stride - 1;
uint16_t lin_even = lin_bytes & ~odd_mask;
tu_hwfifo_read(hwfifo, ff_buf, lin_even, access_mode);
- HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_even);
+ HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_even * HWFIFO_ADDR_DATA_RATIO);
ff_buf += lin_even;
- // There could be odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary
+ // There could be an odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary
// combine it with the wrapped part to form a full word for data stride
const uint8_t lin_odd = lin_bytes & odd_mask;
if (lin_odd > 0) {
@@ -342,7 +340,7 @@ static void hwff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t
const uint32_t odd_mask = data_stride - 1;
uint16_t lin_even = lin_bytes & ~odd_mask;
tu_hwfifo_write(hwfifo, ff_buf, lin_even, access_mode);
- HWFIFO_ADDR_NEXT_N(hwfifo, , lin_even);
+ HWFIFO_ADDR_NEXT_N(hwfifo, , lin_even * HWFIFO_ADDR_DATA_RATIO);
ff_buf += lin_even;
// There could be odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary
@@ -496,7 +494,9 @@ uint16_t tu_fifo_peek_n_access_mode(tu_fifo_t *f, void *p_buffer, uint16_t n, ui
// Read n items without removing it from the FIFO, correct read pointer if overflowed
uint16_t tu_fifo_peek_n(tu_fifo_t *f, void *p_buffer, uint16_t n) {
ff_lock(f->mutex_rd);
- const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, f->wr_idx, f->rd_idx, NULL);
+ const uint16_t wr_idx = f->wr_idx;
+ const uint16_t rd_idx = f->rd_idx;
+ const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, wr_idx, rd_idx, NULL);
ff_unlock(f->mutex_rd);
return ret;
}
@@ -506,7 +506,8 @@ uint16_t tu_fifo_read_n_access_mode(tu_fifo_t *f, void *buffer, uint16_t n, cons
ff_lock(f->mutex_rd);
// Peek the data: f->rd_idx might get modified in case of an overflow so we can not use a local variable
- n = tu_fifo_peek_n_access_mode(f, buffer, n, f->wr_idx, f->rd_idx, access_mode);
+ const uint16_t wr_idx = f->wr_idx;
+ n = tu_fifo_peek_n_access_mode(f, buffer, n, wr_idx, f->rd_idx, access_mode);
f->rd_idx = advance_index(f->depth, f->rd_idx, n);
ff_unlock(f->mutex_rd);
@@ -633,7 +634,8 @@ static bool ff_peek_local(tu_fifo_t *f, void *buf, uint16_t wr_idx, uint16_t rd_
bool tu_fifo_read(tu_fifo_t *f, void *buffer) {
// Peek the data
// f->rd_idx might get modified in case of an overflow so we can not use a local variable
- const bool ret = ff_peek_local(f, buffer, f->wr_idx, f->rd_idx);
+ const uint16_t wr_idx = f->wr_idx;
+ const bool ret = ff_peek_local(f, buffer, wr_idx, f->rd_idx);
if (ret) {
ff_lock(f->mutex_rd);
f->rd_idx = advance_index(f->depth, f->rd_idx, 1);
@@ -645,7 +647,9 @@ bool tu_fifo_read(tu_fifo_t *f, void *buffer) {
// Read one item without removing it from the FIFO, correct read index if overflowed
bool tu_fifo_peek(tu_fifo_t *f, void *p_buffer) {
- return ff_peek_local(f, p_buffer, f->wr_idx, f->rd_idx);
+ const uint16_t wr_idx = f->wr_idx;
+ const uint16_t rd_idx = f->rd_idx;
+ return ff_peek_local(f, p_buffer, wr_idx, rd_idx);
}
// Write one element into the buffer
diff --git a/src/common/tusb_fifo.h b/src/common/tusb_fifo.h
index 86ba59059..b31a0802e 100644
--- a/src/common/tusb_fifo.h
+++ b/src/common/tusb_fifo.h
@@ -120,9 +120,9 @@ typedef struct {
uint8_t *buffer; // buffer pointer
uint16_t depth; // max items
bool overwritable; // overwritable when full
- // 1 byte padding here
+ // 1 byte padding here
- volatile uint16_t wr_idx; // write index TODO maybe can drop volatile
+ volatile uint16_t wr_idx; // write index
volatile uint16_t rd_idx; // read index
#if OSAL_MUTEX_REQUIRED
@@ -291,16 +291,22 @@ TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_empty(const tu_fifo_t *f) {
// return number of items in fifo, capped to fifo's depth
TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_count(const tu_fifo_t *f) {
- return tu_min16(tu_ff_overflow_count(f->depth, f->wr_idx, f->rd_idx), f->depth);
+ const uint16_t wr_idx = f->wr_idx;
+ const uint16_t rd_idx = f->rd_idx;
+ return tu_min16(tu_ff_overflow_count(f->depth, wr_idx, rd_idx), f->depth);
}
// check if fifo is full
TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_full(const tu_fifo_t *f) {
- return tu_ff_overflow_count(f->depth, f->wr_idx, f->rd_idx) >= f->depth;
+ const uint16_t wr_idx = f->wr_idx;
+ const uint16_t rd_idx = f->rd_idx;
+ return tu_ff_overflow_count(f->depth, wr_idx, rd_idx) >= f->depth;
}
TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_remaining(const tu_fifo_t *f) {
- return tu_ff_remaining_local(f->depth, f->wr_idx, f->rd_idx);
+ const uint16_t wr_idx = f->wr_idx;
+ const uint16_t rd_idx = f->rd_idx;
+ return tu_ff_remaining_local(f->depth, wr_idx, rd_idx);
}
#ifdef __cplusplus
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index 5b9497b9a..edc7ecc3e 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -71,13 +71,24 @@
#define TUP_DCD_ENDPOINT_MAX 5
#elif TU_CHECK_MCU(OPT_MCU_LPC54)
+ #include "fsl_device_registers.h"
+
// TODO USB0 has 5, USB1 has 6
#define TUP_USBIP_IP3511
+
+ #if !defined(LPC54114_cm4_SERIES) && !defined(LPC54114_cm0plus_SERIES)
+ #define TUP_USBIP_IP3516
+ #define TUP_USBIP_OHCI
+ #define TUP_USBIP_OHCI_NXP
+ #define TUP_OHCI_RHPORTS 1 // 1 downstream port
+ #endif
+
#define TUP_DCD_ENDPOINT_MAX 6
#elif TU_CHECK_MCU(OPT_MCU_LPC55)
// TODO USB0 has 5, USB1 has 6
#define TUP_USBIP_IP3511
+ #define TUP_USBIP_IP3516
#define TUP_USBIP_OHCI
#define TUP_USBIP_OHCI_NXP
#define TUP_OHCI_RHPORTS 1 // 1 downstream port
@@ -172,6 +183,10 @@
#define TUP_RHPORT_HIGHSPEED 1
#define TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ // Enable dcache if DMA is enabled
+ #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_SAMX7X_DMA_ENABLE
+ #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32
+
#elif TU_CHECK_MCU(OPT_MCU_PIC32MZ)
#define TUP_DCD_ENDPOINT_MAX 8
#define TUD_ENDPOINT_ONE_DIRECTION_ONLY
diff --git a/src/common/tusb_private.h b/src/common/tusb_private.h
index 43ce7a1df..91d213755 100644
--- a/src/common/tusb_private.h
+++ b/src/common/tusb_private.h
@@ -24,8 +24,8 @@
* This file is part of the TinyUSB stack.
*/
-#ifndef TUSB_PRIVATE_H_
-#define TUSB_PRIVATE_H_
+#ifndef TUSB_PRIVATE_H
+#define TUSB_PRIVATE_H
// Internal Helper used by Host and Device Stack
@@ -33,9 +33,11 @@
extern "C" {
#endif
-//--------------------------------------------------------------------+
-// Configuration
-//--------------------------------------------------------------------+
+typedef void (*tusb_defer_func_t)(uintptr_t param);
+
+ //--------------------------------------------------------------------+
+ // Configuration
+ //--------------------------------------------------------------------+
#define TUP_USBIP_CONTROLLER_NUM 2
extern tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM];
@@ -60,9 +62,10 @@ typedef struct {
uint8_t hwid; // device: rhport, host: daddr
bool is_host; // 1: host, 0: device
uint8_t ep_addr;
- uint16_t mps;
+ // 1 byte padding
- uint16_t ep_bufsize;
+ uint16_t mps;
+ uint16_t xfer_len;
uint8_t *ep_buf; // set to NULL to use xfer_fifo when CFG_TUD_EDPT_DEDICATED_HWFIFO = 1
tu_fifo_t ff;
@@ -79,9 +82,8 @@ typedef struct {
bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed);
#else
TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) {
- (void)desc_ep;
(void)speed;
- return true;
+ return tu_edpt_packet_size(desc_ep) > 0;
}
#endif
@@ -101,7 +103,7 @@ bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex);
// Init an endpoint stream
bool tu_edpt_stream_init(tu_edpt_stream_t *s, bool is_host, bool is_tx, bool overwritable, void *ff_buf,
- uint16_t ff_bufsize, uint8_t *ep_buf, uint16_t ep_bufsize);
+ uint16_t ff_bufsize, uint8_t *ep_buf);
// Deinit an endpoint stream
TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_deinit(tu_edpt_stream_t *s) {
@@ -118,10 +120,11 @@ TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_deinit(tu_edpt_stream_t
// Open an endpoint stream
TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_open(tu_edpt_stream_t *s, uint8_t hwid,
- const tusb_desc_endpoint_t *desc_ep) {
+ const tusb_desc_endpoint_t *desc_ep, uint16_t xfer_len) {
s->hwid = hwid;
s->ep_addr = desc_ep->bEndpointAddress;
s->mps = tu_edpt_packet_size(desc_ep);
+ s->xfer_len = xfer_len;
}
TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_stream_is_opened(const tu_edpt_stream_t *s) {
diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h
index 7d26ac74e..a18f9feb7 100644
--- a/src/common/tusb_types.h
+++ b/src/common/tusb_types.h
@@ -115,6 +115,10 @@ enum {
TUSB_EPSIZE_ISO_HS_MAX = 1024,
};
+// Endpoint Bulk size depending on host/device max speed
+#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64)
+
/// Isochronous Endpoint Attributes
typedef enum {
TUSB_ISO_EP_ATT_NO_SYNC = 0x00,
@@ -341,12 +345,10 @@ typedef struct TU_ATTR_PACKED {
uint8_t bLength ; ///< Size of this descriptor in bytes.
uint8_t bDescriptorType ; ///< DEVICE Descriptor Type.
uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H).
-
uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF).
uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF).
uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF).
uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64.
-
uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF).
uint16_t idProduct ; ///< Product ID (assigned by the manufacturer).
uint16_t bcdDevice ; ///< Device release number in binary-coded decimal.
diff --git a/src/device/usbd.c b/src/device/usbd.c
index cca4169d7..42903576c 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -334,6 +334,19 @@ static const usbd_class_driver_t _usbd_driver[] = {
.sof = NULL
},
#endif
+
+ #if CFG_TUD_PRINTER
+ {
+ .name = DRIVER_NAME("PRINTER"),
+ .init = printerd_init,
+ .deinit = printerd_deinit,
+ .reset = printerd_reset,
+ .open = printerd_open,
+ .control_xfer_cb = printerd_control_xfer_cb,
+ .xfer_cb = printerd_xfer_cb,
+ .sof = NULL
+ },
+ #endif
};
enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) };
@@ -585,7 +598,7 @@ bool tud_deinit(uint8_t rhport) {
// Deinit device controller driver
dcd_int_disable(rhport);
dcd_disconnect(rhport);
- TU_VERIFY(dcd_deinit(rhport));
+ TU_ASSERT(dcd_deinit(rhport));
// Deinit class drivers
for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) {
@@ -823,7 +836,9 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const
#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL
if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) {
TU_LOG_USBD(" %s", tu_str_std_request[p_request->bRequest]);
- if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) TU_LOG_USBD("\r\n");
+ if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) {
+ TU_LOG_USBD("\r\n");
+ }
}
#endif
@@ -959,7 +974,25 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const
//------------- Class/Interface Specific Request -------------//
case TUSB_REQ_RCPT_INTERFACE: {
- uint8_t const itf = tu_u16_low(p_request->wIndex);
+ uint8_t itf;
+ #if CFG_TUD_PRINTER
+ // Printer GET_DEVICE_ID has a weird wIndex = interface (high) | alt (low)
+ // attempt to interpret this as a printer request if matched
+ if (TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type &&
+ TUSB_DIR_IN == p_request->bmRequestType_bit.direction &&
+ TUSB_PRINTER_REQUEST_GET_DEVICE_ID == p_request->bRequest) {
+ itf = tu_u16_high(p_request->wIndex);
+ if (itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)) {
+ const usbd_class_driver_t * driver = get_driver(_usbd_dev.itf2drv[itf]);
+ if (driver != NULL && driver->control_xfer_cb == printerd_control_xfer_cb) {
+ if (invoke_class_control(rhport, driver, p_request)) {
+ return true;
+ }
+ }
+ }
+ }
+ #endif
+ itf = tu_u16_low(p_request->wIndex);
TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv));
usbd_class_driver_t const * driver = get_driver(_usbd_dev.itf2drv[itf]);
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 825fdba90..d3a6dccbb 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -297,6 +297,19 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
/* Endpoint In */\
7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0
+//--------------------------------------------------------------------+
+// Printer Descriptor Templates
+//--------------------------------------------------------------------+
+
+#define TUD_PRINTER_DESC_LEN (9 + 7 + 7) // one interface, two endpoints
+
+#define TUD_PRINTER_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \
+ /* Interface */\
+ 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_PRINTER, 1, 2, _stridx,\
+ /* Endpoint Out */\
+ 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\
+ /* Endpoint In */\
+ 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0
//--------------------------------------------------------------------+
// MTP Descriptor Templates
diff --git a/src/device/usbd_control.c b/src/device/usbd_control.c
index 47d58a1f9..87593d4a7 100644
--- a/src/device/usbd_control.c
+++ b/src/device/usbd_control.c
@@ -63,6 +63,10 @@ CFG_TUD_MEM_SECTION static struct {
TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE);
} _ctrl_epbuf;
+uint8_t* usbd_get_ctrl_buf(void) {
+ return _ctrl_epbuf.buf;
+}
+
//--------------------------------------------------------------------+
// Application API
//--------------------------------------------------------------------+
@@ -93,7 +97,7 @@ static bool data_stage_xact(uint8_t rhport) {
if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN) {
ep_addr = EDPT_CTRL_IN;
- if (0u != xact_len) {
+ if (0u != xact_len && _ctrl_xfer.buffer != _ctrl_epbuf.buf) {
TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, _ctrl_xfer.buffer, xact_len));
}
}
@@ -169,7 +173,9 @@ bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result,
if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT) {
TU_VERIFY(_ctrl_xfer.buffer);
- memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes);
+ if (_ctrl_xfer.buffer != _ctrl_epbuf.buf) {
+ memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes);
+ }
TU_LOG_MEM(CFG_TUD_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2);
}
diff --git a/src/device/usbd_pvt.h b/src/device/usbd_pvt.h
index 72323ac80..5f11ea481 100644
--- a/src/device/usbd_pvt.h
+++ b/src/device/usbd_pvt.h
@@ -72,6 +72,8 @@ void usbd_int_set(bool enabled);
void usbd_spin_lock(bool in_isr);
void usbd_spin_unlock(bool in_isr);
+uint8_t* usbd_get_ctrl_buf(void);
+
//--------------------------------------------------------------------+
// USBD Endpoint API
// Note: rhport should be 0 since device stack only support 1 rhport for now
diff --git a/src/host/hcd.h b/src/host/hcd.h
index 36a7f5da5..47d672f9e 100644
--- a/src/host/hcd.h
+++ b/src/host/hcd.h
@@ -59,7 +59,7 @@ typedef enum {
HCD_EVENT_XFER_COMPLETE,
USBH_EVENT_FUNC_CALL, // Not an HCD event
- HCD_EVENT_COUNT
+ HCD_EVENT_INVALID
} hcd_eventid_t;
typedef struct {
@@ -72,7 +72,6 @@ typedef struct {
struct {
uint8_t hub_addr;
uint8_t hub_port;
- uint8_t speed;
} connection;
// XFER_COMPLETE
diff --git a/src/host/hub.c b/src/host/hub.c
index 3baaff30f..7c6735ed4 100644
--- a/src/host/hub.c
+++ b/src/host/hub.c
@@ -48,7 +48,7 @@ typedef struct {
uint8_t bNbrPorts;
uint8_t bPwrOn2PwrGood_2ms; // port power on to good, in 2ms unit
// uint16_t wHubCharacteristics;
-
+ bool mtt;
hub_port_status_response_t port_status;
} hub_interface_t;
@@ -218,23 +218,37 @@ bool hub_deinit(void) {
return true;
}
-uint16_t hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) {
- (void) rhport;
-
- TU_VERIFY(TUSB_CLASS_HUB == itf_desc->bInterfaceClass &&
- 0 == itf_desc->bInterfaceSubClass, 0);
- TU_VERIFY(itf_desc->bInterfaceProtocol <= 1, 0); // not support multiple TT yet
+uint16_t hub_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) {
+ (void)rhport;
+ TU_VERIFY(TUSB_CLASS_HUB == itf_desc->bInterfaceClass && 0 == itf_desc->bInterfaceSubClass, 0);
- const uint16_t drv_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t);
+ const uint16_t itf_ep_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t);
+ uint16_t drv_len = itf_ep_len;
TU_ASSERT(drv_len <= max_len, 0);
+ hub_interface_t *p_hub = get_hub_itf(dev_addr);
+ const tusb_desc_interface_t *desc_itf_use = itf_desc; // interface to use for endpoint open
+
+ // Check device descriptor for MTT hub (bDeviceProtocol == 2)
+ // MTT hub has 2 alt settings: alt 0 is STT (protocol 1), alt 1 is MTT (protocol 2)
+ // Consume both alt settings and use alt setting 1 for endpoint
+ tusb_desc_device_t desc_dev;
+ if (tuh_descriptor_get_device_local(dev_addr, &desc_dev) && desc_dev.bDeviceProtocol == HUB_PROTOCOL_HIGH_SPEED_MTT) {
+ drv_len += itf_ep_len;
+ TU_ASSERT(drv_len <= max_len, 0);
+ const tusb_desc_interface_t *desc_alt1 = (const tusb_desc_interface_t *)((const uint8_t *)itf_desc + itf_ep_len);
+ TU_ASSERT(desc_alt1->bDescriptorType == TUSB_DESC_INTERFACE && desc_alt1->bInterfaceClass == TUSB_CLASS_HUB &&
+ desc_alt1->bInterfaceProtocol == HUB_PROTOCOL_HIGH_SPEED_MTT,
+ 0);
+ p_hub->mtt = true;
+ desc_itf_use = desc_alt1;
+ }
+
// Interrupt Status endpoint
- tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc);
- TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType &&
- TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0);
+ const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(desc_itf_use);
+ TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0);
TU_ASSERT(tuh_edpt_open(dev_addr, desc_ep), 0);
- hub_interface_t* p_hub = get_hub_itf(dev_addr);
p_hub->itf_num = itf_desc->bInterfaceNumber;
p_hub->ep_in = desc_ep->bEndpointAddress;
@@ -269,12 +283,27 @@ bool hub_edpt_status_xfer(uint8_t daddr) {
//--------------------------------------------------------------------+
static void config_set_port_power (tuh_xfer_t* xfer);
static void config_port_power_complete (tuh_xfer_t* xfer);
+static void config_get_hub_descriptor(tuh_xfer_t* xfer);
bool hub_set_config(uint8_t daddr, uint8_t itf_num) {
hub_interface_t* p_hub = get_hub_itf(daddr);
TU_ASSERT(itf_num == p_hub->itf_num);
- hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr);
+ if (p_hub->mtt) {
+ // Set Alternate Setting 1 for MTT hub
+ TU_ASSERT(tuh_interface_set(daddr, itf_num, 1, config_get_hub_descriptor, 0));
+ } else {
+ tuh_xfer_t xfer;
+ xfer.daddr = daddr;
+ xfer.ep_addr = 0;
+ xfer.user_data = 0;
+ config_get_hub_descriptor(&xfer);
+ }
+
+ return true;
+}
+
+static void config_get_hub_descriptor(tuh_xfer_t* xfer) {
// Get Hub Descriptor
tusb_control_request_t const request = {
.bmRequestType_bit = {
@@ -288,17 +317,11 @@ bool hub_set_config(uint8_t daddr, uint8_t itf_num) {
.wLength = sizeof(hub_desc_cs_t)
};
- tuh_xfer_t xfer = {
- .daddr = daddr,
- .ep_addr = 0,
- .setup = &request,
- .buffer = p_epbuf->ctrl_buf,
- .complete_cb = config_set_port_power,
- .user_data = 0
- };
+ xfer->setup = &request;
+ xfer->buffer = get_hub_epbuf(xfer->daddr)->ctrl_buf;
+ xfer->complete_cb = config_set_port_power;
- TU_ASSERT(tuh_control_xfer(&xfer));
- return true;
+ TU_ASSERT(tuh_control_xfer(xfer), );
}
static void config_set_port_power (tuh_xfer_t* xfer) {
@@ -308,7 +331,7 @@ static void config_set_port_power (tuh_xfer_t* xfer) {
hub_interface_t* p_hub = get_hub_itf(daddr);
hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr);
- // only use number of ports in hub descriptor
+ // only use the number of ports in the hub descriptor
hub_desc_cs_t const* desc_hub = (hub_desc_cs_t const*) p_epbuf->ctrl_buf;
p_hub->bNbrPorts = desc_hub->bNbrPorts;
p_hub->bPwrOn2PwrGood_2ms = desc_hub->bPwrOn2PwrGood;
diff --git a/src/host/hub.h b/src/host/hub.h
index d9750f8a5..8a7feda70 100644
--- a/src/host/hub.h
+++ b/src/host/hub.h
@@ -92,6 +92,13 @@ enum {
HUB_CHARS_OVER_CURRENT_INDIVIDUAL = 1,
};
+// Hub Interface Protocol (USB 2.0 spec Table 11-16)
+typedef enum {
+ HUB_PROTOCOL_FULL_SPEED = 0, // Full speed hub
+ HUB_PROTOCOL_HIGH_SPEED_STT = 1, // Hi-speed hub with single TT
+ HUB_PROTOCOL_HIGH_SPEED_MTT = 2, // Hi-speed hub with multiple TTs
+} hub_protocol_t;
+
typedef struct TU_ATTR_PACKED{
uint8_t bLength ; ///< Size of descriptor
uint8_t bDescriptorType ; ///< Other_speed_Configuration Type
diff --git a/src/host/usbh.c b/src/host/usbh.c
index d702e9186..75df6bf60 100644
--- a/src/host/usbh.c
+++ b/src/host/usbh.c
@@ -104,10 +104,9 @@ TU_ATTR_WEAK void tuh_umount_cb(uint8_t daddr) {
//--------------------------------------------------------------------+
// Data Structure
//--------------------------------------------------------------------+
-typedef struct {
- tuh_bus_info_t bus_info;
- // Device Descriptor
+// Device Descriptor (without bLength and bDescriptorType header)
+typedef struct TU_ATTR_PACKED {
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
@@ -120,6 +119,13 @@ typedef struct {
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
+} desc_device_noheader_t;
+
+TU_VERIFY_STATIC( sizeof(desc_device_noheader_t) == 16u, "size is not correct");
+
+typedef struct {
+ tuh_bus_info_t bus_info;
+ desc_device_noheader_t desc_device;
// Device State
struct TU_ATTR_PACKED {
@@ -169,6 +175,12 @@ static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set);
OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t);
static osal_queue_t _usbh_q;
+ #if CFG_TUH_HUB
+// Deferred attachment queue, only needed when using hub
+OSAL_QUEUE_DEF(usbh_int_set, _usbh_daqdef, CFG_TUH_HUB, hcd_event_t);
+static osal_queue_t _usbh_daq;
+ #endif
+
// Control transfers: since most controllers do not support multiple control transfers
// on multiple devices concurrently and control transfers are not used much except for
// enumeration, we will only execute control transfers one at a time.
@@ -184,11 +196,18 @@ typedef struct {
} usbh_ctrl_xfer_info_t;
typedef struct {
+ tusb_defer_func_t func;
+ uintptr_t arg;
+ uint32_t at_ms;
+} usbh_call_after_t;
+
+typedef struct {
uint8_t controller_id; // controller ID
uint8_t enumerating_daddr; // device address of the device being enumerated
uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing
tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration
usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer
+ usbh_call_after_t call_after;
} usbh_data_t;
static usbh_data_t _usbh_data = {
@@ -311,7 +330,8 @@ TU_ATTR_ALWAYS_INLINE static inline usbh_class_driver_t const *get_driver(uint8_
//--------------------------------------------------------------------+
// Function Inline and Prototypes
//--------------------------------------------------------------------+
-static bool enum_new_device(hcd_event_t* event);
+static void enum_new_device(hcd_event_t* event);
+static void enum_delay_async(uintptr_t state);
static void process_remove_event(hcd_event_t *event);
static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port);
static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size);
@@ -349,6 +369,15 @@ TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const ui
return ret;
}
+bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param) {
+ TU_ASSERT(_usbh_data.call_after.func == NULL);
+ TU_LOG_USBH("USBH schedule function after %u ms\r\n", (unsigned int)ms);
+ _usbh_data.call_after.func = func;
+ _usbh_data.call_after.arg = param;
+ _usbh_data.call_after.at_ms = tusb_time_millis_api() + ms;
+ return true;
+}
+
TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) {
hcd_device_close(rhport, daddr);
@@ -360,6 +389,10 @@ TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8
// invalidate if enumerating
if (daddr == _usbh_data.enumerating_daddr) {
_usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8;
+ // clear enum delay function of the device being removed
+ if (_usbh_data.call_after.func == enum_delay_async) {
+ _usbh_data.call_after.func = NULL;
+ }
}
}
@@ -386,10 +419,10 @@ bool tuh_vid_pid_get(uint8_t dev_addr, uint16_t *vid, uint16_t *pid) {
*vid = *pid = 0;
usbh_device_t const *dev = get_device(dev_addr);
- TU_VERIFY(dev && dev->addressed && dev->idVendor != 0);
+ TU_VERIFY(dev && dev->addressed && dev->desc_device.idVendor != 0);
- *vid = dev->idVendor;
- *pid = dev->idProduct;
+ *vid = dev->desc_device.idVendor;
+ *pid = dev->desc_device.idProduct;
return true;
}
@@ -400,18 +433,7 @@ bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_dev
desc_device->bLength = sizeof(tusb_desc_device_t);
desc_device->bDescriptorType = TUSB_DESC_DEVICE;
- desc_device->bcdUSB = dev->bcdUSB;
- desc_device->bDeviceClass = dev->bDeviceClass;
- desc_device->bDeviceSubClass = dev->bDeviceSubClass;
- desc_device->bDeviceProtocol = dev->bDeviceProtocol;
- desc_device->bMaxPacketSize0 = dev->bMaxPacketSize0;
- desc_device->idVendor = dev->idVendor;
- desc_device->idProduct = dev->idProduct;
- desc_device->bcdDevice = dev->bcdDevice;
- desc_device->iManufacturer = dev->iManufacturer;
- desc_device->iProduct = dev->iProduct;
- desc_device->iSerialNumber = dev->iSerialNumber;
- desc_device->bNumConfigurations = dev->bNumConfigurations;
+ memcpy((uint8_t*) desc_device + offsetof(tusb_desc_device_t, bcdUSB), &dev->desc_device, sizeof(desc_device_noheader_t));
return true;
}
@@ -493,11 +515,17 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
_usbh_q = osal_queue_create(&_usbh_qdef);
TU_ASSERT(_usbh_q != NULL);
-#if OSAL_MUTEX_REQUIRED
+ #if CFG_TUH_HUB
+ // Deferred attachment queue
+ _usbh_daq = osal_queue_create(&_usbh_daqdef);
+ TU_ASSERT(_usbh_daq != NULL);
+ #endif
+
+ #if OSAL_MUTEX_REQUIRED
// Init mutex
_usbh_mutex = osal_mutex_create(&_usbh_mutexdef);
TU_ASSERT(_usbh_mutex);
-#endif
+ #endif
// Get application driver if available
_app_driver = usbh_app_driver_get_cb(&_app_driver_count);
@@ -538,7 +566,7 @@ bool tuh_deinit(uint8_t rhport) {
// deinit host controller
hcd_int_disable(rhport);
- hcd_deinit(rhport);
+ TU_ASSERT(hcd_deinit(rhport));
_usbh_data.controller_id = TUSB_INDEX_INVALID_8;
// remove all devices on this rhport (hub_addr = 0, hub_port = 0)
@@ -558,11 +586,16 @@ bool tuh_deinit(uint8_t rhport) {
osal_queue_delete(_usbh_q);
_usbh_q = NULL;
- #if OSAL_MUTEX_REQUIRED
+ #if CFG_TUH_HUB
+ osal_queue_delete(_usbh_daq);
+ _usbh_daq = NULL;
+ #endif
+
+ #if OSAL_MUTEX_REQUIRED
// TODO make sure there is no task waiting on this mutex
osal_mutex_delete(_usbh_mutex);
_usbh_mutex = NULL;
- #endif
+ #endif
}
return true;
@@ -570,9 +603,19 @@ bool tuh_deinit(uint8_t rhport) {
bool tuh_task_event_ready(void) {
if (!tuh_inited()) {
- return false; // Skip if stack is not initialized
+ return false; // Skip if tusb stack is not initialized
+ }
+ if (!osal_queue_empty(_usbh_q)) {
+ return true;
}
- return !osal_queue_empty(_usbh_q);
+
+ #if CFG_TUH_HUB
+ if (!osal_queue_empty(_usbh_daq)) {
+ return true;
+ }
+ #endif
+
+ return false;
}
/* USB Host Driver task
@@ -592,51 +635,87 @@ bool tuh_task_event_ready(void) {
@endcode
*/
void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
- (void) in_isr; // not implemented yet
-
// Skip if stack is not initialized
if (!tuh_inited()) {
return;
}
+ (void) in_isr; // not implemented yet
+
// Loop until there are no more events in the queue or CFG_TUH_TASK_EVENTS_PER_RUN is reached
for (unsigned epr = 0;; epr++) {
-#if CFG_TUH_TASK_EVENTS_PER_RUN > 0
+ #if CFG_TUH_TASK_EVENTS_PER_RUN > 0
if (epr >= CFG_TUH_TASK_EVENTS_PER_RUN) {
TU_LOG_USBH("USBH event limit (" TU_XSTRING(CFG_TUH_TASK_EVENTS_PER_RUN) ") reached\r\n");
break;
}
-#endif
+ #endif
+
+ // Process call_after_ms function if ms is reached
+ tusb_defer_func_t after_cb = _usbh_data.call_after.func;
+ if (after_cb) {
+ int32_t remain_ms = (int32_t)(_usbh_data.call_after.at_ms - tusb_time_millis_api());
+ if (remain_ms <= 0) {
+ // delay expired, run callback now
+ TU_LOG_USBH("USBH invoke scheduled function\r\n");
+ _usbh_data.call_after.func = NULL;
+ after_cb(_usbh_data.call_after.arg);
+ }
+
+ // above after_cb() can re-schedule another function, we need to re-check and reduce timeout of
+ // the main event timeout to make sure we aren't blocking more than call_after remaining ms.
+ if (_usbh_data.call_after.func != NULL) {
+ remain_ms = (int32_t) (_usbh_data.call_after.at_ms - tusb_time_millis_api());
+ if (remain_ms <= 0) {
+ timeout_ms = 0; // expired already
+ } else if (timeout_ms > (uint32_t)remain_ms) {
+ timeout_ms = (uint32_t)remain_ms;
+ }
+ }
+ }
+
hcd_event_t event;
- if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { return; }
+
+ #if CFG_TUH_HUB
+ // Get deferred device attachments if none is enumerating
+ bool has_deferred_attach = false;
+ if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) {
+ // zero wait to avoid blocking the main event queue
+ has_deferred_attach = osal_queue_receive(_usbh_daq, &event, 0);
+ }
+
+ if (!has_deferred_attach) // skip event queue to process deferred attach
+ #endif
+ {
+ if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) {
+ return;
+ }
+ }
switch (event.event_id) {
case HCD_EVENT_DEVICE_ATTACH:
// Should we miss the hub detach event due to high traffic, Or due to physical debouncing, some devices can
- // cause multiple attaches (actually reset) without detach event.
+ // cause multiple attaches (actually reset) without a detached event.
// Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists
process_remove_event(&event);
// due to the shared control buffer, we must fully complete enumerating one device first.
- // TODO better to have an separated queue for newly attached devices
if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) {
// New device attached and we are ready
TU_LOG_USBH("[%u:] USBH Device Attach\r\n", event.rhport);
_usbh_data.enumerating_daddr = 0; // enumerate new device with address 0
enum_new_device(&event);
- } else {
- // currently enumerating another device
+ }
+ #if CFG_TUH_HUB
+ else {
TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport);
- const bool is_empty = osal_queue_empty(_usbh_q);
- queue_event(&event, in_isr);
- if (is_empty) {
- return; // Exit if this is the only event in the queue, otherwise we loop forever
- }
+ TU_ASSERT(osal_queue_send(_usbh_daq, &event, in_isr), );
}
+ #endif
break;
case HCD_EVENT_DEVICE_REMOVE:
- TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port);
+ TU_LOG_USBH("[%u:%u:%u] USBH Device Removed\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port);
process_remove_event(&event);
break;
@@ -709,10 +788,8 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) {
break;
}
-#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO
- // return if there is no more events, for application to run other background
- if (osal_queue_empty(_usbh_q)) return;
-#endif
+ // allow to exit tuh_task() if there is no event in the next run
+ timeout_ms = 0;
}
}
@@ -770,10 +847,8 @@ bool tuh_control_xfer (tuh_xfer_t* xfer) {
while (result == XFER_RESULT_INVALID) {
// Note: this can be called within an callback ie. part of tuh_task()
- // therefore event with RTOS tuh_task() still need to be invoked
- if (tuh_task_event_ready()) {
- tuh_task();
- }
+ // therefore even with RTOS tuh_task_ext() still need to be invoked
+ tuh_task_ext(0, false);
// TODO probably some timeout to prevent hanged
}
@@ -1070,7 +1145,7 @@ static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) {
}
bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const* desc_ep) {
- // HACK: some device incorrectly always report 512 bulk regardless of link speed, overwrite descriptor to force 64
+ // HACK: some device incorrectly always reports 512 bulk regardless of link speed, overwrite descriptor to force 64
if (desc_ep->bmAttributes.xfer == TUSB_XFER_BULK && tu_edpt_packet_size(desc_ep) > 64 &&
tuh_speed_get(dev_addr) == TUSB_SPEED_FULL) {
TU_LOG1(" WARN: EP max packet size is 512 in fullspeed, force to 64\r\n");
@@ -1195,24 +1270,24 @@ bool tuh_descriptor_get_manufacturer_string(uint8_t daddr, uint16_t language_id,
tuh_xfer_cb_t complete_cb, uintptr_t user_data)
{
usbh_device_t const* dev = get_device(daddr);
- TU_VERIFY(dev && dev->iManufacturer);
- return tuh_descriptor_get_string(daddr, dev->iManufacturer, language_id, buffer, len, complete_cb, user_data);
+ TU_VERIFY(dev && dev->desc_device.iManufacturer);
+ return tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, language_id, buffer, len, complete_cb, user_data);
}
// Get product string descriptor
bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len,
tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
usbh_device_t const* dev = get_device(daddr);
- TU_VERIFY(dev && dev->iProduct);
- return tuh_descriptor_get_string(daddr, dev->iProduct, language_id, buffer, len, complete_cb, user_data);
+ TU_VERIFY(dev && dev->desc_device.iProduct);
+ return tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, language_id, buffer, len, complete_cb, user_data);
}
// Get serial string descriptor
bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len,
tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
usbh_device_t const* dev = get_device(daddr);
- TU_VERIFY(dev && dev->iSerialNumber);
- return tuh_descriptor_get_string(daddr, dev->iSerialNumber, language_id, buffer, len, complete_cb, user_data);
+ TU_VERIFY(dev && dev->desc_device.iSerialNumber);
+ return tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, language_id, buffer, len, complete_cb, user_data);
}
// Get HID report descriptor
@@ -1423,10 +1498,10 @@ enum { // USB 2.0 specs 7.1.7 for timing
enum {
ENUM_IDLE,
ENUM_HUB_RERSET,
- ENUM_HUB_GET_STATUS_AFTER_RESET,
+ ENUM_HUB_RESET_COMPLETE,
ENUM_HUB_CLEAR_RESET,
+ ENUM_HUB_CLEAR_RESET_RETRY, // 2nd attempt waiting for hub reset
ENUM_HUB_CLEAR_RESET_COMPLETE,
-
ENUM_ADDR0_DEVICE_DESC,
ENUM_SET_ADDR,
ENUM_GET_DEVICE_DESC,
@@ -1445,138 +1520,175 @@ enum {
};
static uint8_t enum_get_new_address(bool is_hub);
-static bool enum_parse_configuration_desc (uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg);
-static void enum_full_complete(bool success);
-static void process_enumeration(tuh_xfer_t* xfer);
+static bool enum_parse_configuration_desc(uint8_t dev_addr, const tusb_desc_configuration_t *desc_cfg);
+static void enum_full_complete(bool success);
+static void process_enumeration(tuh_xfer_t *xfer);
-// start a new enumeration process
-static bool enum_new_device(hcd_event_t* event) {
- tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus;
- dev0_bus->rhport = event->rhport;
- dev0_bus->hub_addr = event->connection.hub_addr;
- dev0_bus->hub_port = event->connection.hub_port;
+enum {
+ ENUM_AFTER_DEBOUNCING_DELAY,
+ ENUM_AFTER_RESET_ROOT_DELAY,
+ ENUM_AFTER_RESET_ROOT_POST_DELAY,
+ ENUM_AFTER_RESET_HUB_DELAY,
+ ENUM_AFTER_RESET_HUB_DELAY_RETRY,
+ ENUM_AFTER_RESET_RECOVERY_DELAY,
+ ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY,
+};
- // wait until device connection is stable TODO non blocking
- tusb_time_delay_ms_api(ENUM_DEBOUNCING_DELAY_MS);
+// process async delay in enumeration
+static void enum_delay_async(uintptr_t state) {
+ tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus;
+ switch (state) {
+ case ENUM_AFTER_DEBOUNCING_DELAY:
+ #if CFG_TUH_HUB
+ if (dev0_bus->hub_addr != 0) {
+ // connected via hub
+ TU_VERIFY(dev0_bus->hub_port != 0, );
+ TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration,
+ ENUM_HUB_RERSET), );
+ } else
+ #endif
+ {
+ // connected directly to roothub
+ _usbh_data.attach_debouncing_bm &= (uint8_t)~TU_BIT(dev0_bus->rhport); // clear roothub debouncing delay
+ if (!hcd_port_connect_status(dev0_bus->rhport)) {
+ TU_LOG_USBH("Device unplugged while debouncing\r\n");
+ enum_full_complete(false);
+ return;
+ }
+ hcd_port_reset(dev0_bus->rhport); // reset port
+ usbh_defer_func_ms_async(ENUM_RESET_ROOT_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_DELAY);
+ }
+ break;
- if (dev0_bus->hub_addr == 0) {
- // connected directly to roothub
- // USB bus not active and frame number is not available yet.
- // need to depend on tusb_time_millis_api() TODO non blocking
+ case ENUM_AFTER_RESET_ROOT_DELAY:
+ hcd_port_reset_end(dev0_bus->rhport);
+ usbh_defer_func_ms_async(ENUM_RESET_ROOT_POST_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_POST_DELAY);
+ break;
- _usbh_data.attach_debouncing_bm &= (uint8_t) ~TU_BIT(dev0_bus->rhport); // clear roothub debouncing delay
+ case ENUM_AFTER_RESET_ROOT_POST_DELAY:
+ if (!hcd_port_connect_status(dev0_bus->rhport)) {
+ // device unplugged while delaying
+ enum_full_complete(false);
+ return;
+ }
- if (!hcd_port_connect_status(dev0_bus->rhport)) {
- TU_LOG_USBH("Device unplugged while debouncing\r\n");
- enum_full_complete(false);
- return true;
- }
+ dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport);
+ TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]);
- // reset device
- hcd_port_reset(dev0_bus->rhport);
- tusb_time_delay_ms_api(ENUM_RESET_ROOT_DELAY_MS);
- hcd_port_reset_end(dev0_bus->rhport);
- tusb_time_delay_ms_api(ENUM_RESET_ROOT_POST_DELAY_MS);
+ // fake transfer to kick-off the enumeration process
+ tuh_xfer_t xfer;
+ xfer.daddr = 0;
+ xfer.result = XFER_RESULT_SUCCESS;
+ xfer.user_data = ENUM_ADDR0_DEVICE_DESC;
+ process_enumeration(&xfer);
+ break;
- if (!hcd_port_connect_status(dev0_bus->rhport)) {
- // device unplugged while delaying
- enum_full_complete(false);
- return true;
- }
+ #if CFG_TUH_HUB
+ case ENUM_AFTER_RESET_HUB_DELAY:
+ case ENUM_AFTER_RESET_HUB_DELAY_RETRY:
+ // get status after reset complete to check for reset change
+ TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration,
+ state == ENUM_AFTER_RESET_HUB_DELAY ? ENUM_HUB_CLEAR_RESET
+ : ENUM_HUB_CLEAR_RESET_RETRY), );
+ break;
+ #endif
- dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport);
- TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]);
+ case ENUM_AFTER_RESET_RECOVERY_DELAY:
+ // TODO probably doesn't need to open/close each enumeration
+ if (!usbh_edpt_control_open(0, 8)) {
+ TU_LOG_USBH("Failed to open dev0's control endpoint\r\n");
+ enum_full_complete(false); // Stop enumeration gracefully
+ return;
+ }
+ // Get first 8 bytes of device descriptor for control endpoint size
+ TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n");
+ TU_ASSERT(tuh_descriptor_get_device(0, _usbh_epbuf.ctrl, 8, process_enumeration, ENUM_SET_ADDR), );
+ break;
- // fake transfer to kick-off the enumeration process
- tuh_xfer_t xfer;
- xfer.daddr = 0;
- xfer.result = XFER_RESULT_SUCCESS;
- xfer.user_data = ENUM_ADDR0_DEVICE_DESC;
- process_enumeration(&xfer);
- }
- #if CFG_TUH_HUB
- else {
- // connected via hub
- TU_VERIFY(dev0_bus->hub_port != 0);
- TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL,
- process_enumeration, ENUM_HUB_RERSET));
+ case ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY: {
+ const uint8_t new_addr = _usbh_data.enumerating_daddr;
+ usbh_device_t *new_dev = get_device(new_addr);
+ TU_ASSERT(new_dev, );
+ if (!usbh_edpt_control_open(new_addr, new_dev->desc_device.bMaxPacketSize0)) {
+ TU_LOG_USBH("Failed to open new device's control endpoint\r\n");
+ clear_device(new_dev);
+ enum_full_complete(false);
+ return;
+ }
+ TU_LOG_USBH("Get Device Descriptor\r\n");
+ TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), process_enumeration,
+ ENUM_GET_STRING_LANGUAGE_ID_LEN), );
+ break;
+ }
+
+ default:
+ break;
}
- #endif // hub
+}
- return true;
+// start a new enumeration process
+static void enum_new_device(hcd_event_t *event) {
+ tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus;
+ dev0_bus->rhport = event->rhport;
+ dev0_bus->hub_addr = event->connection.hub_addr;
+ dev0_bus->hub_port = event->connection.hub_port;
+ usbh_defer_func_ms_async(ENUM_DEBOUNCING_DELAY_MS, enum_delay_async, ENUM_AFTER_DEBOUNCING_DELAY);
}
// process device enumeration
-static void process_enumeration(tuh_xfer_t* xfer) {
- // Retry a few times while enumerating since device can be unstable when starting up
- static uint8_t failed_count = 0;
+static void process_enumeration(tuh_xfer_t *xfer) {
if (XFER_RESULT_FAILED == xfer->result) {
- enum {
- ATTEMPT_COUNT_MAX = 3,
- ATTEMPT_DELAY_MS = 100
- };
-
- // retry if not reaching max attempt
- failed_count++;
- bool retry = (_usbh_data.enumerating_daddr != TUSB_INDEX_INVALID_8) && (failed_count < ATTEMPT_COUNT_MAX);
- if (retry) {
- tusb_time_delay_ms_api(ATTEMPT_DELAY_MS); // delay a bit
- TU_LOG_USBH("Enumeration attempt %u/%u\r\n", failed_count+1, ATTEMPT_COUNT_MAX);
- retry = tuh_control_xfer(xfer);
- }
-
- if (!retry) {
- enum_full_complete(false); // complete as failed
- }
+ enum_full_complete(false); // failed to enum
return;
}
- failed_count = 0;
- uint8_t const daddr = xfer->daddr;
- uintptr_t const state = xfer->user_data;
- usbh_device_t* dev = get_device(daddr);
- tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus;
+ const uint8_t daddr = xfer->daddr;
+ const uintptr_t state = xfer->user_data;
+ usbh_device_t *dev = get_device(daddr);
+ tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus;
if (daddr > 0) {
TU_ASSERT(dev != NULL,);
}
uint16_t langid = 0x0409; // default is English
+ bool is_enum_failed = false;
switch (state) {
- #if CFG_TUH_HUB
+ #if CFG_TUH_HUB
case ENUM_HUB_RERSET: {
hub_port_status_response_t port_status;
hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status);
if (0 == port_status.status.connection) {
TU_LOG_USBH("Device unplugged from hub while debouncing\r\n");
- enum_full_complete(false);
- return;
+ is_enum_failed = true;
+ } else {
+ TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
+ ENUM_HUB_RESET_COMPLETE), );
}
-
- TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_GET_STATUS_AFTER_RESET),);
break;
}
- case ENUM_HUB_GET_STATUS_AFTER_RESET: {
- tusb_time_delay_ms_api(ENUM_RESET_HUB_DELAY_MS); // wait for reset to take effect
-
- // get status to check for reset change
- TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET),);
+ case ENUM_HUB_RESET_COMPLETE:
+ // wait for reset to take effect
+ usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY);
break;
- }
- case ENUM_HUB_CLEAR_RESET: {
+ case ENUM_HUB_CLEAR_RESET:
+ case ENUM_HUB_CLEAR_RESET_RETRY: {
hub_port_status_response_t port_status;
hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status);
if (1 == port_status.change.reset) {
// Acknowledge Port Reset Change
- TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),);
+ TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration,
+ ENUM_HUB_CLEAR_RESET_COMPLETE), );
+ } else if (state == ENUM_HUB_CLEAR_RESET) {
+ // retry one more time if reset change not set yet
+ usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY_RETRY);
} else {
- // maybe retry if reset change not set but we need timeout to prevent infinite loop
- // TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),);
+ // retry but still not set --> failed
+ is_enum_failed = true;
}
-
break;
}
@@ -1586,70 +1698,50 @@ static void process_enumeration(tuh_xfer_t* xfer) {
if (0 == port_status.status.connection) {
TU_LOG_USBH("Device unplugged from hub (not addressed yet)\r\n");
- enum_full_complete(false);
- return;
+ is_enum_failed = true;
+ break;
}
- dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH :
- (port_status.status.low_speed) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL;
-
+ dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH
+ : (port_status.status.low_speed) ? TUSB_SPEED_LOW
+ : TUSB_SPEED_FULL;
TU_ATTR_FALLTHROUGH;
}
- #endif
-
- case ENUM_ADDR0_DEVICE_DESC: {
- tusb_time_delay_ms_api(ENUM_RESET_RECOVERY_DELAY_MS); // reset recovery
-
- // TODO probably doesn't need to open/close each enumeration
- uint8_t const addr0 = 0;
- if (!usbh_edpt_control_open(addr0, 8)) {
- // Stop enumeration gracefully
- enum_full_complete(false);
- TU_ASSERT(false,);
- }
+ #endif
- // Get first 8 bytes of device descriptor for control endpoint size
- TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n");
- TU_ASSERT(tuh_descriptor_get_device(addr0, _usbh_epbuf.ctrl, 8,
- process_enumeration, ENUM_SET_ADDR),);
+ case ENUM_ADDR0_DEVICE_DESC:
+ usbh_defer_func_ms_async(ENUM_RESET_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_RECOVERY_DELAY);
break;
- }
case ENUM_SET_ADDR: {
const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl;
+ if (!(desc_device->bDescriptorType == TUSB_DESC_DEVICE && desc_device->bMaxPacketSize0 >= 8)) {
+ TU_LOG_USBH("Invalid Device descriptor\r\n");
+ is_enum_failed = true;
+ break;
+ }
+
const uint8_t new_addr = enum_get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB);
TU_ASSERT(new_addr != 0,);
usbh_device_t* new_dev = get_device(new_addr);
new_dev->bus_info = *dev0_bus;
new_dev->connected = 1;
- new_dev->bMaxPacketSize0 = desc_device->bMaxPacketSize0;
+ new_dev->desc_device.bMaxPacketSize0 = desc_device->bMaxPacketSize0;
- TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC),);
+ TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC), );
break;
}
case ENUM_GET_DEVICE_DESC: {
- tusb_time_delay_ms_api(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS); // set address recovery
-
- const uint8_t new_addr = (uint8_t) tu_le16toh(xfer->setup->wValue);
- usbh_device_t* new_dev = get_device(new_addr);
- TU_ASSERT(new_dev,);
- new_dev->addressed = 1;
+ const uint8_t new_addr = (uint8_t)tu_le16toh(xfer->setup->wValue);
+ usbh_device_t *new_dev = get_device(new_addr);
+ TU_ASSERT(new_dev, );
+ new_dev->addressed = 1;
_usbh_data.enumerating_daddr = new_addr;
usbh_device_close(dev0_bus->rhport, 0); // close dev0
-
- if (!usbh_edpt_control_open(new_addr, new_dev->bMaxPacketSize0)) { // open new control endpoint
- // Stop enumeration gracefully
- clear_device(new_dev);
- enum_full_complete(false);
- TU_ASSERT(false,);
- }
-
- TU_LOG_USBH("Get Device Descriptor\r\n");
- TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t),
- process_enumeration, ENUM_GET_STRING_LANGUAGE_ID_LEN),);
+ usbh_defer_func_ms_async(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY);
break;
}
@@ -1659,18 +1751,7 @@ static void process_enumeration(tuh_xfer_t* xfer) {
// save the received device descriptor
tusb_desc_device_t const *desc_device = (tusb_desc_device_t const *) _usbh_epbuf.ctrl;
- dev->bcdUSB = desc_device->bcdUSB;
- dev->bDeviceClass = desc_device->bDeviceClass;
- dev->bDeviceSubClass = desc_device->bDeviceSubClass;
- dev->bDeviceProtocol = desc_device->bDeviceProtocol;
- dev->bMaxPacketSize0 = desc_device->bMaxPacketSize0;
- dev->idVendor = desc_device->idVendor;
- dev->idProduct = desc_device->idProduct;
- dev->bcdDevice = desc_device->bcdDevice;
- dev->iManufacturer = desc_device->iManufacturer;
- dev->iProduct = desc_device->iProduct;
- dev->iSerialNumber = desc_device->iSerialNumber;
- dev->bNumConfigurations = desc_device->bNumConfigurations;
+ memcpy(&dev->desc_device, (const uint8_t*) desc_device + offsetof(tusb_desc_device_t, bcdUSB), sizeof(desc_device_noheader_t));
tuh_enum_descriptor_device_cb(daddr, desc_device); // callback
tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2,
@@ -1690,8 +1771,8 @@ static void process_enumeration(tuh_xfer_t* xfer) {
if (desc_langid->bLength >= 4) {
langid = tu_le16toh(desc_langid->utf16le[0]); // previous request is langid
}
- if (dev->iManufacturer != 0) {
- tuh_descriptor_get_string(daddr, dev->iManufacturer, langid, _usbh_epbuf.ctrl, 2,
+ if (dev->desc_device.iManufacturer != 0) {
+ tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, 2,
process_enumeration, ENUM_GET_STRING_MANUFACTURER);
break;
}
@@ -1699,10 +1780,10 @@ static void process_enumeration(tuh_xfer_t* xfer) {
}
case ENUM_GET_STRING_MANUFACTURER: {
- if (dev->iManufacturer != 0) {
+ if (dev->desc_device.iManufacturer != 0) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
- tuh_descriptor_get_string(daddr, dev->iManufacturer, langid, _usbh_epbuf.ctrl, str_len,
+ tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, str_len,
process_enumeration, ENUM_GET_STRING_PRODUCT_LEN);
break;
}
@@ -1710,22 +1791,22 @@ static void process_enumeration(tuh_xfer_t* xfer) {
}
case ENUM_GET_STRING_PRODUCT_LEN: {
- if (dev->iProduct != 0) {
+ if (dev->desc_device.iProduct != 0) {
if (state == ENUM_GET_STRING_PRODUCT_LEN) {
langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through
}
tuh_descriptor_get_string(
- daddr, dev->iProduct, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_PRODUCT);
+ daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_PRODUCT);
break;
}
TU_ATTR_FALLTHROUGH;
}
case ENUM_GET_STRING_PRODUCT: {
- if (dev->iProduct != 0) {
+ if (dev->desc_device.iProduct != 0) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
- tuh_descriptor_get_string(daddr, dev->iProduct, langid, _usbh_epbuf.ctrl, str_len,
+ tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, str_len,
process_enumeration, ENUM_GET_STRING_SERIAL_LEN);
break;
}
@@ -1733,22 +1814,22 @@ static void process_enumeration(tuh_xfer_t* xfer) {
}
case ENUM_GET_STRING_SERIAL_LEN: {
- if (dev->iSerialNumber != 0) {
+ if (dev->desc_device.iSerialNumber != 0) {
if (state == ENUM_GET_STRING_SERIAL_LEN) {
langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through
}
tuh_descriptor_get_string(
- daddr, dev->iSerialNumber, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_SERIAL);
+ daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_SERIAL);
break;
}
TU_ATTR_FALLTHROUGH;
}
case ENUM_GET_STRING_SERIAL: {
- if (dev->iSerialNumber != 0) {
+ if (dev->desc_device.iSerialNumber != 0) {
langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request
const uint8_t str_len = xfer->buffer[0];
- tuh_descriptor_get_string(daddr, dev->iSerialNumber, langid, _usbh_epbuf.ctrl, str_len,
+ tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, str_len,
process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC);
break;
}
@@ -1787,7 +1868,7 @@ static void process_enumeration(tuh_xfer_t* xfer) {
TU_ASSERT(tuh_configuration_set(daddr, config_idx+1u, process_enumeration, ENUM_CONFIG_DRIVER),);
} else {
config_idx++;
- TU_ASSERT(config_idx < dev->bNumConfigurations,);
+ TU_ASSERT(config_idx < dev->desc_device.bNumConfigurations,);
TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx);
TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9,
process_enumeration, ENUM_GET_FULL_CONFIG_DESC),);
@@ -1799,11 +1880,12 @@ static void process_enumeration(tuh_xfer_t* xfer) {
TU_LOG_USBH("Device configured\r\n");
dev->configured = 1;
- #if CFG_TUH_HUB
+ #if CFG_TUH_HUB
+ // get next hub status now since device can be unplugged before set_configure() is complete
if (_usbh_data.dev0_bus.hub_addr != 0) {
- hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status
+ hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr);
}
- #endif
+ #endif
// Parse configuration & set up drivers
// driver_open() must not make any usb transfer
@@ -1818,9 +1900,13 @@ static void process_enumeration(tuh_xfer_t* xfer) {
}
default:
- enum_full_complete(false); // stop enumeration if unknown state
+ is_enum_failed = true;
break;
}
+
+ if (is_enum_failed) {
+ enum_full_complete(false);
+ }
}
static uint8_t enum_get_new_address(bool is_hub) {
@@ -1858,10 +1944,10 @@ static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configurat
TU_LOG_USBH("Parsing Configuration descriptor (wTotalLength = %u)\r\n", total_len);
- // parse each interfaces
+ // parse all interfaces
while (tu_desc_in_bounds(p_desc, desc_end)) {
if (0 == tu_desc_len(p_desc)) {
- // A zero length descriptor indicates that the device is off spec (e.g. wrong wTotalLength).
+ // A zero-length descriptor indicates that the device is off spec (e.g. wrong wTotalLength).
// Parsed interfaces should still be usable
TU_LOG_USBH("Encountered a zero-length descriptor after %" PRIu32 " bytes\r\n", (uint32_t)p_desc - (uint32_t)desc_cfg);
break;
@@ -1877,7 +1963,7 @@ static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configurat
// uint16_t const drv_len = tu_desc_get_interface_total_len(desc_itf, assoc_itf_count, (uint16_t)
// (desc_end-p_desc)); TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t));
- // Find driver for this interface
+ // Find a driver for this interface
const uint16_t remaining_len = (uint16_t)(desc_end - p_desc);
uint8_t drv_id;
for (drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) {
@@ -1925,7 +2011,7 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) {
}
}
- // all interface are configured
+ // all interfaces are configured
if (itf_num == CFG_TUH_INTERFACE_MAX) {
enum_full_complete(true);
@@ -1940,16 +2026,17 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) {
static void enum_full_complete(bool success) {
(void)success;
- // mark enumeration as complete
- _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8;
+ TU_LOG_USBH("Enumeration complete: success = %u\r\n", success);
-#if CFG_TUH_HUB
+ _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; // mark enumeration as complete
+ _usbh_data.call_after.func = NULL;
+
+ #if CFG_TUH_HUB
// Hub status is already requested in case of successful enumeration
- if (_usbh_data.dev0_bus.hub_addr != 0 && !success) {
- hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status
+ if (!success && _usbh_data.dev0_bus.hub_addr != 0) {
+ hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr);
}
-#endif
-
+ #endif
}
#endif
diff --git a/src/host/usbh.h b/src/host/usbh.h
index 143d36f8c..7ddec35b7 100644
--- a/src/host/usbh.h
+++ b/src/host/usbh.h
@@ -41,8 +41,7 @@
// MACRO CONSTANT TYPEDEF
//--------------------------------------------------------------------+
-// Endpoint Bulk size depending on host mx speed
-#define TUH_EPSIZE_BULK_MPS (TUH_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS)
+
// forward declaration
struct tuh_xfer_s;
diff --git a/src/host/usbh_pvt.h b/src/host/usbh_pvt.h
index 57428e3c5..adb6a8c44 100644
--- a/src/host/usbh_pvt.h
+++ b/src/host/usbh_pvt.h
@@ -68,8 +68,12 @@ uint8_t* usbh_get_enum_buf(void);
void usbh_int_set(bool enabled);
+// Invoke this function later in tuh_task() by putting it into task queue
void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr);
+// Schedules a function to be called after certain time asynchronously
+bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param);
+
void usbh_spin_lock(bool in_isr);
void usbh_spin_unlock(bool in_isr);
diff --git a/src/osal/osal.h b/src/osal/osal.h
index 44521620f..4840463f3 100644
--- a/src/osal/osal.h
+++ b/src/osal/osal.h
@@ -65,6 +65,8 @@ typedef void (*osal_task_func_t)(void* param);
#include "osal_rtx4.h"
#elif CFG_TUSB_OS == OPT_OS_ZEPHYR
#include "osal_zephyr.h"
+#elif CFG_TUSB_OS == OPT_OS_THREADX
+ #include "osal_threadx.h"
#elif CFG_TUSB_OS == OPT_OS_CUSTOM
#include "tusb_os_custom.h" // implemented by application
#else
@@ -74,6 +76,8 @@ typedef void (*osal_task_func_t)(void* param);
/*--------------------------------------------------------------------
OSAL Porting API
Should be implemented as static inline function in osal_port.h header
+ uint32_t osal_time_millis(void);
+
void osal_spin_init(osal_spinlock_t *ctx);
void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr)
void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr);
diff --git a/src/osal/osal_freertos.h b/src/osal/osal_freertos.h
index 9aeda4d01..db724179d 100644
--- a/src/osal/osal_freertos.h
+++ b/src/osal/osal_freertos.h
@@ -99,6 +99,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) {
vTaskDelay(pdMS_TO_TICKS(msec));
}
+TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) {
+ return pdTICKS_TO_MS(xTaskGetTickCount());
+}
+
//--------------------------------------------------------------------+
// Spinlock API
//--------------------------------------------------------------------+
@@ -137,11 +141,12 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) {
TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) {
if (in_isr) {
- if (TUP_MCU_MULTIPLE_CORE == 0) {
- (void) ctx;
- return; // single core MCU does not need to lock in ISR
- }
+ #if TUP_MCU_MULTIPLE_CORE
*ctx = taskENTER_CRITICAL_FROM_ISR();
+ #else
+ (void) ctx;
+ return; // single core MCU does not need to lock in ISR
+ #endif
} else {
taskENTER_CRITICAL();
}
@@ -149,11 +154,12 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bo
TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) {
if (in_isr) {
- if (TUP_MCU_MULTIPLE_CORE == 0) {
- (void) ctx;
- return; // single core MCU does not need to lock in ISR
- }
+ #if TUP_MCU_MULTIPLE_CORE
taskEXIT_CRITICAL_FROM_ISR(*ctx);
+ #else
+ (void) ctx;
+ return; // single core MCU does not need to lock in ISR
+ #endif
} else {
taskEXIT_CRITICAL();
}
diff --git a/src/osal/osal_mynewt.h b/src/osal/osal_mynewt.h
index 6d51f8ec3..94124ca81 100644
--- a/src/osal/osal_mynewt.h
+++ b/src/osal/osal_mynewt.h
@@ -40,6 +40,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) {
os_time_delay( os_time_ms_to_ticks32(msec) );
}
+TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) {
+ return os_time_ticks_to_ms32(os_time_get());
+}
+
//--------------------------------------------------------------------+
// Spinlock API
//--------------------------------------------------------------------+
diff --git a/src/osal/osal_none.h b/src/osal/osal_none.h
index 6ab18ace8..7bf6029d6 100644
--- a/src/osal/osal_none.h
+++ b/src/osal/osal_none.h
@@ -31,6 +31,8 @@
extern "C" {
#endif
+// osal_time_millis() is not provided, tusb_time_millis_api() must be implemented by user application
+
//--------------------------------------------------------------------+
// Spinlock API
//--------------------------------------------------------------------+
@@ -184,7 +186,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, v
(void) msec; // not used, always behave as msec = 0
qhdl->interrupt_set(false);
- const bool success = tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size);
+ const bool success = (tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size) > 0);
qhdl->interrupt_set(true);
return success;
@@ -195,7 +197,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void
qhdl->interrupt_set(false);
}
- const bool success = tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size);
+ const bool success = (tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size) > 0);
if (!in_isr) {
qhdl->interrupt_set(true);
diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h
index 79b728e9a..6a0a21bb3 100644
--- a/src/osal/osal_pico.h
+++ b/src/osal/osal_pico.h
@@ -43,6 +43,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) {
sleep_ms(msec);
}
+TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) {
+ return to_ms_since_boot(get_absolute_time());
+}
+
//--------------------------------------------------------------------+
// Spinlock API
//--------------------------------------------------------------------+
diff --git a/src/osal/osal_rtthread.h b/src/osal/osal_rtthread.h
index a778f5425..f560281c5 100644
--- a/src/osal/osal_rtthread.h
+++ b/src/osal/osal_rtthread.h
@@ -42,6 +42,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) {
rt_thread_mdelay(msec);
}
+TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) {
+ return (uint32_t)((((uint64_t)rt_tick_get()) * 1000) / RT_TICK_PER_SECOND);
+}
+
//--------------------------------------------------------------------+
// Spinlock API
//--------------------------------------------------------------------+
diff --git a/src/osal/osal_rtx4.h b/src/osal/osal_rtx4.h
index 35860ddd5..e1930c96c 100644
--- a/src/osal/osal_rtx4.h
+++ b/src/osal/osal_rtx4.h
@@ -46,6 +46,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) {
os_dly_wait(lo);
}
+TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) {
+ return os_time_get();
+}
+
TU_ATTR_ALWAYS_INLINE static inline uint16_t msec2wait(uint32_t msec) {
if (msec == OSAL_TIMEOUT_WAIT_FOREVER) {
return 0xFFFF;
diff --git a/src/osal/osal_threadx.h b/src/osal/osal_threadx.h
new file mode 100644
index 000000000..6bcf9c5ab
--- /dev/null
+++ b/src/osal/osal_threadx.h
@@ -0,0 +1,201 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_OSAL_THREADX_H_
+#define TUSB_OSAL_THREADX_H_
+
+// ThreadX Headers
+#include "tx_api.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// TASK API
+//--------------------------------------------------------------------+
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) {
+ if ( msec == TX_WAIT_FOREVER ) {
+ return TX_WAIT_FOREVER;
+ }
+ if ( msec == 0 ) {
+ return 0;
+ }
+
+ uint32_t ticks = msec * TX_TIMER_TICKS_PER_SECOND / 1000;
+
+ // TX_TIMER_TICKS_PER_SECOND is less than 1000 and 1 tick > 1 ms
+ // we still need to delay at least 1 tick
+ if ( ticks == 0 ) {
+ ticks = 1;
+ }
+
+ return ticks;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) {
+ return (uint32_t)((uint64_t) tx_time_get() * 1000u / TX_TIMER_TICKS_PER_SECOND);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) {
+ tx_thread_sleep(_osal_ms2tick(msec));
+}
+
+//--------------------------------------------------------------------+
+// Spinlock API
+//--------------------------------------------------------------------+
+//--------------------------------------------------------------------+
+// Spinlock API
+//--------------------------------------------------------------------+
+typedef struct {
+ void (* interrupt_set)(bool);
+} osal_spinlock_t;
+
+// For SMP, spinlock must be locked by hardware, cannot just use interrupt
+#define OSAL_SPINLOCK_DEF(_name, _int_set) \
+ osal_spinlock_t _name = { .interrupt_set = _int_set }
+
+TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) {
+ (void) ctx;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) {
+ if (!in_isr) {
+ ctx->interrupt_set(false);
+ }
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) {
+ if (!in_isr) {
+ ctx->interrupt_set(true);
+ }
+}
+
+
+//--------------------------------------------------------------------+
+// Binary Semaphore API (act)
+//--------------------------------------------------------------------+
+// Note: semaphores are not used in tinyusb for now, and their API has not been tested
+
+typedef TX_SEMAPHORE osal_semaphore_def_t, * osal_semaphore_t;
+
+TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) {
+ tx_semaphore_create(semdef, TX_NULL, 0);
+ return semdef;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t sem_hdl) {
+ (void) sem_hdl;
+ return TX_SUCCESS == tx_semaphore_delete(sem_hdl);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) {
+ (void) in_isr;
+ return TX_SUCCESS == tx_semaphore_put(sem_hdl);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) {
+ return TX_SUCCESS == tx_semaphore_get(sem_hdl, _osal_ms2tick(msec));
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) {
+ (void) sem_hdl;
+}
+
+//--------------------------------------------------------------------+
+// MUTEX API
+//--------------------------------------------------------------------+
+typedef TX_MUTEX osal_mutex_def_t, *osal_mutex_t;
+
+TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) {
+ if (TX_SUCCESS == tx_mutex_create(mdef, mdef->tx_mutex_name, TX_NO_INHERIT)) {
+ return mdef;
+ } else {
+ return NULL;
+ }
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) {
+ (void) mutex_hdl;
+ return true; // nothing to do
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) {
+ return TX_SUCCESS == tx_mutex_get(mutex_hdl, _osal_ms2tick(msec));
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) {
+ return TX_SUCCESS == tx_mutex_put(mutex_hdl);
+}
+
+//--------------------------------------------------------------------+
+// QUEUE API
+//--------------------------------------------------------------------+
+
+typedef TX_QUEUE osal_queue_def_t, * osal_queue_t;
+
+// _int_set is not used with an RTOS _usbd_qdef
+
+#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \
+static _type _name##_buf[_depth]; \
+osal_queue_def_t _name = { \
+ .tx_queue_name = (CHAR*)(uintptr_t)#_name, \
+ .tx_queue_message_size = (sizeof(_type) + 3) / 4, \
+ .tx_queue_capacity = _depth, \
+ .tx_queue_start = (ULONG *) _name##_buf }
+
+
+TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) {
+ return TX_SUCCESS ==
+ tx_queue_create(qdef, qdef->tx_queue_name, qdef->tx_queue_message_size, qdef->tx_queue_start, qdef->tx_queue_capacity * qdef->tx_queue_message_size * 4)
+ ? qdef : 0;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) {
+ (void) qhdl;
+ return true;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) {
+ return 0 == tx_queue_receive(qhdl, data, _osal_ms2tick(msec));
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) {
+ return 0 == tx_queue_send(qhdl, (VOID *)(uintptr_t) data, in_isr ? TX_NO_WAIT : TX_WAIT_FOREVER);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) {
+ ULONG enqueued;
+ tx_queue_info_get(qhdl, 0, &enqueued, 0, 0, 0, 0);
+ return enqueued == 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/osal/osal_zephyr.h b/src/osal/osal_zephyr.h
index 91f225f79..900ac786c 100644
--- a/src/osal/osal_zephyr.h
+++ b/src/osal/osal_zephyr.h
@@ -35,6 +35,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) {
k_msleep(msec);
}
+TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) {
+ return k_uptime_get_32();
+}
+
//--------------------------------------------------------------------+
// Spinlock API
//--------------------------------------------------------------------+
diff --git a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h
index 178eec419..c22aea887 100644
--- a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h
+++ b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h
@@ -47,10 +47,10 @@ static const ci_hs_controller_t _ci_controller[] =
#define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base)
-#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ (_ci_controller[_p].irqnum)
-#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ(_ci_controller[_p].irqnum)
+#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum)
+#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum)
-#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ (_ci_controller[_p].irqnum)
-#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ(_ci_controller[_p].irqnum)
+#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum)
+#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum)
#endif
diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
index 2bba32ada..9ed75ffd9 100644
--- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c
+++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
@@ -285,6 +285,23 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
return true;
}
+bool dcd_deinit(uint8_t rhport) {
+ ci_hs_regs_t* dcd_reg = CI_HS_REG(rhport);
+
+ // disable all interrupt
+ dcd_reg->USBINTR = 0;
+
+ // unattach from bus
+ dcd_reg->USBCMD &= ~USBCMD_RUN_STOP;
+
+ // flush all endpoints
+ while (dcd_reg->ENDPTPRIME) {}
+ dcd_reg->ENDPTFLUSH = 0xFFFFFFFF;
+ while (dcd_reg->ENDPTFLUSH) {}
+
+ return true;
+}
+
void dcd_int_enable(uint8_t rhport) {
CI_DCD_INT_ENABLE(rhport);
}
@@ -371,7 +388,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ep_ctrl_mask(volatile uint32_t *epctrl,
uint32_t or_mask) {
uint32_t value = *epctrl;
if (and_mask != 0) {
- value &= (dir == TUSB_DIR_OUT) ? and_mask : (and_mask << 16u);
+ value &= (dir == TUSB_DIR_OUT) ? (and_mask | 0xFFFF0000u) : ((and_mask << 16u) | 0x0000FFFFu);
}
if (or_mask != 0) {
value |= (dir == TUSB_DIR_OUT) ? or_mask : (or_mask << 16u);
diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c
index 29ce0cd7f..c0d14fe57 100644
--- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c
+++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c
@@ -93,21 +93,25 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
hcd_reg->USBCMD |= USBCMD_RESET;
while (hcd_reg->USBCMD & USBCMD_RESET) {}
- // Set mode to device, must be set immediately after reset
+ // Set mode to host, must be set immediately after reset
#if CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX
// LPC18XX/43XX need to set VBUS Power Select to HIGH
- // RHPORT1 is fullspeed only (need external PHY for Highspeed)
hcd_reg->USBMODE = USBMODE_CM_HOST | USBMODE_VBUS_POWER_SELECT;
- if (rhport == 1) {
- hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED;
- }
#else
hcd_reg->USBMODE = USBMODE_CM_HOST;
#endif
+ #if !TUH_OPT_HIGH_SPEED
+ hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED;
+ #endif
+
return ehci_init(rhport, (uint32_t)&hcd_reg->CAPLENGTH, (uint32_t)&hcd_reg->USBCMD);
}
+bool hcd_deinit(uint8_t rhport) {
+ return ehci_deinit(rhport);
+}
+
void hcd_int_enable(uint8_t rhport) {
CI_HCD_INT_ENABLE(rhport);
}
diff --git a/src/portable/ehci/ehci.c b/src/portable/ehci/ehci.c
index 9b2cf98be..03c3b91fd 100644
--- a/src/portable/ehci/ehci.c
+++ b/src/portable/ehci/ehci.c
@@ -411,17 +411,22 @@ bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg)
return true;
}
-#if 0
-static void ehci_stop(uint8_t rhport) {
+bool ehci_deinit(uint8_t rhport) {
(void) rhport;
ehci_registers_t* regs = ehci_data.regs;
+
+ // Disable all the interrupt
+ regs->inten = 0;
+
+ // Disable schedules
regs->command_bm.run_stop = 0;
// USB Spec: controller has to stop within 16 uframe = 2 frames
while( regs->status_bm.hc_halted == 0 ) {}
+
+ return true;
}
-#endif
//--------------------------------------------------------------------+
// Endpoint API
diff --git a/src/portable/ehci/ehci_api.h b/src/portable/ehci/ehci_api.h
index 79fbe702a..e9018639f 100644
--- a/src/portable/ehci/ehci_api.h
+++ b/src/portable/ehci/ehci_api.h
@@ -38,6 +38,9 @@
// Initialize EHCI driver
bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg);
+// De-initialize EHCI driver
+bool ehci_deinit(uint8_t rhport);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/portable/microchip/samx7x/dcd_samx7x.c b/src/portable/microchip/samx7x/dcd_samx7x.c
index b0a053c01..6da1e9778 100644
--- a/src/portable/microchip/samx7x/dcd_samx7x.c
+++ b/src/portable/microchip/samx7x/dcd_samx7x.c
@@ -1,133 +1,117 @@
/*
-* The MIT License (MIT)
-*
-* Copyright (c) 2018, hathach (tinyusb.org)
-* Copyright (c) 2021, HiFiPhile
-*
-* Permission is hereby granted, free of charge, to any person obtaining a copy
-* of this software and associated documentation files (the "Software"), to deal
-* in the Software without restriction, including without limitation the rights
-* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-* copies of the Software, and to permit persons to whom the Software is
-* furnished to do so, subject to the following conditions:
-*
-* The above copyright notice and this permission notice shall be included in
-* all copies or substantial portions of the Software.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-* THE SOFTWARE.
-*
-* This file is part of the TinyUSB stack.
-*/
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018, hathach (tinyusb.org)
+ * Copyright (c) 2021, HiFiPhile
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
#include "tusb_option.h"
#if CFG_TUD_ENABLED && CFG_TUSB_MCU == OPT_MCU_SAMX7X
-#include "device/dcd.h"
-#include "sam.h"
-#include "common_usb_regs.h"
-//--------------------------------------------------------------------+
-// MACRO TYPEDEF CONSTANT ENUM DECLARATION
-//--------------------------------------------------------------------+
-
-// Since TinyUSB doesn't use SOF for now, and this interrupt too often (1ms interval)
-// We disable SOF for now until needed later on
-#ifndef USE_SOF
-# define USE_SOF 0
-#endif
-
-// Dual bank can improve performance, but need 2 times bigger packet buffer
-// As SAM7x has only 4KB packet buffer, use with caution !
-// Enable in FS mode as packets are smaller
-#ifndef USE_DUAL_BANK
-# if TUD_OPT_HIGH_SPEED
-# define USE_DUAL_BANK 0
-# else
-# define USE_DUAL_BANK 1
-# endif
-#endif
+ #include "device/dcd.h"
+ #include "sam.h"
+ #include "samx7x_common.h"
+ //--------------------------------------------------------------------+
+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION
+ //--------------------------------------------------------------------+
-#define EP_GET_FIFO_PTR(ep, scale) (((TU_XSTRCAT(TU_STRCAT(uint, scale),_t) (*)[0x8000 / ((scale) / 8)])FIFO_RAM_ADDR)[(ep)])
+ // Dual bank can improve performance, but need 2 times bigger packet buffer
+ // As SAM7x has only 4KB packet buffer, use with caution !
+ // Enable in FS mode as packets are smaller
+ #ifndef USE_DUAL_BANK
+ #if TUD_OPT_HIGH_SPEED
+ #define USE_DUAL_BANK 0
+ #else
+ #define USE_DUAL_BANK 1
+ #endif
+ #endif
-// DMA Channel Transfer Descriptor
-typedef struct {
- volatile uint32_t next_desc;
- volatile uint32_t buff_addr;
- volatile uint32_t chnl_ctrl;
- uint32_t padding;
-} dma_desc_t;
+ #define EP_GET_FIFO_PTR(ep, scale) \
+ (((TU_XSTRCAT(TU_STRCAT(uint, scale), _t)(*)[0x8000 / ((scale) / 8)]) FIFO_RAM_ADDR)[(ep)])
// Transfer control context
typedef struct {
- uint8_t * buffer;
- uint16_t total_len;
- uint16_t queued_len;
- uint16_t max_packet_size;
- uint8_t interval;
- tu_fifo_t * fifo;
+ uint8_t *buffer;
+ uint16_t total_len;
+ uint16_t queued_len;
+ uint16_t max_packet_size;
+ uint8_t interval;
+ tu_fifo_t *fifo;
} xfer_ctl_t;
static tusb_speed_t get_speed(void);
-static void dcd_transmit_packet(xfer_ctl_t * xfer, uint8_t ep_ix);
-
-// DMA descriptors shouldn't be placed in ITCM !
-CFG_TUD_MEM_SECTION static dma_desc_t dma_desc[6];
+static void dcd_transmit_packet(xfer_ctl_t *xfer, uint8_t ep_ix);
static xfer_ctl_t xfer_status[EP_MAX];
-static const tusb_desc_endpoint_t ep0_desc =
-{
+static const tusb_desc_endpoint_t ep0_desc = {
.bEndpointAddress = 0x00,
.wMaxPacketSize = CFG_TUD_ENDPOINT0_SIZE,
};
-TU_ATTR_ALWAYS_INLINE static inline void CleanInValidateCache(uint32_t *addr, int32_t size)
-{
- if (SCB->CCR & SCB_CCR_DC_Msk)
- {
- SCB_CleanInvalidateDCache_by_Addr(addr, size);
- }
- else
- {
- __DSB();
- __ISB();
- }
+ #if CFG_TUD_MEM_DCACHE_ENABLE
+bool dcd_dcache_clean(const void *addr, uint32_t data_size) {
+ TU_VERIFY(addr && data_size);
+ return samx7x_dcache_clean(addr, data_size);
}
+
+bool dcd_dcache_invalidate(const void *addr, uint32_t data_size) {
+ TU_VERIFY(addr && data_size);
+ return samx7x_dcache_invalidate(addr, data_size);
+}
+
+bool dcd_dcache_clean_invalidate(const void *addr, uint32_t data_size) {
+ TU_VERIFY(addr && data_size);
+ return samx7x_dcache_clean_invalidate(addr, data_size);
+}
+ #endif
//------------------------------------------------------------------
// Device API
//------------------------------------------------------------------
// Initialize controller to device mode
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rh_init;
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rh_init;
dcd_connect(rhport);
return true;
}
// Enable device interrupt
-void dcd_int_enable (uint8_t rhport)
-{
- (void) rhport;
- NVIC_EnableIRQ((IRQn_Type) ID_USBHS);
+void dcd_int_enable(uint8_t rhport) {
+ (void)rhport;
+ NVIC_EnableIRQ((IRQn_Type)ID_USBHS);
}
// Disable device interrupt
-void dcd_int_disable (uint8_t rhport)
-{
- (void) rhport;
- NVIC_DisableIRQ((IRQn_Type) ID_USBHS);
+void dcd_int_disable(uint8_t rhport) {
+ (void)rhport;
+ NVIC_DisableIRQ((IRQn_Type)ID_USBHS);
}
// Receive Set Address request, mcu port must also include status IN response
-void dcd_set_address (uint8_t rhport, uint8_t dev_addr)
-{
- (void) dev_addr;
+void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
+ (void)dev_addr;
// DCD can only set address after status for this request is complete
// do it at dcd_edpt0_status_complete()
@@ -136,30 +120,26 @@ void dcd_set_address (uint8_t rhport, uint8_t dev_addr)
}
// Wake up host
-void dcd_remote_wakeup (uint8_t rhport)
-{
- (void) rhport;
+void dcd_remote_wakeup(uint8_t rhport) {
+ (void)rhport;
USB_REG->DEVCTRL |= DEVCTRL_RMWKUP;
}
// Connect by enabling internal pull-up resistor on D+/D-
-void dcd_connect(uint8_t rhport)
-{
- (void) rhport;
+void dcd_connect(uint8_t rhport) {
+ (void)rhport;
dcd_int_disable(rhport);
// Enable the USB controller in device mode
USB_REG->CTRL = CTRL_UIMOD | CTRL_USBE;
- while (!(USB_REG->SR & SR_CLKUSABLE));
-#if TUD_OPT_HIGH_SPEED
+ while (!(USB_REG->SR & SR_CLKUSABLE))
+ ;
+ #if TUD_OPT_HIGH_SPEED
USB_REG->DEVCTRL &= ~DEVCTRL_SPDCONF;
-#else
+ #else
USB_REG->DEVCTRL |= DEVCTRL_SPDCONF_LOW_POWER;
-#endif
+ #endif
// Enable the End Of Reset, Suspend & Wakeup interrupts
USB_REG->DEVIER = (DEVIER_EORSTES | DEVIER_SUSPES | DEVIER_WAKEUPES);
-#if USE_SOF
- USB_REG->DEVIER = DEVIER_SOFES;
-#endif
// Clear the End Of Reset, SOF & Wakeup interrupts
USB_REG->DEVICR = (DEVICR_EORSTC | DEVICR_SOFC | DEVICR_WAKEUPC);
// Manually set the Suspend Interrupt
@@ -173,15 +153,15 @@ void dcd_connect(uint8_t rhport)
}
// Disconnect by disabling internal pull-up resistor on D+/D-
-void dcd_disconnect(uint8_t rhport)
-{
- (void) rhport;
+void dcd_disconnect(uint8_t rhport) {
+ (void)rhport;
dcd_int_disable(rhport);
// Disable all endpoints
USB_REG->DEVEPT &= ~(0x3FF << DEVEPT_EPEN0_Pos);
// Unfreeze USB clock
USB_REG->CTRL &= ~CTRL_FRZCLK;
- while (!(USB_REG->SR & SR_CLKUSABLE));
+ while (!(USB_REG->SR & SR_CLKUSABLE))
+ ;
// Clear all the pending interrupts
USB_REG->DEVICR = DEVICR_Msk;
// Disable all interrupts
@@ -189,127 +169,106 @@ void dcd_disconnect(uint8_t rhport)
// Detach the device
USB_REG->DEVCTRL |= DEVCTRL_DETACH;
// Disable the device address
- USB_REG->DEVCTRL &=~(DEVCTRL_ADDEN | DEVCTRL_UADD);
+ USB_REG->DEVCTRL &= ~(DEVCTRL_ADDEN | DEVCTRL_UADD);
}
-void dcd_sof_enable(uint8_t rhport, bool en)
-{
- (void) rhport;
- (void) en;
-
- // TODO implement later
+void dcd_sof_enable(uint8_t rhport, bool en) {
+ (void)rhport;
+ if (en) {
+ USB_REG->DEVIER = DEVIER_SOFES;
+ } else {
+ USB_REG->DEVIDR = DEVIDR_SOFEC;
+ }
}
-static tusb_speed_t get_speed(void)
-{
+static tusb_speed_t get_speed(void) {
switch (USB_REG->SR & SR_SPEED) {
- case SR_SPEED_FULL_SPEED:
- default:
- return TUSB_SPEED_FULL;
- case SR_SPEED_HIGH_SPEED:
- return TUSB_SPEED_HIGH;
- case SR_SPEED_LOW_SPEED:
- return TUSB_SPEED_LOW;
+ case SR_SPEED_FULL_SPEED:
+ default:
+ return TUSB_SPEED_FULL;
+ case SR_SPEED_HIGH_SPEED:
+ return TUSB_SPEED_HIGH;
+ case SR_SPEED_LOW_SPEED:
+ return TUSB_SPEED_LOW;
}
}
-static void dcd_ep_handler(uint8_t ep_ix)
-{
+static void dcd_ep_handler(uint8_t ep_ix) {
uint32_t int_status = USB_REG->DEVEPTISR[ep_ix];
int_status &= USB_REG->DEVEPTIMR[ep_ix];
- uint16_t count = (USB_REG->DEVEPTISR[ep_ix] &
- DEVEPTISR_BYCT) >> DEVEPTISR_BYCT_Pos;
- xfer_ctl_t *xfer = &xfer_status[ep_ix];
+ uint16_t count = (USB_REG->DEVEPTISR[ep_ix] & DEVEPTISR_BYCT) >> DEVEPTISR_BYCT_Pos;
+ xfer_ctl_t *xfer = &xfer_status[ep_ix];
- if (ep_ix == 0U)
- {
+ if (ep_ix == 0U) {
static uint8_t ctrl_dir;
- if (int_status & DEVEPTISR_CTRL_RXSTPI)
- {
+ if (int_status & DEVEPTISR_CTRL_RXSTPI) {
ctrl_dir = (USB_REG->DEVEPTISR[0] & DEVEPTISR_CTRL_CTRLDIR) >> DEVEPTISR_CTRL_CTRLDIR_Pos;
// Setup packet should always be 8 bytes. If not, ignore it, and try again.
- if (count == 8)
- {
- uint8_t *ptr = EP_GET_FIFO_PTR(0,8);
+ if (count == 8) {
+ uint8_t *ptr = EP_GET_FIFO_PTR(0, 8);
dcd_event_setup_received(0, ptr, true);
}
// Ack and disable SETUP interrupt
USB_REG->DEVEPTICR[0] = DEVEPTICR_CTRL_RXSTPIC;
USB_REG->DEVEPTIDR[0] = DEVEPTIDR_CTRL_RXSTPEC;
}
- if (int_status & DEVEPTISR_RXOUTI)
- {
- uint8_t *ptr = EP_GET_FIFO_PTR(0,8);
+ if (int_status & DEVEPTISR_RXOUTI) {
+ uint8_t *ptr = EP_GET_FIFO_PTR(0, 8);
- if (count && xfer->total_len)
- {
+ if (count && xfer->total_len) {
uint16_t remain = xfer->total_len - xfer->queued_len;
- if (count > remain)
- {
+ if (count > remain) {
count = remain;
}
- if (xfer->buffer)
- {
+ if (xfer->buffer) {
memcpy(xfer->buffer + xfer->queued_len, ptr, count);
- } else
- {
- tu_fifo_write_n(xfer->fifo, ptr, count);
+ } else {
+ tu_hwfifo_read_to_fifo(ptr, xfer->fifo, count, NULL);
}
xfer->queued_len = (uint16_t)(xfer->queued_len + count);
}
// Acknowledge the interrupt
USB_REG->DEVEPTICR[0] = DEVEPTICR_RXOUTIC;
- if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len))
- {
+ if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len)) {
// RX COMPLETE
dcd_event_xfer_complete(0, 0, xfer->queued_len, XFER_RESULT_SUCCESS, true);
// Disable the interrupt
USB_REG->DEVEPTIDR[0] = DEVEPTIDR_RXOUTEC;
// Re-enable SETUP interrupt
- if (ctrl_dir == 1)
- {
+ if (ctrl_dir == 1) {
USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES;
}
}
}
- if (int_status & DEVEPTISR_TXINI)
- {
+ if (int_status & DEVEPTISR_TXINI) {
// Disable the interrupt
USB_REG->DEVEPTIDR[0] = DEVEPTIDR_TXINEC;
- if ((xfer->total_len != xfer->queued_len))
- {
+ if ((xfer->total_len != xfer->queued_len)) {
// TX not complete
dcd_transmit_packet(xfer, 0);
- } else
- {
+ } else {
// TX complete
dcd_event_xfer_complete(0, 0x80 + 0, xfer->total_len, XFER_RESULT_SUCCESS, true);
// Re-enable SETUP interrupt
- if (ctrl_dir == 0)
- {
+ if (ctrl_dir == 0) {
USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES;
}
}
}
- } else
- {
- if (int_status & DEVEPTISR_RXOUTI)
- {
- if (count && xfer->total_len)
- {
+ } else {
+ if (int_status & DEVEPTISR_RXOUTI) {
+ if (count && xfer->total_len) {
uint16_t remain = xfer->total_len - xfer->queued_len;
- if (count > remain)
- {
+ if (count > remain) {
count = remain;
}
- uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix,8);
- if (xfer->buffer)
- {
+ uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix, 8);
+ if (xfer->buffer) {
memcpy(xfer->buffer + xfer->queued_len, ptr, count);
} else {
- tu_fifo_write_n(xfer->fifo, ptr, count);
+ tu_hwfifo_read_to_fifo(ptr, xfer->fifo, count, NULL);
}
xfer->queued_len = (uint16_t)(xfer->queued_len + count);
}
@@ -317,8 +276,7 @@ static void dcd_ep_handler(uint8_t ep_ix)
USB_REG->DEVEPTIDR[ep_ix] = DEVEPTIDR_FIFOCONC;
// Acknowledge the interrupt
USB_REG->DEVEPTICR[ep_ix] = DEVEPTICR_RXOUTIC;
- if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len))
- {
+ if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len)) {
// RX COMPLETE
dcd_event_xfer_complete(0, ep_ix, xfer->queued_len, XFER_RESULT_SUCCESS, true);
// Disable the interrupt
@@ -326,16 +284,13 @@ static void dcd_ep_handler(uint8_t ep_ix)
// Though the host could still send, we don't know.
}
}
- if (int_status & DEVEPTISR_TXINI)
- {
+ if (int_status & DEVEPTISR_TXINI) {
// Acknowledge the interrupt
USB_REG->DEVEPTICR[ep_ix] = DEVEPTICR_TXINIC;
- if ((xfer->total_len != xfer->queued_len))
- {
+ if ((xfer->total_len != xfer->queued_len)) {
// TX not complete
dcd_transmit_packet(xfer, ep_ix);
- } else
- {
+ } else {
// TX complete
dcd_event_xfer_complete(0, 0x80 + ep_ix, xfer->total_len, XFER_RESULT_SUCCESS, true);
// Disable the interrupt
@@ -345,45 +300,40 @@ static void dcd_ep_handler(uint8_t ep_ix)
}
}
-static void dcd_dma_handler(uint8_t ep_ix)
-{
+static void dcd_dma_handler(uint8_t ep_ix) {
uint32_t status = USB_REG->DEVDMA[ep_ix - 1].DEVDMASTATUS;
- if (status & DEVDMASTATUS_CHANN_ENB)
- {
+ if (status & DEVDMASTATUS_CHANN_ENB) {
return; // Ignore EOT_STA interrupt
}
// Disable DMA interrupt
USB_REG->DEVIDR = DEVIDR_DMA_1 << (ep_ix - 1);
- xfer_ctl_t *xfer = &xfer_status[ep_ix];
- uint16_t count = xfer->total_len - ((status & DEVDMASTATUS_BUFF_COUNT) >> DEVDMASTATUS_BUFF_COUNT_Pos);
- if(USB_REG->DEVEPTCFG[ep_ix] & DEVEPTCFG_EPDIR)
- {
+ xfer_ctl_t *xfer = &xfer_status[ep_ix];
+ uint16_t count = xfer->total_len - ((status & DEVDMASTATUS_BUFF_COUNT) >> DEVDMASTATUS_BUFF_COUNT_Pos);
+ if (USB_REG->DEVEPTCFG[ep_ix] & DEVEPTCFG_EPDIR) {
dcd_event_xfer_complete(0, 0x80 + ep_ix, count, XFER_RESULT_SUCCESS, true);
- } else
- {
+ } else {
+ dcd_dcache_invalidate(xfer->buffer, count);
dcd_event_xfer_complete(0, ep_ix, count, XFER_RESULT_SUCCESS, true);
}
}
-void dcd_int_handler(uint8_t rhport)
-{
- (void) rhport;
+void dcd_int_handler(uint8_t rhport) {
+ (void)rhport;
uint32_t int_status = USB_REG->DEVISR;
int_status &= USB_REG->DEVIMR;
// End of reset interrupt
- if (int_status & DEVISR_EORST)
- {
+ if (int_status & DEVISR_EORST) {
// Unfreeze USB clock
USB_REG->CTRL &= ~CTRL_FRZCLK;
- while(!(USB_REG->SR & SR_CLKUSABLE));
+ while (!(USB_REG->SR & SR_CLKUSABLE))
+ ;
// Reset all endpoints
- for (int ep_ix = 1; ep_ix < EP_MAX; ep_ix++)
- {
+ for (int ep_ix = 1; ep_ix < EP_MAX; ep_ix++) {
USB_REG->DEVEPT |= 1 << (DEVEPT_EPRST0_Pos + ep_ix);
- USB_REG->DEVEPT &=~(1 << (DEVEPT_EPRST0_Pos + ep_ix));
+ USB_REG->DEVEPT &= ~(1 << (DEVEPT_EPRST0_Pos + ep_ix));
}
- dcd_edpt_open (0, &ep0_desc);
+ dcd_edpt_open(0, &ep0_desc);
USB_REG->DEVICR = DEVICR_EORSTC;
USB_REG->DEVICR = DEVICR_WAKEUPC;
USB_REG->DEVICR = DEVICR_SUSPC;
@@ -392,10 +342,10 @@ void dcd_int_handler(uint8_t rhport)
dcd_event_bus_reset(rhport, get_speed(), true);
}
// End of Wakeup interrupt
- if (int_status & DEVISR_WAKEUP)
- {
+ if (int_status & DEVISR_WAKEUP) {
USB_REG->CTRL &= ~CTRL_FRZCLK;
- while (!(USB_REG->SR & SR_CLKUSABLE));
+ while (!(USB_REG->SR & SR_CLKUSABLE))
+ ;
USB_REG->DEVICR = DEVICR_WAKEUPC;
USB_REG->DEVIDR = DEVIDR_WAKEUPEC;
USB_REG->DEVIER = DEVIER_SUSPES;
@@ -403,11 +353,11 @@ void dcd_int_handler(uint8_t rhport)
dcd_event_bus_signal(0, DCD_EVENT_RESUME, true);
}
// Suspend interrupt
- if (int_status & DEVISR_SUSP)
- {
+ if (int_status & DEVISR_SUSP) {
// Unfreeze USB clock
USB_REG->CTRL &= ~CTRL_FRZCLK;
- while (!(USB_REG->SR & SR_CLKUSABLE));
+ while (!(USB_REG->SR & SR_CLKUSABLE))
+ ;
USB_REG->DEVICR = DEVICR_SUSPC;
USB_REG->DEVIDR = DEVIDR_SUSPEC;
USB_REG->DEVIER = DEVIER_WAKEUPES;
@@ -415,29 +365,21 @@ void dcd_int_handler(uint8_t rhport)
dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true);
}
-#if USE_SOF
- if(int_status & DEVISR_SOF)
- {
+ if (int_status & DEVISR_SOF) {
USB_REG->DEVICR = DEVICR_SOFC;
dcd_event_bus_signal(0, DCD_EVENT_SOF, true);
}
-#endif
// Endpoints interrupt
- for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++)
- {
- if (int_status & (DEVISR_PEP_0 << ep_ix))
- {
+ for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++) {
+ if (int_status & (DEVISR_PEP_0 << ep_ix)) {
dcd_ep_handler(ep_ix);
}
}
// Endpoints DMA interrupt
- for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++)
- {
- if (EP_DMA_SUPPORT(ep_ix))
- {
- if (int_status & (DEVISR_DMA_1 << (ep_ix - 1)))
- {
+ for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++) {
+ if (EP_DMA_SUPPORT(ep_ix)) {
+ if (int_status & (DEVISR_DMA_1 << (ep_ix - 1))) {
dcd_dma_handler(ep_ix);
}
}
@@ -449,35 +391,29 @@ void dcd_int_handler(uint8_t rhport)
//--------------------------------------------------------------------+
// Invoked when a control transfer's status stage is complete.
// May help DCD to prepare for next control transfer, this API is optional.
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request)
-{
- (void) rhport;
+void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) {
+ (void)rhport;
if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
- request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
- request->bRequest == TUSB_REQ_SET_ADDRESS )
- {
- uint8_t const dev_addr = (uint8_t) request->wValue;
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) {
+ const uint8_t dev_addr = (uint8_t)request->wValue;
USB_REG->DEVCTRL |= dev_addr | DEVCTRL_ADDEN;
}
}
// Configure endpoint's registers according to descriptor
-bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * ep_desc)
-{
- (void) rhport;
- uint8_t const epnum = tu_edpt_number(ep_desc->bEndpointAddress);
- uint8_t const dir = tu_edpt_dir(ep_desc->bEndpointAddress);
- uint16_t const epMaxPktSize = tu_edpt_packet_size(ep_desc);
- tusb_xfer_type_t const eptype = (tusb_xfer_type_t)ep_desc->bmAttributes.xfer;
- uint8_t fifoSize = 0; // FIFO size
- uint16_t defaultEndpointSize = 8; // Default size of Endpoint
+bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) {
+ (void)rhport;
+ const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress);
+ const uint8_t dir = tu_edpt_dir(ep_desc->bEndpointAddress);
+ const uint16_t epMaxPktSize = tu_edpt_packet_size(ep_desc);
+ const tusb_xfer_type_t eptype = (tusb_xfer_type_t)ep_desc->bmAttributes.xfer;
+ uint8_t fifoSize = 0; // FIFO size
+ uint16_t defaultEndpointSize = 8; // Default size of Endpoint
// Find upper 2 power number of epMaxPktSize
- if (epMaxPktSize)
- {
- while (defaultEndpointSize < epMaxPktSize)
- {
+ if (epMaxPktSize) {
+ while (defaultEndpointSize < epMaxPktSize) {
fifoSize++;
defaultEndpointSize <<= 1;
}
@@ -485,121 +421,94 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * ep_desc)
xfer_status[epnum].max_packet_size = epMaxPktSize;
USB_REG->DEVEPT |= 1 << (DEVEPT_EPRST0_Pos + epnum);
- USB_REG->DEVEPT &=~(1 << (DEVEPT_EPRST0_Pos + epnum));
+ USB_REG->DEVEPT &= ~(1 << (DEVEPT_EPRST0_Pos + epnum));
- if (epnum == 0)
- {
+ if (epnum == 0) {
// Enable the control endpoint - Endpoint 0
USB_REG->DEVEPT |= DEVEPT_EPEN0;
// Configure the Endpoint 0 configuration register
- USB_REG->DEVEPTCFG[0] =
- (
- (fifoSize << DEVEPTCFG_EPSIZE_Pos) |
- (TUSB_XFER_CONTROL << DEVEPTCFG_EPTYPE_Pos) |
- (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) |
- DEVEPTCFG_ALLOC
- );
+ USB_REG->DEVEPTCFG[0] = ((fifoSize << DEVEPTCFG_EPSIZE_Pos) | (TUSB_XFER_CONTROL << DEVEPTCFG_EPTYPE_Pos) |
+ (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) | DEVEPTCFG_ALLOC);
USB_REG->DEVEPTIER[0] = DEVEPTIER_RSTDTS;
USB_REG->DEVEPTIDR[0] = DEVEPTIDR_CTRL_STALLRQC;
- if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[0] & DEVEPTISR_CFGOK))
- {
+ if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[0] & DEVEPTISR_CFGOK)) {
// Endpoint configuration is successful
USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES;
// Enable Endpoint 0 Interrupts
USB_REG->DEVIER = DEVIER_PEP_0;
return true;
- } else
- {
+ } else {
// Endpoint configuration is not successful
return false;
}
- } else
- {
+ } else {
// Enable the endpoint
USB_REG->DEVEPT |= ((0x01 << epnum) << DEVEPT_EPEN0_Pos);
// Set up the maxpacket size, fifo start address fifosize
// and enable the interrupt. CLear the data toggle.
// AUTOSW is needed for DMA ack !
USB_REG->DEVEPTCFG[epnum] =
- (
- (fifoSize << DEVEPTCFG_EPSIZE_Pos) |
- (eptype << DEVEPTCFG_EPTYPE_Pos) |
- (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) |
- DEVEPTCFG_AUTOSW |
- ((dir & 0x01) << DEVEPTCFG_EPDIR_Pos)
- );
- if (eptype == TUSB_XFER_ISOCHRONOUS)
- {
+ ((fifoSize << DEVEPTCFG_EPSIZE_Pos) | (eptype << DEVEPTCFG_EPTYPE_Pos) |
+ (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) | DEVEPTCFG_AUTOSW | ((dir & 0x01) << DEVEPTCFG_EPDIR_Pos));
+ if (eptype == TUSB_XFER_ISOCHRONOUS) {
USB_REG->DEVEPTCFG[epnum] |= DEVEPTCFG_NBTRANS_1_TRANS;
}
-#if USE_DUAL_BANK
- if (eptype == TUSB_XFER_ISOCHRONOUS || eptype == TUSB_XFER_BULK)
- {
+ #if USE_DUAL_BANK
+ if (eptype == TUSB_XFER_ISOCHRONOUS || eptype == TUSB_XFER_BULK) {
USB_REG->DEVEPTCFG[epnum] |= DEVEPTCFG_EPBK_2_BANK;
}
-#endif
+ #endif
USB_REG->DEVEPTCFG[epnum] |= DEVEPTCFG_ALLOC;
USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RSTDTS;
USB_REG->DEVEPTIDR[epnum] = DEVEPTIDR_CTRL_STALLRQC;
- if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[epnum] & DEVEPTISR_CFGOK))
- {
+ if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[epnum] & DEVEPTISR_CFGOK)) {
USB_REG->DEVIER = ((0x01 << epnum) << DEVIER_PEP_0_Pos);
return true;
- } else
- {
+ } else {
// Endpoint configuration is not successful
return false;
}
}
}
-void dcd_edpt_close_all (uint8_t rhport)
-{
- (void) rhport;
+void dcd_edpt_close_all(uint8_t rhport) {
+ (void)rhport;
// TODO implement dcd_edpt_close_all()
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void) rhport;
- (void) ep_addr;
- (void) largest_packet_size;
+ (void)rhport;
+ (void)ep_addr;
+ (void)largest_packet_size;
return false;
}
-bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) {
- (void) rhport;
- (void) desc_ep;
+bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
+ (void)rhport;
+ (void)desc_ep;
return false;
}
-static void dcd_transmit_packet(xfer_ctl_t * xfer, uint8_t ep_ix)
-{
+static void dcd_transmit_packet(xfer_ctl_t *xfer, uint8_t ep_ix) {
uint16_t len = (uint16_t)(xfer->total_len - xfer->queued_len);
- if (len)
- {
- if (len > xfer->max_packet_size)
- {
+ if (len) {
+ if (len > xfer->max_packet_size) {
len = xfer->max_packet_size;
}
- uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix,8);
- if(xfer->buffer)
- {
+ uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix, 8);
+ if (xfer->buffer) {
memcpy(ptr, xfer->buffer + xfer->queued_len, len);
- }
- else
- {
- tu_fifo_read_n(xfer->fifo, ptr, len);
+ } else {
+ tu_hwfifo_write_from_fifo(ptr, xfer->fifo, len, NULL);
}
__DSB();
__ISB();
xfer->queued_len = (uint16_t)(xfer->queued_len + len);
}
- if (ep_ix == 0U)
- {
+ if (ep_ix == 0U) {
// Control endpoint: clear the interrupt flag to send the data
USB_REG->DEVEPTICR[0] = DEVEPTICR_TXINIC;
- } else
- {
+ } else {
// Other endpoint types: clear the FIFO control flag to send the data
USB_REG->DEVEPTIDR[ep_ix] = DEVEPTIDR_FIFOCONC;
}
@@ -607,59 +516,36 @@ static void dcd_transmit_packet(xfer_ctl_t * xfer, uint8_t ep_ix)
}
// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr)
-{
- (void) is_isr;
- (void) rhport;
- uint8_t const epnum = tu_edpt_number(ep_addr);
- uint8_t const dir = tu_edpt_dir(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const uint8_t dir = tu_edpt_dir(ep_addr);
- xfer_ctl_t * xfer = &xfer_status[epnum];
+ xfer_ctl_t *xfer = &xfer_status[epnum];
- xfer->buffer = buffer;
- xfer->total_len = total_bytes;
+ xfer->buffer = buffer;
+ xfer->total_len = total_bytes;
xfer->queued_len = 0;
- xfer->fifo = NULL;
+ xfer->fifo = NULL;
- if (EP_DMA_SUPPORT(epnum) && total_bytes != 0)
- {
- // Force the CPU to flush the buffer. We increase the size by 32 because the call aligns the
- // address to 32-byte boundaries.
- CleanInValidateCache((uint32_t*) tu_align((uint32_t) buffer, 4), total_bytes + 31);
+ if (EP_DMA_SUPPORT(epnum) && total_bytes != 0) {
uint32_t udd_dma_ctrl = total_bytes << DEVDMACONTROL_BUFF_LENGTH_Pos;
- if (dir == TUSB_DIR_OUT)
- {
+ if (dir == TUSB_DIR_OUT) {
udd_dma_ctrl |= DEVDMACONTROL_END_TR_IT | DEVDMACONTROL_END_TR_EN;
} else {
udd_dma_ctrl |= DEVDMACONTROL_END_B_EN;
+ dcd_dcache_clean(xfer->buffer, total_bytes);
}
USB_REG->DEVDMA[epnum - 1].DEVDMAADDRESS = (uint32_t)buffer;
udd_dma_ctrl |= DEVDMACONTROL_END_BUFFIT | DEVDMACONTROL_CHANN_ENB;
- // Disable IRQs to have a short sequence
- // between read of EOT_STA and DMA enable
- uint32_t irq_state = __get_PRIMASK();
- __disable_irq();
- if (!(USB_REG->DEVDMA[epnum - 1].DEVDMASTATUS & DEVDMASTATUS_END_TR_ST))
- {
- USB_REG->DEVDMA[epnum - 1].DEVDMACONTROL = udd_dma_ctrl;
- USB_REG->DEVIER = DEVIER_DMA_1 << (epnum - 1);
- __set_PRIMASK(irq_state);
- return true;
- }
- __set_PRIMASK(irq_state);
-
- // Here a ZLP has been received
- // and the DMA transfer must be not started.
- // It is the end of transfer
- return false;
- } else
- {
- if (dir == TUSB_DIR_OUT)
- {
+ USB_REG->DEVDMA[epnum - 1].DEVDMACONTROL = udd_dma_ctrl;
+ USB_REG->DEVIER = DEVIER_DMA_1 << (epnum - 1);
+ } else {
+ if (dir == TUSB_DIR_OUT) {
USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RXOUTES;
- } else
- {
- dcd_transmit_packet(xfer,epnum);
+ } else {
+ dcd_transmit_packet(xfer, epnum);
}
}
return true;
@@ -669,112 +555,43 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
// bytes should be written and second to keep the return value free to give back a boolean
// success message. If total_bytes is too big, the FIFO will copy only what is available
// into the USB buffer!
-bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr)
-{
- (void) is_isr;
- (void) rhport;
- uint8_t const epnum = tu_edpt_number(ep_addr);
- uint8_t const dir = tu_edpt_dir(ep_addr);
+bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const uint8_t dir = tu_edpt_dir(ep_addr);
- xfer_ctl_t * xfer = &xfer_status[epnum];
- if(epnum == 0x80)
- xfer = &xfer_status[EP_MAX];
+ xfer_ctl_t *xfer = &xfer_status[epnum];
- xfer->buffer = NULL;
- xfer->total_len = total_bytes;
+ xfer->buffer = NULL;
+ xfer->total_len = total_bytes;
xfer->queued_len = 0;
- xfer->fifo = ff;
-
- if (EP_DMA_SUPPORT(epnum) && total_bytes != 0)
- {
- tu_fifo_buffer_info_t info;
- uint32_t udd_dma_ctrl_lin = DEVDMACONTROL_CHANN_ENB;
- uint32_t udd_dma_ctrl_wrap = DEVDMACONTROL_CHANN_ENB | DEVDMACONTROL_END_BUFFIT;
- if (dir == TUSB_DIR_OUT)
- {
- tu_fifo_get_write_info(ff, &info);
- udd_dma_ctrl_lin |= DEVDMACONTROL_END_TR_IT | DEVDMACONTROL_END_TR_EN;
- udd_dma_ctrl_wrap |= DEVDMACONTROL_END_TR_IT | DEVDMACONTROL_END_TR_EN;
- } else {
- tu_fifo_get_read_info(ff, &info);
- if(info.wrapped.len == 0)
- {
- udd_dma_ctrl_lin |= DEVDMACONTROL_END_B_EN;
- }
- udd_dma_ctrl_wrap |= DEVDMACONTROL_END_B_EN;
- }
-
- // Clean invalidate cache of linear part
- CleanInValidateCache((uint32_t*) tu_align((uint32_t) info.linear.ptr, 4), info.linear.len + 31);
-
- USB_REG->DEVDMA[epnum - 1].DEVDMAADDRESS = (uint32_t)info.linear.ptr;
- if (info.wrapped.len)
- {
- // Clean invalidate cache of wrapped part
- CleanInValidateCache((uint32_t*) tu_align((uint32_t) info.wrapped.ptr, 4), info.wrapped.len + 31);
+ xfer->fifo = ff;
- dma_desc[epnum - 1].next_desc = 0;
- dma_desc[epnum - 1].buff_addr = (uint32_t)info.wrapped.ptr;
- dma_desc[epnum - 1].chnl_ctrl =
- udd_dma_ctrl_wrap | (info.wrapped.len << DEVDMACONTROL_BUFF_LENGTH_Pos);
- // Clean cache of wrapped DMA descriptor
- CleanInValidateCache((uint32_t*)&dma_desc[epnum - 1], sizeof(dma_desc_t));
-
- udd_dma_ctrl_lin |= DEVDMASTATUS_DESC_LDST;
- USB_REG->DEVDMA[epnum - 1].DEVDMANXTDSC = (uint32_t)&dma_desc[epnum - 1];
- } else {
- udd_dma_ctrl_lin |= DEVDMACONTROL_END_BUFFIT;
- }
- udd_dma_ctrl_lin |= (info.linear.len << DEVDMACONTROL_BUFF_LENGTH_Pos);
- // Disable IRQs to have a short sequence
- // between read of EOT_STA and DMA enable
- uint32_t irq_state = __get_PRIMASK();
- __disable_irq();
- if (!(USB_REG->DEVDMA[epnum - 1].DEVDMASTATUS & DEVDMASTATUS_END_TR_ST))
- {
- USB_REG->DEVDMA[epnum - 1].DEVDMACONTROL = udd_dma_ctrl_lin;
- USB_REG->DEVIER = DEVIER_DMA_1 << (epnum - 1);
- __set_PRIMASK(irq_state);
- return true;
- }
- __set_PRIMASK(irq_state);
-
- // Here a ZLP has been received
- // and the DMA transfer must be not started.
- // It is the end of transfer
- return false;
- } else
- {
- if (dir == TUSB_DIR_OUT)
- {
- USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RXOUTES;
- } else
- {
- dcd_transmit_packet(xfer,epnum);
- }
+ if (dir == TUSB_DIR_OUT) {
+ USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RXOUTES;
+ } else {
+ dcd_transmit_packet(xfer, epnum);
}
return true;
}
// Stall endpoint
-void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
- uint8_t const epnum = tu_edpt_number(ep_addr);
+void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
+ (void)rhport;
+ const uint8_t epnum = tu_edpt_number(ep_addr);
USB_REG->DEVEPTIER[epnum] = DEVEPTIER_CTRL_STALLRQS;
// Re-enable SETUP interrupt
- if (epnum == 0)
- {
+ if (epnum == 0) {
USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES;
}
}
// clear stall, data toggle is also reset to DATA0
-void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
- uint8_t const epnum = tu_edpt_number(ep_addr);
+void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
+ (void)rhport;
+ const uint8_t epnum = tu_edpt_number(ep_addr);
USB_REG->DEVEPTIDR[epnum] = DEVEPTIDR_CTRL_STALLRQC;
- USB_REG->DEVEPTIER[epnum] = HSTPIPIER_RSTDTS;
+ USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RSTDTS;
}
#endif
diff --git a/src/portable/microchip/samx7x/common_usb_regs.h b/src/portable/microchip/samx7x/samx7x_common.h
index db4a81e0e..5e3c20c0f 100644
--- a/src/portable/microchip/samx7x/common_usb_regs.h
+++ b/src/portable/microchip/samx7x/samx7x_common.h
@@ -1,4 +1,4 @@
- /*
+/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Microchip Technology Inc.
@@ -2098,7 +2098,72 @@ typedef struct
#define FIFO_RAM_ADDR 0xA0100000u
// Errata: The DMA feature is not available for Pipe/Endpoint 7
-#define EP_DMA_SUPPORT(epnum) (epnum >= 1 && epnum <= 6)
+#define EP_DMA_SUPPORT(epnum) (epnum >= 1 && epnum <= 6 && CFG_TUD_SAMX7X_DMA_ENABLE)
+
+//------------- DCache -------------//
+#if CFG_TUD_MEM_DCACHE_ENABLE || CFG_TUH_MEM_DCACHE_ENABLE
+
+typedef struct {
+ uintptr_t start;
+ uintptr_t end;
+} mem_region_t;
+
+// Can be used to define additional uncached regions
+#ifndef CFG_SAMX7X_MEM_UNCACHED_REGIONS
+#define CFG_SAMX7X_MEM_UNCACHED_REGIONS
+#endif
+
+static mem_region_t uncached_regions[] = {
+ // DTCM
+ {.start = 0x20000000, .end = 0x203fffff},
+ CFG_SAMX7X_MEM_UNCACHED_REGIONS
+};
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t round_up_to_cache_line_size(uint32_t size) {
+ if (size & (CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT-1)) {
+ size = (size & ~(CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT-1)) + CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT;
+ }
+ return size;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool is_cache_mem(uintptr_t addr) {
+ if (0 == (SCB->CCR & SCB_CCR_DC_Msk)) {
+ return false; // D-Cache is disabled
+ }
+ for (unsigned int i = 0; i < TU_ARRAY_SIZE(uncached_regions); i++) {
+ if (uncached_regions[i].start <= addr && addr <= uncached_regions[i].end) { return false; }
+ }
+ return true;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool samx7x_dcache_clean(void const* addr, uint32_t data_size) {
+ const uintptr_t addr32 = (uintptr_t) addr;
+ if (is_cache_mem(addr32)) {
+ data_size = round_up_to_cache_line_size(data_size);
+ SCB_CleanDCache_by_Addr((uint32_t *) addr32, (int32_t) data_size);
+ }
+ return true;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool samx7x_dcache_invalidate(void const* addr, uint32_t data_size) {
+ const uintptr_t addr32 = (uintptr_t) addr;
+ if (is_cache_mem(addr32)) {
+ data_size = round_up_to_cache_line_size(data_size);
+ SCB_InvalidateDCache_by_Addr((void*) addr32, (int32_t) data_size);
+ }
+ return true;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool samx7x_dcache_clean_invalidate(void const* addr, uint32_t data_size) {
+ const uintptr_t addr32 = (uintptr_t) addr;
+ if (is_cache_mem(addr32)) {
+ data_size = round_up_to_cache_line_size(data_size);
+ SCB_CleanInvalidateDCache_by_Addr((uint32_t *) addr32, (int32_t) data_size);
+ }
+ return true;
+}
+
+#endif
#else // TODO : SAM3U
diff --git a/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c
new file mode 100644
index 000000000..c9810f51c
--- /dev/null
+++ b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c
@@ -0,0 +1,821 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 HiFiPhile (Zixun LI)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if CFG_TUH_ENABLED && defined(TUP_USBIP_IP3516)
+
+//--------------------------------------------------------------------+
+// INCLUDE
+//--------------------------------------------------------------------+
+#include "common/tusb_common.h"
+#include "host/hcd.h"
+#include "host/usbh.h"
+#include "hcd_lpc_ip3516.h"
+
+#if TU_CHECK_MCU(OPT_MCU_LPC55, OPT_MCU_LPC54)
+ #include "fsl_device_registers.h"
+#else
+ #error "Unsupported MCUs"
+#endif
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+#if TU_CHECK_MCU(OPT_MCU_LPC54)
+ #define ATLPTD ATL_PTD_BASE_ADDR
+ #define INTPTD INT_PTD_BASE_ADDR
+ #define ISOPTD ISO_PTD_BASE_ADDR
+ #define ATLPTDD ATL_PTD_DONE_MAP
+ #define INTPTDD INT_PTD_DONE_MAP
+ #define ISOPTDD ISO_PTD_DONE_MAP
+ #define ATLPTDS ATL_PTD_SKIP_MAP
+ #define INTPTDS INT_PTD_SKIP_MAP
+ #define ISOPTDS ISO_PTD_SKIP_MAP
+ #define DATAPAYLOAD DATA_PAYLOAD_BASE_ADDR
+ #define LASTPTD LAST_PTD_INUSE
+
+ #define USBHSH_ATLPTD_ATL_BASE_MASK USBHSH_ATL_PTD_BASE_ADDR_ATL_BASE_MASK
+ #define USBHSH_INTPTD_INT_BASE_MASK USBHSH_INT_PTD_BASE_ADDR_INT_BASE_MASK
+ #define USBHSH_ISOPTD_ISO_BASE_MASK USBHSH_ISO_PTD_BASE_ADDR_ISO_BASE_MASK
+
+ #define USBHSH_DATAPAYLOAD_DAT_BASE_MASK USBHSH_DATA_PAYLOAD_BASE_ADDR_DAT_BASE_MASK
+
+ #define USBHSH_LASTPTD_ATL_LAST USBHSH_LAST_PTD_INUSE_ATL_LAST
+ #define USBHSH_LASTPTD_INT_LAST USBHSH_LAST_PTD_INUSE_INT_LAST
+ #define USBHSH_LASTPTD_ISO_LAST USBHSH_LAST_PTD_INUSE_ISO_LAST
+#endif
+
+#define USBHSH_PORTSC1_W1C_MASK (USBHSH_PORTSC1_CSC_MASK | USBHSH_PORTSC1_PEDC_MASK | USBHSH_PORTSC1_OCC_MASK)
+
+#define IP3516_PSPD_LOW 0
+#define IP3516_PSPD_FULL 1
+#define IP3516_PSPD_HIGH 2
+
+//--------------------------------------------------------------------+
+// Proprietary Transfer Descriptor
+//--------------------------------------------------------------------+
+
+CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(1024) static ip3516_ptd_t _ptd;
+
+static struct {
+ uint32_t uframe_number;
+ uint32_t uframe_length;
+ bool attached; // Track attachment state to avoid duplicate events, sometimes high-speed disconnection detector is not reliable
+} _hcd_data;
+
+//--------------------------------------------------------------------+
+// Helper Functions
+//--------------------------------------------------------------------+
+
+static inline bool is_ptd_free(const ptd_ctrl1_t ctrl1) {
+ return ctrl1.mps == 0;
+}
+
+static inline bool is_xfer_async(tusb_xfer_type_t xfer_type) {
+ return (xfer_type == TUSB_XFER_CONTROL || xfer_type == TUSB_XFER_BULK);
+}
+
+static inline void ptd_clear_state(ptd_state_t *state) {
+ ptd_state_t local = {.value = 0};
+ local.ep_type = state->ep_type; // preserve ep_type
+ local.token = state->token; // preserve token
+ local.data_toggle = state->data_toggle; // preserve data_toggle
+ *state = local;
+}
+
+static inline uint8_t ptd_find_free(tusb_xfer_type_t xfer_type) {
+ uint8_t max_count;
+ intptr_t ptd_array;
+
+ switch (xfer_type) {
+ case TUSB_XFER_CONTROL:
+ case TUSB_XFER_BULK:
+ max_count = IP3516_ATL_NUM;
+ ptd_array = (intptr_t)&_ptd.atl;
+ break;
+
+ case TUSB_XFER_INTERRUPT:
+ max_count = IP3516_PTL_NUM;
+ ptd_array = (intptr_t)&_ptd.intr;
+ break;
+
+ case TUSB_XFER_ISOCHRONOUS:
+ max_count = IP3516_PTL_NUM;
+ ptd_array = (intptr_t)&_ptd.iso;
+ break;
+
+ default:
+ return TUSB_INDEX_INVALID_8;
+ }
+
+ for (uint8_t i = 0; i < max_count; i++) {
+ // For ATL: stride is sizeof(ip3516_atl_t) = 16 bytes = 4 words
+ // For PTL: stride is sizeof(ip3516_ptl_t) = 32 bytes = 8 words
+ uint8_t stride = is_xfer_async(xfer_type) ? sizeof(ip3516_atl_t) : sizeof(ip3516_ptl_t);
+ ptd_ctrl1_t *ctrl1 = (ptd_ctrl1_t *)(ptd_array + i * stride);
+
+ if (is_ptd_free(*ctrl1)) {
+ return i;
+ }
+ }
+
+ return TUSB_INDEX_INVALID_8; // No free PTD found
+}
+
+// Close all PTDs associated with a specific device address
+static void close_ptds_by_device(uint8_t dev_addr, intptr_t ptd_array, uint8_t max_count, uint8_t stride,
+ volatile uint32_t *skip_reg) {
+
+ uint32_t skip_mask = 0;
+
+ for (uint8_t i = 0; i < max_count; i++) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+
+ if (!is_ptd_free(*ptd_ctrl1) && ptd_ctrl2->dev_addr == dev_addr) {
+ *skip_reg |= (1 << i);
+ skip_mask |= (1 << i);
+ }
+ }
+
+ if (skip_mask) {
+ // Wait 1 uframe for PTDs to be inactive (with timeout)
+ uint32_t start_uframe =
+ (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
+ uint32_t timeout = 10000;
+ while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) ==
+ start_uframe && timeout > 0) {
+ timeout--;
+ }
+
+ // Clear PTDs
+ for (uint8_t i = 0; i < max_count; i++) {
+ if (skip_mask & (1 << i)) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ tu_memclr((void *)ptd_ptr, stride);
+ }
+ }
+
+ // Clear skip bits
+ *skip_reg &= ~skip_mask;
+ }
+}
+
+// Check if a PTD matches the given endpoint criteria
+static bool ptd_matches(intptr_t ptd_ptr, uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) {
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ if (is_ptd_free(*ptd_ctrl1)) {
+ return false;
+ }
+
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ if (ptd_ctrl2->dev_addr != dev_addr || ptd_ctrl2->ep_num != ep_num) {
+ return false;
+ }
+
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+ bool is_control = (ptd_state->ep_type == TUSB_XFER_CONTROL);
+
+ // For control endpoint, match both IN and OUT directions
+ if (is_control) {
+ return true;
+ }
+
+ if (ep_dir == TUSB_DIR_IN && ptd_state->token == IP3516_PTD_TOKEN_IN) {
+ return true;
+ }
+
+ if (ep_dir == TUSB_DIR_OUT && ptd_state->token == IP3516_PTD_TOKEN_OUT) {
+ return true;
+ }
+
+ return false;
+}
+
+// Find and close a specific PTD
+static bool find_and_close_ptd(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir, intptr_t ptd_array, uint8_t max_count,
+ uint8_t stride, volatile uint32_t *skip_reg) {
+ for (uint8_t i = 0; i < max_count; i++) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ if (skip_reg) {
+ *skip_reg |= (1 << i);
+
+ // Wait 1 uframe for PTD to be inactive (with timeout)
+ uint32_t start_uframe =
+ (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
+ uint32_t timeout = 10000;
+ while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) ==
+ start_uframe && timeout > 0) {
+ timeout--;
+ }
+
+ // Just clear state
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+ ptd_clear_state(ptd_state);
+ ptd_ctrl1->valid = 0;
+
+ *skip_reg &= ~(1 << i);
+ } else {
+ // Clear PTD
+ tu_memclr((void *)ptd_ptr, stride);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+// Find an opened PTD
+static intptr_t find_opened_ptd(uint8_t dev_addr, uint8_t ep_addr) {
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ // Search in ATL
+ for (uint8_t i = 0; i < IP3516_ATL_NUM; i++) {
+ intptr_t ptd_ptr = (intptr_t)&_ptd.atl[i];
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ return ptd_ptr;
+ }
+ }
+
+ // Search in INT
+ for (uint8_t i = 0; i < IP3516_PTL_NUM; i++) {
+ intptr_t ptd_ptr = (intptr_t)&_ptd.intr[i];
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ return ptd_ptr;
+ }
+ }
+
+ // Search in ISO
+ for (uint8_t i = 0; i < IP3516_PTL_NUM; i++) {
+ intptr_t ptd_ptr = (intptr_t)&_ptd.iso[i];
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ return ptd_ptr;
+ }
+ }
+
+ return 0;
+}
+
+static bool edpt_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen, bool is_setup) {
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ intptr_t ptd_ptr = find_opened_ptd(dev_addr, ep_addr);
+ TU_ASSERT(ptd_ptr != 0);
+
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ ptd_data_t *ptd_data = (ptd_data_t *)(ptd_ptr + offsetof(ip3516_atl_t, data));
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+
+ // Setup data buffer and length
+ ptd_data->data_addr = (uint32_t)(uintptr_t)buffer & IP3516_PTD_DATA_ADDR_MASK;
+ ptd_data->xfer_len = buflen;
+
+ // Clear previous state
+ ptd_clear_state(ptd_state);
+
+ // Set token for EP0
+ if (ep_num == 0) {
+ if (is_setup) {
+ ptd_state->token = IP3516_PTD_TOKEN_SETUP;
+ ptd_state->data_toggle = 0;
+ } else {
+ ptd_state->token = (ep_dir == TUSB_DIR_IN) ? IP3516_PTD_TOKEN_IN : IP3516_PTD_TOKEN_OUT;
+ ptd_state->data_toggle = 1;
+ }
+ }
+
+ // Interrupt split transfer needs to be relaunched manually if NAKed
+ if (ptd_ctrl2->split && ptd_state->ep_type == TUSB_XFER_INTERRUPT) {
+ ptd_ctrl2->reload = 0x0f;
+ ptd_state->nak_cnt = 0x0f;
+ }
+
+ // Activate PTD
+ ptd_ctrl1->valid = 1;
+ ptd_state->active = 1;
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Controller API
+//--------------------------------------------------------------------+
+
+// Initialize controller to host mode
+bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rh_init;
+ (void)rhport;
+
+ // Reset controller
+ USBHSH->USBCMD |= USBHSH_USBCMD_HCRESET_MASK;
+ while (USBHSH->USBCMD & USBHSH_USBCMD_HCRESET_MASK) {}
+
+ USBHSH->PORTMODE = USBHSH_PORTMODE_SW_CTRL_PDCOM_MASK;
+
+ tu_memclr(&_ptd, sizeof(_ptd));
+ tu_varclr(&_hcd_data);
+
+ // Set base addresses
+ USBHSH->ATLPTD = (uint32_t)&_ptd.atl & USBHSH_ATLPTD_ATL_BASE_MASK;
+ USBHSH->INTPTD = (uint32_t)&_ptd.intr & USBHSH_INTPTD_INT_BASE_MASK;
+ USBHSH->ISOPTD = (uint32_t)&_ptd.iso & USBHSH_ISOPTD_ISO_BASE_MASK;
+ USBHSH->DATAPAYLOAD = (uint32_t)&_ptd & USBHSH_DATAPAYLOAD_DAT_BASE_MASK;
+
+ // Turn on power switch
+ if (USBHSH->HCSPARAMS & USBHSH_HCSPARAMS_PPC_MASK) {
+ USBHSH->PORTSC1 |= USBHSH_PORTSC1_PP_MASK;
+ }
+
+ // Get frame list size
+ uint32_t fls = (USBHSH->USBCMD & USBHSH_USBCMD_FLS_MASK) >> USBHSH_USBCMD_FLS_SHIFT;
+ _hcd_data.uframe_length = 8192 >> fls;
+
+ // Clear pending interrupts
+ USBHSH->USBSTS = 0xFFFFFFFF;
+
+ // Enable interrupts
+ USBHSH->USBINTR = USBHSH_USBINTR_ATL_IRQ_E_MASK | USBHSH_USBINTR_INT_IRQ_E_MASK | USBHSH_USBINTR_ISO_IRQ_E_MASK |
+ USBHSH_USBINTR_PCDE_MASK | USBHSH_USBINTR_FLRE_MASK;
+
+
+ // Enable all PTDs
+ USBHSH->LASTPTD = USBHSH_LASTPTD_ATL_LAST(IP3516_ATL_NUM - 1) | USBHSH_LASTPTD_INT_LAST(IP3516_PTL_NUM - 1) |
+ USBHSH_LASTPTD_ISO_LAST(IP3516_PTL_NUM - 1);
+
+ // Enable controller
+ USBHSH->USBCMD = USBHSH_USBCMD_ATL_EN_MASK | USBHSH_USBCMD_INT_EN_MASK | USBHSH_USBCMD_ISO_EN_MASK | USBHSH_USBCMD_RS_MASK;
+
+ return true;
+}
+
+// Enable USB interrupt
+void hcd_int_enable(uint8_t rhport) {
+ (void)rhport;
+ NVIC_EnableIRQ(USB1_IRQn);
+}
+
+// Disable USB interrupt
+void hcd_int_disable(uint8_t rhport) {
+ (void)rhport;
+ NVIC_DisableIRQ(USB1_IRQn);
+}
+
+bool hcd_deinit(uint8_t rhport) {
+ (void)rhport;
+
+ // Disable interrupts
+ USBHSH->USBINTR = 0;
+ USBHSH->USBSTS = 0xFFFFFFFF;
+
+ // Disable controller
+ USBHSH->USBCMD &= ~(USBHSH_USBCMD_ATL_EN_MASK | USBHSH_USBCMD_INT_EN_MASK | USBHSH_USBCMD_ISO_EN_MASK | USBHSH_USBCMD_RS_MASK);
+
+ // Turn off power switch
+ if (USBHSH->HCSPARAMS & USBHSH_HCSPARAMS_PPC_MASK) {
+ USBHSH->PORTSC1 &= ~USBHSH_PORTSC1_PP_MASK;
+ }
+
+ // Connect PHY to device mode
+ USBHSH->PORTMODE = USBHSH_PORTMODE_SW_CTRL_PDCOM_MASK | USBHSH_PORTMODE_DEV_ENABLE_MASK;
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Port API
+//--------------------------------------------------------------------+
+
+// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete.
+// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence.
+void hcd_port_reset(uint8_t rhport) {
+ (void)rhport;
+ uint32_t status = USBHSH->PORTSC1 & ~USBHSH_PORTSC1_W1C_MASK;
+ USBHSH->PORTSC1 = status | USBHSH_PORTSC1_PR_MASK;
+}
+
+// Complete bus reset sequence, may be required by some controllers
+void hcd_port_reset_end(uint8_t rhport) {
+ (void)rhport;
+ uint32_t status = USBHSH->PORTSC1 & ~USBHSH_PORTSC1_W1C_MASK;
+ USBHSH->PORTSC1 = status & ~USBHSH_PORTSC1_PR_MASK;
+ while (USBHSH->PORTSC1 & USBHSH_PORTSC1_PR_MASK) {}
+#if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U))
+ uint32_t pspd = (USBHSH->PORTSC1 & USBHSH_PORTSC1_PSPD_MASK) >> USBHSH_PORTSC1_PSPD_SHIFT;
+ if (pspd == IP3516_PSPD_HIGH) {
+ // enable phy disconnection for high speed
+ USBPHY->CTRL |= USBPHY_CTRL_ENHOSTDISCONDETECT_MASK;
+ }
+#endif
+}
+
+// Get the current connect status of roothub port
+bool hcd_port_connect_status(uint8_t rhport) {
+ (void)rhport;
+ return (USBHSH->PORTSC1 & USBHSH_PORTSC1_CCS_MASK) ? true : false;
+}
+
+// Get port link speed
+tusb_speed_t hcd_port_speed_get(uint8_t rhport) {
+ (void)rhport;
+ uint32_t pspd = (USBHSH->PORTSC1 & USBHSH_PORTSC1_PSPD_MASK) >> USBHSH_PORTSC1_PSPD_SHIFT;
+ switch (pspd) {
+ case IP3516_PSPD_LOW:
+ return TUSB_SPEED_LOW;
+ case IP3516_PSPD_FULL:
+ return TUSB_SPEED_FULL;
+ case IP3516_PSPD_HIGH:
+ return TUSB_SPEED_HIGH;
+ default:
+ return TUSB_SPEED_INVALID;
+ }
+}
+
+// Get frame number (1ms)
+uint32_t hcd_frame_number(uint8_t rhport) {
+ (void)rhport;
+ uint32_t uframe = (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
+ uframe &= (_hcd_data.uframe_length - 1);
+ return (uframe + _hcd_data.uframe_number) >> 3;
+}
+
+// HCD closes all opened endpoints belong to this device
+void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
+ (void)rhport;
+
+ close_ptds_by_device(dev_addr, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t), &USBHSH->ATLPTDS);
+ close_ptds_by_device(dev_addr, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), &USBHSH->INTPTDS);
+ close_ptds_by_device(dev_addr, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), &USBHSH->ISOPTDS);
+}
+
+//--------------------------------------------------------------------+
+// Endpoints API
+//--------------------------------------------------------------------+
+
+static inline intptr_t get_ptd_from_index(tusb_xfer_type_t xfer_type, uint8_t ptd_index) {
+ if (is_xfer_async(xfer_type)) {
+ return (intptr_t)&_ptd.atl[ptd_index];
+ } else {
+ if (xfer_type == TUSB_XFER_INTERRUPT) {
+ return (intptr_t)&_ptd.intr[ptd_index];
+ } else {
+ return (intptr_t)&_ptd.iso[ptd_index];
+ }
+ }
+}
+
+
+// Open an endpoint
+bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) {
+ (void)rhport;
+
+ const uint8_t ep_num = tu_edpt_number(ep_desc->bEndpointAddress);
+ const tusb_xfer_type_t xfer_type = (tusb_xfer_type_t)ep_desc->bmAttributes.xfer;
+
+ tuh_bus_info_t bus_info;
+ tuh_bus_info_get(dev_addr, &bus_info);
+
+ // Find a free PTD
+ uint8_t ptd_index = ptd_find_free(xfer_type);
+ TU_ASSERT(ptd_index != TUSB_INDEX_INVALID_8);
+
+ // Configure PTD
+ intptr_t ptd_ptr = get_ptd_from_index(xfer_type, ptd_index);
+ volatile ptd_ctrl1_t *ctrl1 = (volatile ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ volatile ptd_ctrl2_t *ctrl2 = (volatile ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ volatile ptd_data_t *data = (volatile ptd_data_t *)(ptd_ptr + offsetof(ip3516_atl_t, data));
+ volatile ptd_state_t *state = (volatile ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+
+ // Initialize PTD fields
+ ctrl1->mps = ep_desc->wMaxPacketSize;
+ ctrl1->mult = 1;
+
+ ctrl2->dev_addr = dev_addr;
+ ctrl2->ep_num = ep_num;
+ ctrl2->speed = bus_info.speed == TUSB_SPEED_LOW ? 2 : 0;
+ ctrl2->hub_addr = bus_info.hub_addr;
+ ctrl2->hub_port = bus_info.hub_port;
+ ctrl2->split = (hcd_port_speed_get(rhport) == TUSB_SPEED_HIGH) && (bus_info.speed != TUSB_SPEED_HIGH) ? 1 : 0;
+
+ data->intr = 1;
+
+ state->ep_type = (uint32_t)xfer_type;
+ state->token = tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN ? IP3516_PTD_TOKEN_IN : IP3516_PTD_TOKEN_OUT;
+
+ if (!is_xfer_async(xfer_type)) {
+ ip3516_ptl_t *ptd = (ip3516_ptl_t *)ptd_ptr;
+
+ uint32_t uframe_interval;
+ if (bus_info.speed == TUSB_SPEED_HIGH) {
+ uframe_interval = 1 << (ep_desc->bInterval - 1);
+ } else {
+ uframe_interval = ep_desc->bInterval << 3;
+ // round down to nearest power of 2
+ uframe_interval = 1 << tu_log2(uframe_interval);
+ }
+ uframe_interval = tu_min32(uframe_interval, IP3516_MAX_UFRAME);
+
+ // uframe_active is an 8-bit mask, where each bit corresponds to a micro-frame within a 1ms frame.
+ // A '1' indicates the endpoint should be polled in that micro-frame.
+ // For example:
+ // Interval 1 (poll every u-frame) -> mask is 0b11111111 (0xFF)
+ // Interval 2 (poll every 2nd u-frame, e.g., 0, 2, 4, 6) -> mask is 0b10101010 (0xAA)
+ // Interval 4 (poll every 4th u-frame, e.g., 0, 4) -> mask is 0b10001000 (0x88)
+ // Interval 8 (poll every 8th u-frame, e.g., 0) -> mask is 0b10000000 (0x80)
+ switch (uframe_interval) {
+ case 1:
+ ptd->status.uframe_active = 0xFF;
+ break;
+ case 2:
+ ptd->status.uframe_active = 0xAA;
+ break;
+ case 4:
+ ptd->status.uframe_active = 0x11;
+ break;
+ case 8:
+ ptd->status.uframe_active = 0x01;
+ break;
+ default:
+ // For intervals > 8, we poll once per frame (every 8 u-frames) and use ctrl1.uframe to skip frames.
+ ptd->status.uframe_active = 0x01;
+ if (uframe_interval >= 16) {
+ ctrl1->uframe = tu_log2(uframe_interval) - 3;
+ }
+ break;
+ }
+
+ if (ctrl2->split) {
+ // 11.18.1 Best Case Full-Speed Budget
+ //
+ // A microframe of time allows at most 187.5 raw bytes of signaling on a full-speed bus.
+ // The best case full-speed budget assumes that 188 full-speed bytes occur in each microframe.
+ //
+ // A 1 ms frame subdivided into microframes of budget time:
+ //
+ // Microframes Y_0 Y_1 Y_2 Y_3 Y_4 Y_5 Y_6 Y_7
+ // Max wire time 187.5 187.5 187.5 187.5 187.5 187.5 32
+ // Best case wire budget 188 188 188 188 188 188 29
+ //
+ // 11.18.4 Host Split Transaction Scheduling Requirements
+ //
+ // 1. The host must never schedule a start-split in microframe Y_6.
+ // 2. For isochronous OUT full-speed transactions, for each microframe in which the transaction is
+ // budgeted, the host must schedule a 188 (or the remaining data size) data byte start-split transaction.
+ // For isochronous IN and interrupt IN/OUT full-/low-speed transactions, a single start-split must be
+ // scheduled in the microframe before the transaction is budgeted to start on the full-/low-speed bus.
+ // 3. For isochronous OUT full-speed transactions, the host must never schedule a complete-split. The
+ // TT response to a complete-split for an isochronous OUT is undefined.
+ // For interrupt IN/OUT full-/low-speed transactions, the host must schedule a complete-split
+ // transaction in each of the two microframes following the first microframe in which the full-/low-
+ // speed transaction is budgeted. An additional complete-split must also be scheduled in the third
+ // following microframe unless the full-/low-speed transaction was budgeted to start in microframe Y_6
+ // For isochronous IN full-speed transactions, for each microframe in which the full-speed transaction
+ // is budgeted, a complete-split must be scheduled for each following microframe.
+ // Also, determine the last microframe in which a complete-split is scheduled, call it L.
+ // If L is less than Y_6, schedule additional complete-splits in microframe L+1 and L+2.
+ // If L is equal to Y_6, schedule one complete-split in microframe Y_7.
+ //
+ // TODO: Implement budget check scheduling
+ // Otherwise, it may cause bus contention with other split transfers
+ // Here we simply start interrupt transfers for Y_0 and Y1 and isochronous transfers for Y_2
+ if (xfer_type == TUSB_XFER_ISOCHRONOUS) {
+ const uint8_t ss_slot = 2; // Start-split slot
+ const uint8_t slots = (ep_desc->wMaxPacketSize + 187) / 188;
+ const tusb_dir_t ep_dir = tu_edpt_dir(ep_desc->bEndpointAddress);
+ if (ep_dir == TUSB_DIR_IN) {
+ if (ep_desc->wMaxPacketSize > 192) {
+ ctrl1->mps = 192;
+ }
+ ptd->status.uframe_active = 1 << ss_slot;
+ for (uint8_t i = 0; i < slots; i++) {
+ ptd->iso_in_0.uframe_complete |= 1 << (2 + ss_slot + i);
+ }
+ // Schedule additional complete-splits if needed
+ uint8_t last_complete = ss_slot + slots + 1;
+ if (last_complete < 6) {
+ ptd->iso_in_0.uframe_complete |= 1 << (ss_slot + last_complete + 1);
+ ptd->iso_in_0.uframe_complete |= 1 << (ss_slot + last_complete + 2);
+ } else if (last_complete == 6) {
+ ptd->iso_in_0.uframe_complete |= 1 << 7;
+ }
+ } else {
+ if (ep_desc->wMaxPacketSize > 188) {
+ ctrl1->mps = 188;
+ }
+ for (uint8_t i = 0; i < slots; i++) {
+ ptd->status.uframe_active |= 1 << (ss_slot + i);
+ }
+ }
+ } else {
+ // Start-split slot, jigging to avoid bus contention: EP odd -> Y_1, EP even -> Y_0
+ const uint8_t ss_slot = ep_num & 0x01;
+ ptd->status.uframe_active = 1 << ss_slot;
+ // Complete-split slots: next 3 u-frames
+ ptd->iso_in_0.uframe_complete = 0x1c << ss_slot;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Close an opened endpoint
+bool hcd_edpt_close(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ // Search in ATL
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t), NULL)) {
+ return true;
+ }
+
+ // Search in INT
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), NULL)) {
+ return true;
+ }
+
+ // Search in ISO
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), NULL)) {
+ return true;
+ }
+
+ return false;
+}
+
+// Submit a transfer on an endpoint
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
+ (void)rhport;
+
+ return edpt_xfer(dev_addr, ep_addr, buffer, buflen, false);
+}
+
+// Abort a queued transfer. Note: it can only abort transfer that has not been started
+// Return true if a queued transfer is aborted, false if there is no transfer to abort
+bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ // Search in ATL
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t),
+ &USBHSH->ATLPTDS)) {
+ return true;
+ }
+
+ // Search in INT
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t),
+ &USBHSH->INTPTDS)) {
+ return true;
+ }
+
+ // Search in ISO
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t),
+ &USBHSH->ISOPTDS)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
+ (void)rhport;
+
+ return edpt_xfer(dev_addr, 0x00, (uint8_t *)(uintptr_t)setup_packet, 8, true);
+}
+
+bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+
+ intptr_t ptd_ptr = find_opened_ptd(dev_addr, ep_addr);
+ TU_ASSERT(ptd_ptr != 0);
+
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+ ptd_clear_state(ptd_state);
+ ptd_state->data_toggle = 0; // reset data toggle to DATA0
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Interrupt Handler
+//--------------------------------------------------------------------+
+
+// Handle port status change event
+static inline void handle_port_status_change(uint8_t rhport) {
+ const uint32_t status = USBHSH->PORTSC1;
+
+ if (status & USBHSH_PORTSC1_CSC_MASK) {
+ if (status & USBHSH_PORTSC1_CCS_MASK && !_hcd_data.attached) {
+ _hcd_data.attached = true;
+ hcd_event_device_attach(rhport, true);
+ } else {
+ _hcd_data.attached = false;
+ hcd_event_device_remove(rhport, true);
+ #if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U))
+ // disable phy disconnection for high speed
+ USBPHY->CTRL &= ~USBPHY_CTRL_ENHOSTDISCONDETECT_MASK;
+ #endif
+ }
+ }
+
+ USBHSH->PORTSC1 |= status & USBHSH_PORTSC1_W1C_MASK;
+}
+
+// Handle PTD done interrupt
+static inline void handle_ptd_done(uint32_t done_status, intptr_t ptd_array, bool is_async) {
+ uint8_t max_count = is_async ? IP3516_ATL_NUM : IP3516_PTL_NUM;
+ uint8_t stride = is_async ? sizeof(ip3516_atl_t) : sizeof(ip3516_ptl_t);
+
+ for (uint8_t i = 0; i < max_count; i++) {
+ if (done_status & (1 << i)) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+
+ xfer_result_t result;
+ if (ptd_state->halt) {
+ result = XFER_RESULT_STALLED;
+ } else if (ptd_state->error || ptd_state->babble) {
+ result = XFER_RESULT_FAILED;
+ } else {
+ result = XFER_RESULT_SUCCESS;
+ }
+
+ uint8_t ep_addr = ptd_ctrl2->ep_num | (ptd_state->token == IP3516_PTD_TOKEN_IN ? 0x80 : 0x00);
+
+ hcd_event_xfer_complete(ptd_ctrl2->dev_addr, ep_addr, ptd_state->xferred_len, result, true);
+ }
+ }
+}
+
+void hcd_int_handler(uint8_t rhport, bool in_isr) {
+ (void)in_isr;
+
+ uint32_t int_status = USBHSH->USBSTS;
+ USBHSH->USBSTS = int_status; // clear interrupt status
+
+ // Port Change Detect
+ if (int_status & USBHSH_USBSTS_PCD_MASK) {
+ handle_port_status_change(rhport);
+ }
+
+ // Frame List Rollover
+ if (int_status & USBHSH_USBSTS_FLR_MASK) {
+ _hcd_data.uframe_number += _hcd_data.uframe_length;
+ }
+
+ // ATL done
+ if (int_status & USBHSH_USBSTS_ATL_IRQ_MASK) {
+ uint32_t done_status = USBHSH->ATLPTDD;
+ handle_ptd_done(done_status, (intptr_t)&_ptd.atl, true);
+ USBHSH->ATLPTDD = done_status;
+ }
+
+ // INT done
+ if (int_status & USBHSH_USBSTS_INT_IRQ_MASK) {
+ uint32_t done_status = USBHSH->INTPTDD;
+ handle_ptd_done(done_status, (intptr_t)&_ptd.intr, false);
+ USBHSH->INTPTDD = done_status;
+ }
+
+ // ISO done
+ if (int_status & USBHSH_USBSTS_ISO_IRQ_MASK) {
+ uint32_t done_status = USBHSH->ISOPTDD;
+ handle_ptd_done(done_status, (intptr_t)&_ptd.iso, false);
+ USBHSH->ISOPTDD = done_status;
+ }
+}
+
+#endif
diff --git a/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h
new file mode 100644
index 000000000..5214c18ae
--- /dev/null
+++ b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h
@@ -0,0 +1,188 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 HiFiPhile (Zixun LI)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_HCD_IP3516_H_
+#define TUSB_HCD_IP3516_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// IP3516 CONFIGURATION & CONSTANTS
+//--------------------------------------------------------------------+
+
+#define IP3516_ATL_NUM 32
+#define IP3516_PTL_NUM 32
+
+#define IP3516_PTD_TOKEN_OUT 0x00U
+#define IP3516_PTD_TOKEN_IN 0x01U
+#define IP3516_PTD_TOKEN_SETUP 0x02U
+
+#define IP3516_PTD_EPTYPE_OUT 0x00U
+#define IP3516_PTD_EPTYPE_IN 0x01U
+#define IP3516_PTD_EPTYPE_SETUP 0x02U
+
+#define IP3516_PTD_MAX_TRANSFER_LENGTH 0x7FFFU
+
+#define IP3516_PTD_DATA_ADDR_MASK 0xFFFFU
+
+#define IP3516_MAX_UFRAME (1UL << 8)
+
+#define IP3516_PERIODIC_TRANSFER_GAP (3U)
+#define IP3516_ISO_MULTIPLE_TRANSFER (8U)
+
+
+//--------------------------------------------------------------------+
+// IP3516 PTD Data Structure
+//--------------------------------------------------------------------+
+
+// Control Word 1
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t valid : 1;
+ uint32_t next_ptd : 5;
+ uint32_t : 1;
+ uint32_t jump : 1;
+ uint32_t uframe : 8;
+ uint32_t mps : 11;
+ uint32_t : 1;
+ uint32_t mult : 2;
+ uint32_t : 2;
+ };
+} ptd_ctrl1_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_ctrl1_t) == 4, "size is not correct");
+
+// Control Word 2
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t ep_num : 4;
+ uint32_t dev_addr : 7;
+ uint32_t split : 1;
+ uint32_t reload : 4;
+ uint32_t speed : 2;
+ uint32_t hub_port : 7;
+ uint32_t hub_addr : 7;
+ };
+} ptd_ctrl2_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_ctrl2_t) == 4, "size is not correct");
+
+// Data Word
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t xfer_len : 15;
+ uint32_t intr : 1;
+ uint32_t data_addr : 16;
+ };
+} ptd_data_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_data_t) == 4, "size is not correct");
+
+// State Word
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t xferred_len : 15;
+ uint32_t token : 2;
+ uint32_t ep_type : 2;
+ uint32_t nak_cnt : 4;
+ uint32_t err_cnt : 2;
+ uint32_t data_toggle : 1;
+ uint32_t ping : 1;
+ uint32_t start_complete : 1;
+ uint32_t error : 1;
+ uint32_t babble : 1;
+ uint32_t halt : 1;
+ uint32_t active : 1;
+ };
+} ptd_state_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_state_t) == 4, "size is not correct");
+
+// Status Word
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t uframe_active : 8;
+ uint32_t iso_status0 : 3;
+ uint32_t iso_status1 : 3;
+ uint32_t iso_status2 : 3;
+ uint32_t iso_status3 : 3;
+ uint32_t iso_status4 : 3;
+ uint32_t iso_status5 : 3;
+ uint32_t iso_status6 : 3;
+ uint32_t iso_status7 : 3;
+ };
+} ptd_status_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_status_t) == 4, "size is not correct");
+
+// ATL (Asynchronous Transfer List) structure
+typedef volatile struct {
+ ptd_ctrl1_t ctrl1;
+ ptd_ctrl2_t ctrl2;
+ ptd_data_t data;
+ ptd_state_t state;
+} ip3516_atl_t;
+
+TU_VERIFY_STATIC(sizeof(ip3516_atl_t) == 16, "size is not correct");
+
+// PTL (Periodic Transfer List) structure
+typedef volatile struct {
+ ptd_ctrl1_t ctrl1;
+ ptd_ctrl2_t ctrl2;
+ ptd_data_t data;
+ ptd_state_t state;
+ ptd_status_t status;
+ union {
+ uint32_t value;
+ struct {
+ uint32_t uframe_complete : 8;
+ uint32_t spl_iso_in_0 : 24;
+ };
+ } iso_in_0;
+ uint32_t iso_in_1;
+ uint32_t iso_in_2;
+} ip3516_ptl_t;
+
+TU_VERIFY_STATIC(sizeof(ip3516_ptl_t) == 32, "size is not correct");
+
+// Proprietary Transfer Descriptor
+typedef struct {
+ ip3516_ptl_t intr[IP3516_PTL_NUM];
+ ip3516_ptl_t iso[IP3516_PTL_NUM];
+ ip3516_atl_t atl[IP3516_ATL_NUM];
+} ip3516_ptd_t;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* TUSB_HCD_IP3516_H_ */
diff --git a/src/portable/ohci/ohci.c b/src/portable/ohci/ohci.c
index 5ca093506..4b248d9e6 100644
--- a/src/portable/ohci/ohci.c
+++ b/src/portable/ohci/ohci.c
@@ -243,7 +243,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
OHCI_CONTROL_LIST_BULK_ENABLE_MASK | OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK; // TODO Isochronous
OHCI_REG->frame_interval = (OHCI_FMINTERVAL_FSMPS << 16) | OHCI_FMINTERVAL_FI;
- OHCI_REG->frame_interval ^= (1 << 31); //Must toggle when frame_interval is updated.
+ OHCI_REG->frame_interval ^= (1ul << 31); //Must toggle when frame_interval is updated.
OHCI_REG->periodic_start = (OHCI_FMINTERVAL_FI * 9) / 10; // Periodic start is 90% of frame interval
OHCI_REG->control_bit.hc_functional_state = OHCI_CONTROL_FUNCSTATE_OPERATIONAL; // make HC's state to operational state TODO use this to suspend (save power)
@@ -557,6 +557,7 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
(void) rhport;
ohci_ed_t * const p_ed = ed_from_addr(dev_addr, ep_addr);
+ TU_ASSERT(p_ed);
ohci_ed_word2_t td_head = p_ed->td_head;
td_head.toggle = 0; // reset data toggle
diff --git a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
index acdeccf6d..1813ef70b 100644
--- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
@@ -223,9 +223,13 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
tu_memclr(&_hcd_data, sizeof(_hcd_data));
+ // Clear pending interrupts
+ // Normally no interrupts should be pending here since we just reset the core,
+ // but device mode suspend needs to cleared by WKUP flag
+ FSDEV_REG->ISTR = 0;
+
// Enable interrupts for host mode
- FSDEV_REG->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_SUSPM |
- USB_CNTR_WKUPM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM;
+ FSDEV_REG->CNTR |= USB_CNTR_DCON | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM;
// Initialize port state
_hcd_data.connected = false;
diff --git a/src/portable/sunxi/dcd_sunxi_musb.c b/src/portable/sunxi/dcd_sunxi_musb.c
index b413121a5..9fac0bc1c 100644
--- a/src/portable/sunxi/dcd_sunxi_musb.c
+++ b/src/portable/sunxi/dcd_sunxi_musb.c
@@ -36,9 +36,6 @@
#include <device/dcd.h>
#include "musb_def.h"
-//#include "bsp/board_api.h"
-extern uint32_t board_millis(void); // TODO remove
-
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 2e2b050bc..8685ec6dc 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -492,6 +492,12 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
return true;
}
+bool dcd_deinit(uint8_t rhport) {
+ dcd_disconnect(rhport);
+ dwc2_core_deinit(rhport);
+ return true;
+}
+
void dcd_int_enable(uint8_t rhport) {
dwc2_dcd_int_enable(rhport);
}
@@ -527,34 +533,38 @@ void dcd_remote_wakeup(uint8_t rhport) {
}
void dcd_connect(uint8_t rhport) {
- (void) rhport;
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
#ifdef TUP_USBIP_DWC2_ESP32
- usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
- conf.pad_pull_override = 0;
- conf.dp_pullup = 0;
- conf.dp_pulldown = 0;
- conf.dm_pullup = 0;
- conf.dm_pulldown = 0;
- USB_WRAP.otg_conf = conf;
+ // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY
+ if (rhport == 0) {
+ usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
+ conf.pad_pull_override = 0;
+ conf.dp_pullup = 0;
+ conf.dp_pulldown = 0;
+ conf.dm_pullup = 0;
+ conf.dm_pulldown = 0;
+ USB_WRAP.otg_conf = conf;
+ }
#endif
dwc2->dctl &= ~DCTL_SDIS;
}
void dcd_disconnect(uint8_t rhport) {
- (void) rhport;
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
#ifdef TUP_USBIP_DWC2_ESP32
- usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
- conf.pad_pull_override = 1;
- conf.dp_pullup = 0;
- conf.dp_pulldown = 1;
- conf.dm_pullup = 0;
- conf.dm_pulldown = 1;
- USB_WRAP.otg_conf = conf;
+ // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY
+ if (rhport == 0) {
+ usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
+ conf.pad_pull_override = 1;
+ conf.dp_pullup = 0;
+ conf.dp_pulldown = 1;
+ conf.dm_pullup = 0;
+ conf.dm_pulldown = 1;
+ USB_WRAP.otg_conf = conf;
+ }
#endif
dwc2->dctl |= DCTL_SDIS;
diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h
index 10824ae92..fa6d10c12 100644
--- a/src/portable/synopsys/dwc2/dwc2_at32.h
+++ b/src/portable/synopsys/dwc2/dwc2_at32.h
@@ -112,6 +112,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_
}
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
+ (void) hs_phy_type;
+ dwc2->stm32_gccfg &= ~(STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN);
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void) dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_bcm.h b/src/portable/synopsys/dwc2/dwc2_bcm.h
index df6d4a852..00842bba2 100644
--- a/src/portable/synopsys/dwc2/dwc2_bcm.h
+++ b/src/portable/synopsys/dwc2/dwc2_bcm.h
@@ -73,6 +73,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
// nothing to do
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_common.c b/src/portable/synopsys/dwc2/dwc2_common.c
index 27dda44ee..33eabaeab 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.c
+++ b/src/portable/synopsys/dwc2/dwc2_common.c
@@ -39,14 +39,11 @@ static void reset_core(dwc2_regs_t* dwc2) {
while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {
}
- // load gsnpsid (it is not readable after reset is asserted)
- const uint32_t gsnpsid = dwc2->gsnpsid;
-
- // reset core
- dwc2->grstctl |= GRSTCTL_CSRST;
+ const uint32_t gsnpsid = dwc2->gsnpsid; // preload gsnpsid which is not readable while resetting
+ dwc2->grstctl |= GRSTCTL_CSRST; // reset core
if ((gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) {
- // prior v4.20a: CSRST is self-clearing and the core clears this bit after all the necessary logic is reset in
+ // prior v4.20a: CSRST is self-clearing, and the core clears this bit after all the necessary logic is reset in
// the core, which can take several clocks, depending on the current state of the core. Once this bit has been
// cleared, the software must wait at least 3 PHY clocks before accessing the PHY domain (synchronization delay).
while (dwc2->grstctl & GRSTCTL_CSRST) {}
@@ -88,8 +85,7 @@ static void phy_fs_init(dwc2_regs_t* dwc2) {
}
/* dwc2 has 2 highspeed PHYs options
- * - UTMI+ is internal highspeed PHY, can be clocked at 30/60 Mhz for fullspeed or 60 Mhz for highspeed. Can be either
- * 8 or 16-bit interface.
+ * - UTMI+ is internal highspeed PHY, can be clocked at 30 Mhz (8-bit) or 60 Mhz (16-bit).
* - ULPI is external highspeed PHY, clocked at 60Mhz with 8-bit interface.
*
* In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz
@@ -247,6 +243,24 @@ bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma) {
return true;
}
+void dwc2_core_deinit(uint8_t rhport) {
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+
+ // Disable global interrupt
+ dwc2->gahbcfg &= ~GAHBCFG_GINT;
+
+ // Reset core: this also flushes FIFOs and clears all interrupt registers
+ reset_core(dwc2);
+
+ // Stop PHY clock and gate HCLK for power saving (per databook chapter 14)
+ dwc2->pcgcctl |= PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK;
+
+ // MCU-specific PHY deinit (disable PHY power)
+ const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2};
+ const uint8_t hs_phy_type = (dwc2->gusbcfg & GUSBCFG_PHYSEL) ? GHWCFG2_HSPHY_NOT_SUPPORTED : ghwcfg2.hs_phy_type;
+ dwc2_phy_deinit(dwc2, hs_phy_type);
+}
+
// void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr) {
// (void) in_isr;
// dwc2_regs_t * const dwc2 = DWC2_REG(rhport);
diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h
index 1947173b2..9f28ab2e0 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.h
+++ b/src/portable/synopsys/dwc2/dwc2_common.h
@@ -42,6 +42,7 @@
// - _dwc2_controller[]: array of controllers
// - DWC2_EP_MAX: largest EP counts of all controllers
// - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
+// - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core
// - dwc2_dcd_int_enable/dwc2_dcd_int_disable
// - dwc2_remote_wakeup_delay
@@ -87,6 +88,7 @@ TU_ATTR_ALWAYS_INLINE static inline dwc2_regs_t* DWC2_REG(uint8_t rhport) {
// check if highspeed phy should be used
bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy);
bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma);
+void dwc2_core_deinit(uint8_t rhport);
void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr);
//--------------------------------------------------------------------+
diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h
index 0e3570cbb..e1cb7c769 100644
--- a/src/portable/synopsys/dwc2/dwc2_efm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_efm32.h
@@ -72,6 +72,14 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
USB->ROUTE = USB_ROUTE_PHYPEN;
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // Disable PHY pin
+ USB->ROUTE = 0;
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h
index a4e0d1770..ff9f216bd 100644
--- a/src/portable/synopsys/dwc2/dwc2_esp32.h
+++ b/src/portable/synopsys/dwc2/dwc2_esp32.h
@@ -121,6 +121,13 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+ (void)dwc2;
+ (void)hs_phy_type;
+ // PHY managed by ESP-IDF
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
(void)dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h
index 0375fffe4..ccbf93a76 100644
--- a/src/portable/synopsys/dwc2/dwc2_gd32.h
+++ b/src/portable/synopsys/dwc2/dwc2_gd32.h
@@ -85,6 +85,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
// nothing to do
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_nrf.h b/src/portable/synopsys/dwc2/dwc2_nrf.h
index b93571f16..51f2d684f 100644
--- a/src/portable/synopsys/dwc2/dwc2_nrf.h
+++ b/src/portable/synopsys/dwc2/dwc2_nrf.h
@@ -52,6 +52,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_
(void)hs_phy_type;
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+ (void)dwc2;
+ (void)hs_phy_type;
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
(void)dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h
index 753917a20..259ad21b9 100644
--- a/src/portable/synopsys/dwc2/dwc2_stm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_stm32.h
@@ -264,6 +264,22 @@ static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
}
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+ if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) {
+ // Disable on-chip FS PHY
+ dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN;
+ } else {
+ // Disable HS PHY
+ #ifdef USB_HS_PHYC
+ dwc2->stm32_gccfg &= ~STM32_GCCFG_PHYHSEN;
+ // Disable PLL and LDO
+ USB_HS_PHYC->USB_HS_PHYC_PLL &= ~USB_HS_PHYC_PLL_PLLEN;
+ USB_HS_PHYC->USB_HS_PHYC_LDO &= ~USB_HS_PHYC_LDO_ENABLE;
+ #endif
+ }
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
// used to set turnaround time for fullspeed, nothing to do in highspeed mode
diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h
index 63419abf7..aca3873df 100644
--- a/src/portable/synopsys/dwc2/dwc2_xmc.h
+++ b/src/portable/synopsys/dwc2/dwc2_xmc.h
@@ -71,6 +71,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
//USB->ROUTE = USB_ROUTE_PHYPEN;
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index 0fd60b35d..e12e44a41 100644
--- a/src/portable/synopsys/dwc2/hcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/hcd_dwc2.c
@@ -416,11 +416,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// force host mode and wait for mode switch
dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FDMOD) | GUSBCFG_FHMOD;
- #if CFG_TUSB_MCU == OPT_MCU_STM32N6
- // No hardware detection of Vbus B-session is available on the STM32N6
- dwc2->stm32_gccfg &= ~STM32_GCCFG_VBVALOVAL;
- #endif
-
while ((dwc2->gintsts & GINTSTS_CMOD) != GINTSTS_CMODE_HOST) {}
#ifdef TUP_USBIP_DWC2_STM32
@@ -440,7 +435,7 @@ 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 |= GINTSTS_OTGINT | GINTSTS_CONIDSTSCHNG | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT;
+ dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT;
// NPTX can hold at least 2 packet, change interrupt level to half-empty
uint32_t gahbcfg = dwc2->gahbcfg & ~GAHBCFG_TX_FIFO_EPMTY_LVL;
@@ -450,6 +445,17 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
return true;
}
+bool hcd_deinit(uint8_t rhport) {
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+
+ // Turn off VBUS
+ dwc2->hprt = HPRT_W1_MASK; // clear w1c bits without side effects
+ // HPRT_POWER is not set -> VBUS off
+
+ dwc2_core_deinit(rhport);
+ return true;
+}
+
// Enable USB interrupt
void hcd_int_enable (uint8_t rhport) {
dwc2_int_set(rhport, TUSB_ROLE_HOST, true);
@@ -874,7 +880,7 @@ static void handle_rxflvl_irq(uint8_t rhport) {
break;
case GRXSTS_PKTSTS_HOST_DATATOGGLE_ERR:
- TU_ASSERT(0, ); // maybe try to change DToggle
+ // handle in channel interrupt
break;
case GRXSTS_PKTSTS_HOST_CHANNEL_HALTED:
@@ -1014,8 +1020,11 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h
channel_xfer_in_retry(dwc2, ch_id, hcint);
}
} else if (hcint & HCINT_DATATOGGLE_ERR) {
+ channel->hcintmsk &= ~HCINT_DATATOGGLE_ERR;
xfer->err_count = 0;
- TU_ASSERT(false);
+ hcsplt.split_compl = 0; // restart with start-split
+ channel->hcsplt = hcsplt.value;
+ channel_disable(dwc2, channel);
} else {
// nothing to do
}
@@ -1351,6 +1360,17 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) {
}
// Config HCFG FS/LS clock and HFIR for SOF interval according to link speed (value is in PHY clock unit)
+// Databook Table 2-2: System Clock Speeds
+// +-----------+------------------+----------+-----------+-------------------+
+// | PHY | PHY Clock (MHz) | Width | HCFG.Sel | HFIR (clk cycles) |
+// +-----------+------------------+----------+-----------+-------------------+
+// | HS UTMI+ | 30 | 16-bit | 30_60 | HS:3749 FS:29999 |
+// | HS UTMI+ | 60 | 8-bit | 30_60 | HS:7499 FS:59999 |
+// | HS ULPI | 60 | 8-bit | 30_60 | HS:7499 FS:59999 |
+// | FS (dead.) | 48 | internal | 48 | FS:47999 |
+// | LS via FS | 48 (6 effective) | internal | 6 | LS:47999 |
+// +-----------+------------------+----------+-----------+-------------------+
+// HFIR = (interval_us * phy_clock) - 1, where interval is 125us (HS) or 1000us (FS/LS)
static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) {
uint32_t hcfg = dwc2->hcfg & ~HCFG_FSLS_PHYCLK_SEL;
@@ -1443,16 +1463,6 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
// TU_LOG1_HEX(gintsts);
- if (gintsts & GINTSTS_CONIDSTSCHNG) {
- // Connector ID status change
- dwc2->gintsts = GINTSTS_CONIDSTSCHNG;
-
- //if (dwc2->gotgctl)
- // dwc2->hprt = HPRT_POWER; // power on port to turn on VBUS
- //dwc2->gintmsk |= GINTMSK_PRTIM;
- // TODO wait for SRP if OTG
- }
-
if (gintsts & GINTSTS_SOF) {
const bool more_sof = handle_sof_irq(rhport, in_isr);
if (!more_sof) {
diff --git a/src/tinyusb.mk b/src/tinyusb.mk
index 8f9d52de9..e7d1c0b5b 100644
--- a/src/tinyusb.mk
+++ b/src/tinyusb.mk
@@ -15,6 +15,7 @@ TINYUSB_SRC_C += \
src/class/mtp/mtp_device.c \
src/class/net/ecm_rndis_device.c \
src/class/net/ncm_device.c \
+ src/class/printer/printer_device.c \
src/class/usbtmc/usbtmc_device.c \
src/class/video/video_device.c \
src/class/vendor/vendor_device.c \
diff --git a/src/tusb.c b/src/tusb.c
index 6075e9db4..5e4422e41 100644
--- a/src/tusb.c
+++ b/src/tusb.c
@@ -39,23 +39,38 @@
#include "host/usbh_pvt.h"
#endif
+// Suppress IAR warning
+// Warning[Pe111]: statement is unreachable
+#if defined(__ICCARM__)
+#pragma diag_suppress = Pe111
+#endif
+
tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM] = { TUSB_ROLE_INVALID };
//--------------------------------------------------------------------
// Weak/Default API, can be overwritten by Application
//--------------------------------------------------------------------
-TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) {
#if CFG_TUSB_OS != OPT_OS_NONE
+TU_ATTR_WEAK uint32_t tusb_time_millis_api(void) {
+ return osal_time_millis();
+}
+
+TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) {
osal_task_delay(ms);
+}
+
#else
- // delay using millis() (if implemented) and/or frame number if possible
+// tusb_time_millis_api() must be implemented by user application.
+
+TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) {
+ // delay using millis()
const uint32_t time_ms = tusb_time_millis_api();
while ((tusb_time_millis_api() - time_ms) < ms) {}
-#endif
}
+#endif
-TU_ATTR_WEAK void* tusb_app_virt_to_phys(void *virt_addr) {
+TU_ATTR_WEAK void *tusb_app_virt_to_phys(void *virt_addr) {
return virt_addr;
}
@@ -245,6 +260,7 @@ bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) {
bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) {
const uint16_t max_packet_size = tu_edpt_packet_size(desc_ep);
TU_LOG2(" Open EP %02X with Size = %u\r\n", desc_ep->bEndpointAddress, max_packet_size);
+ TU_ASSERT(max_packet_size > 0);
switch (desc_ep->bmAttributes.xfer) {
case TUSB_XFER_ISOCHRONOUS: {
@@ -264,7 +280,7 @@ bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) {
break;
case TUSB_XFER_INTERRUPT: {
- uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64);
+ const uint16_t spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64);
TU_ASSERT(max_packet_size <= spec_size);
break;
}
@@ -306,7 +322,7 @@ bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t it
//--------------------------------------------------------------------+
bool tu_edpt_stream_init(tu_edpt_stream_t *s, bool is_host, bool is_tx, bool overwritable, void *ff_buf,
- uint16_t ff_bufsize, uint8_t *ep_buf, uint16_t ep_bufsize) {
+ uint16_t ff_bufsize, uint8_t *ep_buf) {
(void) is_tx;
if (ff_buf == NULL || ff_bufsize == 0) {
@@ -324,7 +340,6 @@ bool tu_edpt_stream_init(tu_edpt_stream_t *s, bool is_host, bool is_tx, bool ove
#endif
s->ep_buf = ep_buf;
- s->ep_bufsize = ep_bufsize;
return true;
}
@@ -394,7 +409,7 @@ uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t *s) {
if (s->ep_buf == NULL) {
count = tu_fifo_count(&s->ff); // re-get count since fifo can be changed
} else {
- count = tu_fifo_read_n(&s->ff, s->ep_buf, s->ep_bufsize);
+ count = tu_fifo_read_n(&s->ff, s->ep_buf, s->xfer_len);
}
if (count > 0) {
@@ -441,7 +456,7 @@ uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t *s) {
if (available >= s->mps) {
// multiple of packet size limit by ep bufsize
uint16_t count = (uint16_t) (available & ~(s->mps - 1));
- count = tu_min16(count, s->ep_bufsize);
+ count = tu_min16(count, s->xfer_len);
TU_ASSERT(stream_xfer(s, count), 0);
return count;
} else {
diff --git a/src/tusb.h b/src/tusb.h
index 62b3b9783..c80c8433c 100644
--- a/src/tusb.h
+++ b/src/tusb.h
@@ -88,6 +88,10 @@
#include "class/msc/msc_device.h"
#endif
+ #if CFG_TUD_PRINTER
+ #include "class/printer/printer_device.h"
+ #endif
+
#if CFG_TUD_MTP
#include "class/mtp/mtp_device.h"
#endif
@@ -135,7 +139,7 @@
//--------------------------------------------------------------------+
-// User API
+// Application API
//--------------------------------------------------------------------+
#if CFG_TUH_ENABLED || CFG_TUD_ENABLED
diff --git a/src/tusb_option.h b/src/tusb_option.h
index d87c2dc8b..f5879e1df 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -238,6 +238,7 @@
#define OPT_OS_RTTHREAD 6 ///< RT-Thread
#define OPT_OS_RTX4 7 ///< Keil RTX 4
#define OPT_OS_ZEPHYR 8 ///< Zephyr
+#define OPT_OS_THREADX 9 ///< ThreadX
//--------------------------------------------------------------------+
// Mode and Speed
@@ -394,6 +395,24 @@
// odd byte with byte access
#endif
+//------- Microchip SAMX7X -------//
+// DMA mode for device
+#ifndef CFG_TUD_SAMX7X_DMA_ENABLE
+ #ifndef CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT
+ #define CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT 0
+ #endif
+
+ #define CFG_TUD_SAMX7X_DMA_ENABLE CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT
+#endif
+
+#if (CFG_TUSB_MCU == OPT_MCU_SAMX7X)
+ #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4
+ #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4
+ #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS
+ #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS
+ #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1
+#endif
+
//--------------------------------------------------------------------
// RootHub Mode detection
//--------------------------------------------------------------------
@@ -469,7 +488,6 @@
#define TUP_MCU_STRICT_ALIGN 0
#endif
-
//--------------------------------------------------------------------+
// Common Options (Default)
//--------------------------------------------------------------------+
@@ -514,6 +532,10 @@
#define CFG_TUSB_OS OPT_OS_NONE
#endif
+#ifndef CFG_TUSB_OS_HAS_SCHEDULER
+ #define CFG_TUSB_OS_HAS_SCHEDULER (CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO)
+#endif
+
#ifndef CFG_TUSB_OS_INC_PATH
#ifndef CFG_TUSB_OS_INC_PATH_DEFAULT
#define CFG_TUSB_OS_INC_PATH_DEFAULT
@@ -654,6 +676,10 @@
#define CFG_TUD_NCM 0
#endif
+#ifndef CFG_TUD_PRINTER
+ #define CFG_TUD_PRINTER 0
+#endif
+
#ifndef CFG_TUD_EDPT_DEDICATED_HWFIFO
#define CFG_TUD_EDPT_DEDICATED_HWFIFO 0
#endif