diff options
| author | hathach <[email protected]> | 2025-09-15 16:45:41 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2025-09-15 16:45:41 +0700 |
| commit | 8e34ba9cf6bdcabe2c9eb73ae1aafec38d0ec46d (patch) | |
| tree | 58c2426146aa5d413706a62521ab6e2cb740b170 /src | |
| parent | 802819d271314298ea5a786a37ec6a1e65ef31d6 (diff) | |
| parent | feef534921446e1a3ded54a0c46191fe1709bdb4 (diff) | |
Merge branch 'master' into fork/ennebi/mtp
Diffstat (limited to 'src')
82 files changed, 8564 insertions, 5041 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9918407b3..951683104 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,6 @@ -# TODO more docs and example on how to use this file -# TINYUSB_TARGET_PREFIX and TINYUSB_TARGET_SUFFIX can be used to change the name of the target - cmake_minimum_required(VERSION 3.20) -# Add tinyusb to a existing target +# Add tinyusb to a existing target, DCD and HCD drivers are not included function(tinyusb_target_add TARGET) target_sources(${TARGET} PRIVATE # common diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 388168424..bb906c7cf 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -108,19 +108,19 @@ #endif // Put swap buffer in USB section only if necessary -#if USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING +#if USE_LINEAR_BUFFER #define IN_SW_BUF_MEM_ATTR TU_ATTR_ALIGNED(4) #else #define IN_SW_BUF_MEM_ATTR CFG_TUD_MEM_SECTION CFG_TUD_MEM_ALIGN #endif -#if USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING +#if USE_LINEAR_BUFFER #define OUT_SW_BUF_MEM_ATTR TU_ATTR_ALIGNED(4) #else #define OUT_SW_BUF_MEM_ATTR CFG_TUD_MEM_SECTION CFG_TUD_MEM_ALIGN #endif -// EP IN software buffers and mutexes -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +// EP IN software buffers +#if CFG_TUD_AUDIO_ENABLE_EP_IN tu_static IN_SW_BUF_MEM_ATTR struct { #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ); @@ -132,24 +132,11 @@ tu_static IN_SW_BUF_MEM_ATTR struct { TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ); #endif } ep_in_sw_buf; - - #if CFG_FIFO_MUTEX - #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 - tu_static osal_mutex_def_t ep_in_ff_mutex_wr_1; - #endif - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 - tu_static osal_mutex_def_t ep_in_ff_mutex_wr_2; - #endif - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 - tu_static osal_mutex_def_t ep_in_ff_mutex_wr_3; - #endif - #endif -#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN // Linear buffer TX in case: // - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR -// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into -#if CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING) +#if CFG_TUD_AUDIO_ENABLE_EP_IN && USE_LINEAR_BUFFER tu_static CFG_TUD_MEM_SECTION struct { #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX); @@ -161,10 +148,10 @@ tu_static CFG_TUD_MEM_SECTION struct { TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX); #endif } lin_buf_in; -#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && USE_LINEAR_BUFFER -// EP OUT software buffers and mutexes -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +// EP OUT software buffers +#if CFG_TUD_AUDIO_ENABLE_EP_OUT tu_static OUT_SW_BUF_MEM_ATTR struct { #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ); @@ -176,24 +163,11 @@ tu_static OUT_SW_BUF_MEM_ATTR struct { TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ); #endif } ep_out_sw_buf; - - #if CFG_FIFO_MUTEX - #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 - tu_static osal_mutex_def_t ep_out_ff_mutex_rd_1; - #endif - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 - tu_static osal_mutex_def_t ep_out_ff_mutex_rd_2; - #endif - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 - tu_static osal_mutex_def_t ep_out_ff_mutex_rd_3; - #endif - #endif -#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT // Linear buffer RX in case: // - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR -// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && USE_LINEAR_BUFFER tu_static CFG_TUD_MEM_SECTION struct { #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX); @@ -205,18 +179,18 @@ tu_static CFG_TUD_MEM_SECTION struct { TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX); #endif } lin_buf_out; -#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && USE_LINEAR_BUFFER // Control buffers -tu_static uint8_t ctrl_buf_1[CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ]; - -#if CFG_TUD_AUDIO > 1 -tu_static uint8_t ctrl_buf_2[CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ]; -#endif - -#if CFG_TUD_AUDIO > 2 -tu_static uint8_t ctrl_buf_3[CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ]; -#endif +tu_static CFG_TUD_MEM_SECTION struct { + TUD_EPBUF_DEF(buf1, CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ); + #if CFG_TUD_AUDIO > 1 + TUD_EPBUF_DEF(buf2, CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 2 + TUD_EPBUF_DEF(buf3, CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ); + #endif +} ctrl_buf; // Active alternate setting of interfaces tu_static uint8_t alt_setting_1[CFG_TUD_AUDIO_FUNC_1_N_AS_INT]; @@ -229,59 +203,6 @@ tu_static uint8_t alt_setting_2[CFG_TUD_AUDIO_FUNC_2_N_AS_INT]; tu_static uint8_t alt_setting_3[CFG_TUD_AUDIO_FUNC_3_N_AS_INT]; #endif -// Software encoding/decoding support FIFOs -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING - #if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 - tu_static TU_ATTR_ALIGNED(4) uint8_t tx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ]; - tu_static tu_fifo_t tx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO]; - #if CFG_FIFO_MUTEX - tu_static osal_mutex_def_t tx_supp_ff_mutex_wr_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO];// No need for read mutex as only USB driver reads from FIFO - #endif - #endif - - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 - tu_static TU_ATTR_ALIGNED(4) uint8_t tx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ]; - tu_static tu_fifo_t tx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO]; - #if CFG_FIFO_MUTEX - tu_static osal_mutex_def_t tx_supp_ff_mutex_wr_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO];// No need for read mutex as only USB driver reads from FIFO - #endif - #endif - - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 - tu_static TU_ATTR_ALIGNED(4) uint8_t tx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ]; - tu_static tu_fifo_t tx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO]; - #if CFG_FIFO_MUTEX - tu_static osal_mutex_def_t tx_supp_ff_mutex_wr_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO];// No need for read mutex as only USB driver reads from FIFO - #endif - #endif -#endif - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - #if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 - tu_static TU_ATTR_ALIGNED(4) uint8_t rx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ]; - tu_static tu_fifo_t rx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO]; - #if CFG_FIFO_MUTEX - tu_static osal_mutex_def_t rx_supp_ff_mutex_rd_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO];// No need for write mutex as only USB driver writes into FIFO - #endif - #endif - - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 - tu_static TU_ATTR_ALIGNED(4) uint8_t rx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ]; - tu_static tu_fifo_t rx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO]; - #if CFG_FIFO_MUTEX - tu_static osal_mutex_def_t rx_supp_ff_mutex_rd_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO];// No need for write mutex as only USB driver writes into FIFO - #endif - #endif - - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 - tu_static TU_ATTR_ALIGNED(4) uint8_t rx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ]; - tu_static tu_fifo_t rx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO]; - #if CFG_FIFO_MUTEX - tu_static osal_mutex_def_t rx_supp_ff_mutex_rd_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO];// No need for write mutex as only USB driver writes into FIFO - #endif - #endif -#endif - // Aligned buffer for feedback EP #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP tu_static CFG_TUD_MEM_SECTION struct { @@ -363,19 +284,6 @@ typedef struct } feedback; #endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP -// Decoding parameters - parameters are set when alternate AS interface is set by host -// Coding is currently only supported for EP. Software coding corresponding to AS interfaces without EPs are not supported currently. -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - audio_format_type_t format_type_rx; - uint8_t n_channels_rx; - - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING - audio_data_format_type_I_t format_type_I_rx; - uint8_t n_bytes_per_sample_rx; - uint8_t n_ff_used_rx; - #endif -#endif - #if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL uint32_t sample_rate_tx; uint16_t packet_sz_tx[3]; @@ -384,15 +292,10 @@ typedef struct #endif // Encoding parameters - parameters are set when alternate AS interface is set by host -#if CFG_TUD_AUDIO_ENABLE_EP_IN && (CFG_TUD_AUDIO_ENABLE_ENCODING || CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL) +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL audio_format_type_t format_type_tx; uint8_t n_channels_tx; uint8_t n_bytes_per_sample_tx; - - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING - audio_data_format_type_I_t format_type_I_tx; - uint8_t n_ff_used_tx; - #endif #endif /*------------- From this point, data is not cleared by bus reset -------------*/ @@ -405,40 +308,21 @@ typedef struct uint8_t *alt_setting;// We need to save the current alternate setting this way, because it is possible that there are AS interfaces which do not have an EP! // EP Transfer buffers and FIFOs -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_ENABLE_EP_OUT tu_fifo_t ep_out_ff; #endif -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_ENABLE_EP_IN tu_fifo_t ep_in_ff; #endif -// Support FIFOs for software encoding and decoding -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - tu_fifo_t *rx_supp_ff; - uint8_t n_rx_supp_ff; - uint16_t rx_supp_ff_sz_max; - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING - uint8_t n_channels_per_ff_rx; - #endif -#endif - -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING - tu_fifo_t *tx_supp_ff; - uint8_t n_tx_supp_ff; - uint16_t tx_supp_ff_sz_max; - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING - uint8_t n_channels_per_ff_tx; - #endif -#endif - -// Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR the support FIFOs are used -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) +// Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && USE_LINEAR_BUFFER uint8_t *lin_buf_out; #define USE_LINEAR_BUFFER_RX 1 #endif -#if CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING) +#if CFG_TUD_AUDIO_ENABLE_EP_IN && USE_LINEAR_BUFFER uint8_t *lin_buf_in; #define USE_LINEAR_BUFFER_TX 1 #endif @@ -463,35 +347,19 @@ typedef struct //--------------------------------------------------------------------+ #if CFG_TUD_AUDIO_ENABLE_EP_IN -TU_ATTR_WEAK bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting) { +TU_ATTR_WEAK bool tud_audio_tx_done_isr(uint8_t rhport, uint16_t n_bytes_sent, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting) { (void) rhport; + (void) n_bytes_sent; (void) func_id; (void) ep_in; (void) cur_alt_setting; return true; } -TU_ATTR_WEAK bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting) { - (void) rhport; - (void) n_bytes_copied; - (void) func_id; - (void) ep_in; - (void) cur_alt_setting; - return true; -} #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT -TU_ATTR_WEAK bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) { - (void) rhport; - (void) n_bytes_received; - (void) func_id; - (void) ep_out; - (void) cur_alt_setting; - return true; -} - -TU_ATTR_WEAK bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) { +TU_ATTR_WEAK bool tud_audio_rx_done_isr(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) { (void) rhport; (void) n_bytes_received; (void) func_id; @@ -502,10 +370,6 @@ TU_ATTR_WEAK bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_byte #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP -TU_ATTR_WEAK void tud_audio_fb_done_cb(uint8_t func_id) { - (void) func_id; -} - TU_ATTR_WEAK void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t *feedback_param) { (void) func_id; (void) alt_itf; @@ -525,7 +389,7 @@ TU_ATTR_WEAK TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func #endif #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP -TU_ATTR_WEAK void tud_audio_int_done_cb(uint8_t rhport) { +TU_ATTR_WEAK void tud_audio_int_xfer_cb(uint8_t rhport) { (void) rhport; } #endif @@ -538,7 +402,7 @@ TU_ATTR_WEAK bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t co } // Invoked when audio set interface request received which closes an EP -TU_ATTR_WEAK bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const *p_request) { +TU_ATTR_WEAK bool tud_audio_set_itf_close_ep_cb(uint8_t rhport, tusb_control_request_t const *p_request) { (void) rhport; (void) p_request; return true; @@ -601,19 +465,11 @@ TU_ATTR_WEAK bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_reque tu_static CFG_TUD_MEM_SECTION audiod_function_t _audiod_fct[CFG_TUD_AUDIO]; #if CFG_TUD_AUDIO_ENABLE_EP_OUT -static bool audiod_rx_done_cb(uint8_t rhport, audiod_function_t *audio, uint16_t n_bytes_received); -#endif - -#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT -static bool audiod_decode_type_I_pcm(uint8_t rhport, audiod_function_t *audio, uint16_t n_bytes_received); +static bool audiod_rx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received); #endif #if CFG_TUD_AUDIO_ENABLE_EP_IN -static bool audiod_tx_done_cb(uint8_t rhport, audiod_function_t *audio); -#endif - -#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN -static uint16_t audiod_encode_type_I_pcm(uint8_t rhport, audiod_function_t *audio); +static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_sent); #endif static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const *p_request); @@ -626,11 +482,8 @@ static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id); static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id); static uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio); -#if (CFG_TUD_AUDIO_ENABLE_EP_IN && (CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL || CFG_TUD_AUDIO_ENABLE_ENCODING)) || (CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING) -static void audiod_parse_for_AS_params(audiod_function_t *audio, uint8_t const *p_desc, uint8_t const *p_desc_end, uint8_t const as_itf); -#endif - #if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +static void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc); static bool audiod_calc_tx_packet_sz(audiod_function_t *audio); static uint16_t audiod_tx_packet_size(const uint16_t *norminal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t max_size); #endif @@ -651,7 +504,7 @@ bool tud_audio_n_mounted(uint8_t func_id) { // READ API //--------------------------------------------------------------------+ -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_ENABLE_EP_OUT uint16_t tud_audio_n_available(uint8_t func_id) { TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); @@ -669,276 +522,102 @@ bool tud_audio_n_clear_ep_out_ff(uint8_t func_id) { } tu_fifo_t *tud_audio_n_get_ep_out_ff(uint8_t func_id) { - if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) return &_audiod_fct[func_id].ep_out_ff; - return NULL; -} - -#endif - -#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT -// Delete all content in the support RX FIFOs -bool tud_audio_n_clear_rx_support_ff(uint8_t func_id, uint8_t ff_idx) { - TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff); - return tu_fifo_clear(&_audiod_fct[func_id].rx_supp_ff[ff_idx]); -} - -uint16_t tud_audio_n_available_support_ff(uint8_t func_id, uint8_t ff_idx) { - TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff); - return tu_fifo_count(&_audiod_fct[func_id].rx_supp_ff[ff_idx]); -} - -uint16_t tud_audio_n_read_support_ff(uint8_t func_id, uint8_t ff_idx, void *buffer, uint16_t bufsize) { - TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff); - return tu_fifo_read_n(&_audiod_fct[func_id].rx_supp_ff[ff_idx], buffer, bufsize); -} - -tu_fifo_t *tud_audio_n_get_rx_support_ff(uint8_t func_id, uint8_t ff_idx) { - if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff) return &_audiod_fct[func_id].rx_supp_ff[ff_idx]; + if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) { + return &_audiod_fct[func_id].ep_out_ff; + } return NULL; } -#endif - -// This function is called once an audio packet is received by the USB and is responsible for putting data from USB memory into EP_OUT_FIFO (or support FIFOs + decoding of received stream into audio channels). -// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_ENABLE_DECODING = 0. - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT -static bool audiod_rx_done_cb(uint8_t rhport, audiod_function_t *audio, uint16_t n_bytes_received) { - uint8_t idxItf = 0; +static bool audiod_rx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received) { + uint8_t idxItf; uint8_t const *dummy2; - uint8_t idx_audio_fct = 0; - idx_audio_fct = audiod_get_audio_fct_idx(audio); + uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); TU_VERIFY(audiod_get_AS_interface_index(audio->ep_out_as_intf_num, audio, &idxItf, &dummy2)); - // Call a weak callback here - a possibility for user to get informed an audio packet was received and data gets now loaded into EP FIFO (or decoded into support RX software FIFO) - TU_VERIFY(tud_audio_rx_done_pre_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf])); - - #if CFG_TUD_AUDIO_ENABLE_DECODING - - switch (audio->format_type_rx) { - case AUDIO_FORMAT_TYPE_UNDEFINED: - // INDIVIDUAL DECODING PROCEDURE REQUIRED HERE! - TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT encoding not implemented!\r\n"); - TU_BREAKPOINT(); - break; - - case AUDIO_FORMAT_TYPE_I: - - switch (audio->format_type_I_rx) { - case AUDIO_DATA_FORMAT_TYPE_I_PCM: - TU_VERIFY(audiod_decode_type_I_pcm(rhport, audio, n_bytes_received)); - break; - - default: - // DESIRED CFG_TUD_AUDIO_FORMAT_TYPE_I_RX NOT IMPLEMENTED! - TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_I_RX encoding not implemented!\r\n"); - TU_BREAKPOINT(); - break; - } - break; - - default: - // Desired CFG_TUD_AUDIO_FORMAT_TYPE_RX not implemented! - TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_RX not implemented!\r\n"); - TU_BREAKPOINT(); - break; - } - - // Prepare for next transmission - TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); - - #else - - #if USE_LINEAR_BUFFER_RX + #if USE_LINEAR_BUFFER_RX // Data currently is in linear buffer, copy into EP OUT FIFO TU_VERIFY(tu_fifo_write_n(&audio->ep_out_ff, audio->lin_buf_out, n_bytes_received)); // Schedule for next receive TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); - #else + #else // Data is already placed in EP FIFO, schedule for next receive TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false); - #endif + #endif - #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP if (audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FIFO_COUNT) { audiod_fb_fifo_count_update(audio, tu_fifo_count(&audio->ep_out_ff)); } - #endif - #endif - // Call a weak callback here - a possibility for user to get informed decoding was completed - TU_VERIFY(tud_audio_rx_done_post_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf])); + // Call a weak callback here - a possibility for user to get informed an audio packet was received and data gets now loaded into EP FIFO + TU_VERIFY(tud_audio_rx_done_isr(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf])); return true; } #endif//CFG_TUD_AUDIO_ENABLE_EP_OUT -// The following functions are used in case CFG_TUD_AUDIO_ENABLE_DECODING != 0 -#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT - -// Decoding according to 2.3.1.5 Audio Streams - -// Helper function -static inline void *audiod_interleaved_copy_bytes_fast_decode(uint16_t const nBytesPerSample, void *dst, const void *dst_end, void *src, uint8_t const n_ff_used) { - // Due to one FIFO contains 2 channels, data always aligned to (nBytesPerSample * 2) - uint16_t *dst16 = dst; - uint16_t *src16 = src; - const uint16_t *dst_end16 = dst_end; - uint32_t *dst32 = dst; - uint32_t *src32 = src; - const uint32_t *dst_end32 = dst_end; - - if (nBytesPerSample == 1) { - while (dst16 < dst_end16) { - *dst16++ = *src16++; - src16 += n_ff_used - 1; - } - return src16; - } else if (nBytesPerSample == 2) { - while (dst32 < dst_end32) { - *dst32++ = *src32++; - src32 += n_ff_used - 1; - } - return src32; - } else if (nBytesPerSample == 3) { - while (dst16 < dst_end16) { - *dst16++ = *src16++; - *dst16++ = *src16++; - *dst16++ = *src16++; - src16 += 3 * (n_ff_used - 1); - } - return src16; - } else// nBytesPerSample == 4 - { - while (dst32 < dst_end32) { - *dst32++ = *src32++; - *dst32++ = *src32++; - src32 += 2 * (n_ff_used - 1); - } - return src32; - } -} - -static bool audiod_decode_type_I_pcm(uint8_t rhport, audiod_function_t *audio, uint16_t n_bytes_received) { - (void) rhport; - - // Determine amount of samples - uint8_t const n_ff_used = audio->n_ff_used_rx; - uint16_t const nBytesPerFFToRead = n_bytes_received / n_ff_used; - uint8_t cnt_ff; - - // Decode - uint8_t *src; - uint8_t *dst_end; - - tu_fifo_buffer_info_t info; - - for (cnt_ff = 0; cnt_ff < n_ff_used; cnt_ff++) { - tu_fifo_get_write_info(&audio->rx_supp_ff[cnt_ff], &info); - - if (info.len_lin != 0) { - info.len_lin = tu_min16(nBytesPerFFToRead, info.len_lin); - src = &audio->lin_buf_out[cnt_ff * audio->n_channels_per_ff_rx * audio->n_bytes_per_sample_rx]; - dst_end = info.ptr_lin + info.len_lin; - src = audiod_interleaved_copy_bytes_fast_decode(audio->n_bytes_per_sample_rx, info.ptr_lin, dst_end, src, n_ff_used); - - // Handle wrapped part of FIFO - info.len_wrap = tu_min16(nBytesPerFFToRead - info.len_lin, info.len_wrap); - if (info.len_wrap != 0) { - dst_end = info.ptr_wrap + info.len_wrap; - audiod_interleaved_copy_bytes_fast_decode(audio->n_bytes_per_sample_rx, info.ptr_wrap, dst_end, src, n_ff_used); - } - tu_fifo_advance_write_pointer(&audio->rx_supp_ff[cnt_ff], info.len_lin + info.len_wrap); - } - } - - // Number of bytes should be a multiple of CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX but checking makes no sense - no way to correct it - // TU_VERIFY(cnt != n_bytes); - - #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP - if (audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FIFO_COUNT) { - audiod_fb_fifo_count_update(audio, tu_fifo_count(&audio->rx_supp_ff[0])); - } - #endif - - return true; -} -#endif//CFG_TUD_AUDIO_ENABLE_DECODING - //--------------------------------------------------------------------+ // WRITE API //--------------------------------------------------------------------+ -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_ENABLE_EP_IN -/** - * \brief Write data to EP in buffer - * - * Write data to buffer. If it is full, new data can be inserted once a transmit was scheduled. See audiod_tx_done_cb(). - * If TX FIFOs are used, this function is not available in order to not let the user mess up the encoding process. - * - * \param[in] func_id: Index of audio function interface - * \param[in] data: Pointer to data array to be copied from - * \param[in] len: # of array elements to copy - * \return Number of bytes actually written - */ uint16_t tud_audio_n_write(uint8_t func_id, const void *data, uint16_t len) { TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); return tu_fifo_write_n(&_audiod_fct[func_id].ep_in_ff, data, len); } -bool tud_audio_n_clear_ep_in_ff(uint8_t func_id)// Delete all content in the EP IN FIFO -{ +bool tud_audio_n_clear_ep_in_ff(uint8_t func_id) { TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); return tu_fifo_clear(&_audiod_fct[func_id].ep_in_ff); } tu_fifo_t *tud_audio_n_get_ep_in_ff(uint8_t func_id) { - if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) return &_audiod_fct[func_id].ep_in_ff; + if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) { + return &_audiod_fct[func_id].ep_in_ff; + } return NULL; } -#endif - -#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN - -uint16_t tud_audio_n_flush_tx_support_ff(uint8_t func_id)// Force all content in the support TX FIFOs to be written into linear buffer and schedule a transmit -{ - TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); - audiod_function_t *audio = &_audiod_fct[func_id]; - - uint16_t n_bytes_copied = tu_fifo_count(&audio->tx_supp_ff[0]); +static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t * audio, uint16_t n_bytes_sent) { + uint8_t idxItf; + uint8_t const *dummy2; - TU_VERIFY(audiod_tx_done_cb(audio->rhport, audio)); + uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); + TU_VERIFY(audiod_get_AS_interface_index(audio->ep_in_as_intf_num, audio, &idxItf, &dummy2)); - n_bytes_copied -= tu_fifo_count(&audio->tx_supp_ff[0]); - n_bytes_copied = n_bytes_copied * audio->tx_supp_ff[0].item_size; + // Only send something if current alternate interface is not 0 as in this case nothing is to be sent due to UAC2 specifications + if (audio->alt_setting[idxItf] == 0) { return false; } - return n_bytes_copied; -} + // Send everything in ISO EP FIFO + uint16_t n_bytes_tx; -bool tud_audio_n_clear_tx_support_ff(uint8_t func_id, uint8_t ff_idx) { - TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_tx_supp_ff); - return tu_fifo_clear(&_audiod_fct[func_id].tx_supp_ff[ff_idx]); -} + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + // packet_sz_tx is based on total packet size, here we want size for each support buffer. + n_bytes_tx = audiod_tx_packet_size(audio->packet_sz_tx, tu_fifo_count(&audio->ep_in_ff), audio->ep_in_ff.depth, audio->ep_in_sz); + #else + n_bytes_tx = tu_min16(tu_fifo_count(&audio->ep_in_ff), audio->ep_in_sz);// Limit up to max packet size, more can not be done for ISO + #endif + #if USE_LINEAR_BUFFER_TX + tu_fifo_read_n(&audio->ep_in_ff, audio->lin_buf_in, n_bytes_tx); + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx)); + #else + // Send everything in ISO EP FIFO + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, n_bytes_tx)); + #endif -uint16_t tud_audio_n_write_support_ff(uint8_t func_id, uint8_t ff_idx, const void *data, uint16_t len) { - TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_tx_supp_ff); - return tu_fifo_write_n(&_audiod_fct[func_id].tx_supp_ff[ff_idx], data, len); -} + // Call a weak callback here - a possibility for user to get informed former TX was completed and data gets now loaded into EP in buffer + TU_VERIFY(tud_audio_tx_done_isr(rhport, n_bytes_sent, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf])); -tu_fifo_t *tud_audio_n_get_tx_support_ff(uint8_t func_id, uint8_t ff_idx) { - if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_tx_supp_ff) return &_audiod_fct[func_id].tx_supp_ff[ff_idx]; - return NULL; + return true; } #endif - #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP // If no interrupt transmit is pending bytes get written into buffer and a transmit is scheduled - once transmit completed tud_audio_int_done_cb() is called in inform user bool tud_audio_int_n_write(uint8_t func_id, const audio_interrupt_data_t *data) { @@ -962,223 +641,8 @@ bool tud_audio_int_n_write(uint8_t func_id, const audio_interrupt_data_t *data) } #endif -// This function is called once a transmit of an audio packet was successfully completed. Here, we encode samples and place it in IN EP's buffer for next transmission. -// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_ENABLE_ENCODING = 0 and use tud_audio_n_write. - -// n_bytes_copied - Informs caller how many bytes were loaded. In case n_bytes_copied = 0, a ZLP is scheduled to inform host no data is available for current frame. -#if CFG_TUD_AUDIO_ENABLE_EP_IN -static bool audiod_tx_done_cb(uint8_t rhport, audiod_function_t *audio) { - uint8_t idxItf; - uint8_t const *dummy2; - - uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); - TU_VERIFY(audiod_get_AS_interface_index(audio->ep_in_as_intf_num, audio, &idxItf, &dummy2)); - - // Only send something if current alternate interface is not 0 as in this case nothing is to be sent due to UAC2 specifications - if (audio->alt_setting[idxItf] == 0) return false; - - // Call a weak callback here - a possibility for user to get informed former TX was completed and data gets now loaded into EP in buffer (in case FIFOs are used) or - // if no FIFOs are used the user may use this call back to load its data into the EP IN buffer by use of tud_audio_n_write_ep_in_buffer(). - TU_VERIFY(tud_audio_tx_done_pre_load_cb(rhport, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf])); - - // Send everything in ISO EP FIFO - uint16_t n_bytes_tx; - - // If support FIFOs are used, encode and schedule transmit - #if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN - switch (audio->format_type_tx) { - case AUDIO_FORMAT_TYPE_UNDEFINED: - // INDIVIDUAL ENCODING PROCEDURE REQUIRED HERE! - TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT encoding not implemented!\r\n"); - TU_BREAKPOINT(); - n_bytes_tx = 0; - break; - - case AUDIO_FORMAT_TYPE_I: - - switch (audio->format_type_I_tx) { - case AUDIO_DATA_FORMAT_TYPE_I_PCM: - - n_bytes_tx = audiod_encode_type_I_pcm(rhport, audio); - break; - - default: - // YOUR ENCODING IS REQUIRED HERE! - TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_I_TX encoding not implemented!\r\n"); - TU_BREAKPOINT(); - n_bytes_tx = 0; - break; - } - break; - - default: - // Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented! - TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented!\r\n"); - TU_BREAKPOINT(); - n_bytes_tx = 0; - break; - } - - TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx)); - - #else - // No support FIFOs, if no linear buffer required schedule transmit, else put data into linear buffer and schedule - #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL - // packet_sz_tx is based on total packet size, here we want size for each support buffer. - n_bytes_tx = audiod_tx_packet_size(audio->packet_sz_tx, tu_fifo_count(&audio->ep_in_ff), audio->ep_in_ff.depth, audio->ep_in_sz); - #else - n_bytes_tx = tu_min16(tu_fifo_count(&audio->ep_in_ff), audio->ep_in_sz);// Limit up to max packet size, more can not be done for ISO - #endif - #if USE_LINEAR_BUFFER_TX - tu_fifo_read_n(&audio->ep_in_ff, audio->lin_buf_in, n_bytes_tx); - TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx)); - #else - // Send everything in ISO EP FIFO - TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, n_bytes_tx)); - #endif - - #endif - - // Call a weak callback here - a possibility for user to get informed former TX was completed and how many bytes were loaded for the next frame - TU_VERIFY(tud_audio_tx_done_post_load_cb(rhport, n_bytes_tx, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf])); - - return true; -} - -#endif//CFG_TUD_AUDIO_ENABLE_EP_IN - -#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN -// Take samples from the support buffer and encode them into the IN EP software FIFO -// Returns number of bytes written into linear buffer - -/* 2.3.1.7.1 PCM Format -The PCM (Pulse Coded Modulation) format is the most commonly used audio format to represent audio -data streams. The audio data is not compressed and uses a signed two’s-complement fixed point format. It -is left-justified (the sign bit is the Msb) and data is padded with trailing zeros to fill the remaining unused -bits of the subslot. The binary point is located to the right of the sign bit so that all values lie within the -range [-1, +1) - */ - -/* - * This function encodes channels saved within the support FIFOs into one stream by interleaving the PCM samples - * in the support FIFOs according to 2.3.1.5 Audio Streams. It does not control justification (left or right) and - * does not change the number of bytes per sample. - * */ - -// Helper function -static inline void *audiod_interleaved_copy_bytes_fast_encode(uint16_t const nBytesPerSample, void *src, const void *src_end, void *dst, uint8_t const n_ff_used) { - // Due to one FIFO contains 2 channels, data always aligned to (nBytesPerSample * 2) - uint16_t *dst16 = dst; - uint16_t *src16 = src; - const uint16_t *src_end16 = src_end; - uint32_t *dst32 = dst; - uint32_t *src32 = src; - const uint32_t *src_end32 = src_end; - - if (nBytesPerSample == 1) { - while (src16 < src_end16) { - *dst16++ = *src16++; - dst16 += n_ff_used - 1; - } - return dst16; - } else if (nBytesPerSample == 2) { - while (src32 < src_end32) { - *dst32++ = *src32++; - dst32 += n_ff_used - 1; - } - return dst32; - } else if (nBytesPerSample == 3) { - while (src16 < src_end16) { - *dst16++ = *src16++; - *dst16++ = *src16++; - *dst16++ = *src16++; - dst16 += 3 * (n_ff_used - 1); - } - return dst16; - } else// nBytesPerSample == 4 - { - while (src32 < src_end32) { - *dst32++ = *src32++; - *dst32++ = *src32++; - dst32 += 2 * (n_ff_used - 1); - } - return dst32; - } -} - -static uint16_t audiod_encode_type_I_pcm(uint8_t rhport, audiod_function_t *audio) { - // This function relies on the fact that the length of the support FIFOs was configured to be a multiple of the active sample size in bytes s.t. no sample is split within a wrap - // This is ensured within set_interface, where the FIFOs are reconfigured according to this size - - // We encode directly into IN EP's linear buffer - abort if previous transfer not complete - TU_VERIFY(!usbd_edpt_busy(rhport, audio->ep_in)); - - // Determine amount of samples - uint8_t const n_ff_used = audio->n_ff_used_tx; - uint16_t nBytesPerFFToSend = tu_fifo_count(&audio->tx_supp_ff[0]); - uint8_t cnt_ff; - - for (cnt_ff = 1; cnt_ff < n_ff_used; cnt_ff++) { - uint16_t const count = tu_fifo_count(&audio->tx_supp_ff[cnt_ff]); - if (count < nBytesPerFFToSend) { - nBytesPerFFToSend = count; - } - } - - #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL - const uint16_t norm_packet_sz_tx[3] = {audio->packet_sz_tx[0] / n_ff_used, - audio->packet_sz_tx[1] / n_ff_used, - audio->packet_sz_tx[2] / n_ff_used}; - // packet_sz_tx is based on total packet size, here we want size for each support buffer. - nBytesPerFFToSend = audiod_tx_packet_size(norm_packet_sz_tx, nBytesPerFFToSend, audio->tx_supp_ff[0].depth, audio->ep_in_sz / n_ff_used); - // Check if there is enough data - if (nBytesPerFFToSend == 0) return 0; - #else - // Check if there is enough data - if (nBytesPerFFToSend == 0) return 0; - // Limit to maximum sample number - THIS IS A POSSIBLE ERROR SOURCE IF TOO MANY SAMPLE WOULD NEED TO BE SENT BUT CAN NOT! - nBytesPerFFToSend = tu_min16(nBytesPerFFToSend, audio->ep_in_sz / n_ff_used); - // Round to full number of samples (flooring) - uint16_t const nSlotSize = audio->n_channels_per_ff_tx * audio->n_bytes_per_sample_tx; - nBytesPerFFToSend = (nBytesPerFFToSend / nSlotSize) * nSlotSize; - #endif - - // Encode - uint8_t *dst; - uint8_t *src_end; - - tu_fifo_buffer_info_t info; - - for (cnt_ff = 0; cnt_ff < n_ff_used; cnt_ff++) { - dst = &audio->lin_buf_in[cnt_ff * audio->n_channels_per_ff_tx * audio->n_bytes_per_sample_tx]; - - tu_fifo_get_read_info(&audio->tx_supp_ff[cnt_ff], &info); - - if (info.len_lin != 0) { - info.len_lin = tu_min16(nBytesPerFFToSend, info.len_lin);// Limit up to desired length - src_end = (uint8_t *) info.ptr_lin + info.len_lin; - dst = audiod_interleaved_copy_bytes_fast_encode(audio->n_bytes_per_sample_tx, info.ptr_lin, src_end, dst, n_ff_used); - - // Limit up to desired length - info.len_wrap = tu_min16(nBytesPerFFToSend - info.len_lin, info.len_wrap); - - // Handle wrapped part of FIFO - if (info.len_wrap != 0) { - src_end = (uint8_t *) info.ptr_wrap + info.len_wrap; - audiod_interleaved_copy_bytes_fast_encode(audio->n_bytes_per_sample_tx, info.ptr_wrap, src_end, dst, n_ff_used); - } - - tu_fifo_advance_read_pointer(&audio->tx_supp_ff[cnt_ff], info.len_lin + info.len_wrap); - } - } - - return nBytesPerFFToSend * n_ff_used; -} -#endif//CFG_TUD_AUDIO_ENABLE_ENCODING - -// This function is called once a transmit of a feedback packet was successfully completed. Here, we get the next feedback value to be sent - #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +// This function is called once a transmit of a feedback packet was successfully completed. Here, we get the next feedback value to be sent static inline bool audiod_fb_send(audiod_function_t *audio) { bool apply_correction = (TUSB_SPEED_FULL == tud_speed_get()) && audio->feedback.format_correction; // Format the feedback value @@ -1223,18 +687,18 @@ void audiod_init(void) { // Initialize control buffers switch (i) { case 0: - audio->ctrl_buf = ctrl_buf_1; + audio->ctrl_buf = ctrl_buf.buf1; audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ; break; #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ > 0 case 1: - audio->ctrl_buf = ctrl_buf_2; + audio->ctrl_buf = ctrl_buf.buf2; audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ; break; #endif #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ > 0 case 2: - audio->ctrl_buf = ctrl_buf_3; + audio->ctrl_buf = ctrl_buf.buf3; audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ; break; #endif @@ -1260,35 +724,26 @@ void audiod_init(void) { } // Initialize IN EP FIFO if required -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_ENABLE_EP_IN switch (i) { #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 case 0: tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_1), NULL); - #endif break; #endif #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 case 1: tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_2), NULL); - #endif break; #endif #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 case 2: tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_3), NULL); - #endif break; #endif } -#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN // Initialize linear buffers #if USE_LINEAR_BUFFER_TX @@ -1312,35 +767,26 @@ void audiod_init(void) { #endif// USE_LINEAR_BUFFER_TX // Initialize OUT EP FIFO if required -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_ENABLE_EP_OUT switch (i) { #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 case 0: tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_1)); - #endif break; #endif #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 case 1: tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_2)); - #endif break; #endif #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 case 2: tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_3)); - #endif break; #endif } -#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT // Initialize linear buffers #if USE_LINEAR_BUFFER_RX @@ -1382,150 +828,6 @@ void audiod_init(void) { #endif } #endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP - - // Initialize TX support FIFOs if required -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING - - switch (i) { - #if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 - case 0: - audio->tx_supp_ff = tx_supp_ff_1; - audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; - audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ; - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; cnt++) { - tu_fifo_config(&tx_supp_ff_1[cnt], tx_supp_ff_buf_1[cnt], CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&tx_supp_ff_1[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_1[cnt]), NULL); - #endif - } - - break; - #endif// CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 - - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 - case 1: - audio->tx_supp_ff = tx_supp_ff_2; - audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO; - audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ; - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO; cnt++) { - tu_fifo_config(&tx_supp_ff_2[cnt], tx_supp_ff_buf_2[cnt], CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&tx_supp_ff_2[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_2[cnt]), NULL); - #endif - } - - break; - #endif// CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 - - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 - case 2: - audio->tx_supp_ff = tx_supp_ff_3; - audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO; - audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ; - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO; cnt++) { - tu_fifo_config(&tx_supp_ff_3[cnt], tx_supp_ff_buf_3[cnt], CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&tx_supp_ff_3[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_3[cnt]), NULL); - #endif - } - - break; - #endif// CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 - } -#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING - - // Set encoding parameters for Type_I formats -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING - switch (i) { - #if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 - case 0: - audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX; - break; - #endif - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 - case 1: - audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_TX; - break; - #endif - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 - case 2: - audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_TX; - break; - #endif - } -#endif// CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING - - // Initialize RX support FIFOs if required -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - - switch (i) { - #if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 - case 0: - audio->rx_supp_ff = rx_supp_ff_1; - audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO; - audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ; - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO; cnt++) { - tu_fifo_config(&rx_supp_ff_1[cnt], rx_supp_ff_buf_1[cnt], CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&rx_supp_ff_1[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_1[cnt]), NULL); - #endif - } - - break; - #endif// CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 - - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 - case 1: - audio->rx_supp_ff = rx_supp_ff_2; - audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO; - audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ; - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO; cnt++) { - tu_fifo_config(&rx_supp_ff_2[cnt], rx_supp_ff_buf_2[cnt], CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&rx_supp_ff_2[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_2[cnt]), NULL); - #endif - } - - break; - #endif// CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 - - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 - case 2: - audio->rx_supp_ff = rx_supp_ff_3; - audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO; - audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ; - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO; cnt++) { - tu_fifo_config(&rx_supp_ff_3[cnt], rx_supp_ff_buf_3[cnt], CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ, 1, true); - #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&rx_supp_ff_3[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_3[cnt]), NULL); - #endif - } - - break; - #endif// CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 - } -#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING - - // Set encoding parameters for Type_I formats -#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING - switch (i) { - #if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 - case 0: - audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_RX; - break; - #endif - #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 - case 1: - audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_RX; - break; - #endif - #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 - case 2: - audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_RX; - break; - #endif - } -#endif// CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING } } @@ -1540,25 +842,13 @@ void audiod_reset(uint8_t rhport) { audiod_function_t *audio = &_audiod_fct[i]; tu_memclr(audio, ITF_MEM_RESET_SIZE); -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_ENABLE_EP_IN tu_fifo_clear(&audio->ep_in_ff); #endif -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_ENABLE_EP_OUT tu_fifo_clear(&audio->ep_out_ff); #endif - -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING - for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) { - tu_fifo_clear(&audio->tx_supp_ff[cnt]); - } -#endif - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) { - tu_fifo_clear(&audio->rx_supp_ff[cnt]); - } -#endif } } @@ -1632,20 +922,22 @@ uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint ep_fb = desc_ep->bEndpointAddress; } #endif - // Data EP - if (desc_ep->bmAttributes.usage == 0) { - if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { #if CFG_TUD_AUDIO_ENABLE_EP_IN - ep_in = desc_ep->bEndpointAddress; - ep_in_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_in_size); + // Data or data with implicit feedback IN EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN + && (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2)) { + ep_in = desc_ep->bEndpointAddress; + ep_in_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_in_size); + } #endif - } else { #if CFG_TUD_AUDIO_ENABLE_EP_OUT - ep_out = desc_ep->bEndpointAddress; - ep_out_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_out_size); - #endif - } + // Data OUT EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_OUT + && desc_ep->bmAttributes.usage == 0) { + ep_out = desc_ep->bEndpointAddress; + ep_out_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_out_size); } + #endif } } @@ -1681,10 +973,10 @@ uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { - if (desc_ep->bmAttributes.usage == 0) { - if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { - _audiod_fct[i].interval_tx = desc_ep->bInterval; - } + // For data or data with implicit feedback IN EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN + && (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2)) { + _audiod_fct[i].interval_tx = desc_ep->bInterval; } } } else if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL) { @@ -1783,16 +1075,10 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p #endif // Clear FIFOs, since data is no longer valid - #if !CFG_TUD_AUDIO_ENABLE_ENCODING tu_fifo_clear(&audio->ep_in_ff); - #else - for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) { - tu_fifo_clear(&audio->tx_supp_ff[cnt]); - } - #endif // Invoke callback - can be used to stop data sampling - TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request)); + TU_VERIFY(tud_audio_set_itf_close_ep_cb(rhport, p_request)); audio->ep_in = 0;// Necessary? @@ -1812,16 +1098,10 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p #endif // Clear FIFOs, since data is no longer valid - #if !CFG_TUD_AUDIO_ENABLE_DECODING tu_fifo_clear(&audio->ep_out_ff); - #else - for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) { - tu_fifo_clear(&audio->rx_supp_ff[cnt]); - } - #endif // Invoke callback - can be used to stop data sampling - TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request)); + TU_VERIFY(tud_audio_set_itf_close_ep_cb(rhport, p_request)); audio->ep_out = 0;// Necessary? @@ -1848,7 +1128,7 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p while (p_desc_end - p_desc > 0) { // Find correct interface if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *) p_desc)->bInterfaceNumber == itf && ((tusb_desc_interface_t const *) p_desc)->bAlternateSetting == alt) { -#if (CFG_TUD_AUDIO_ENABLE_EP_IN && (CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL || CFG_TUD_AUDIO_ENABLE_ENCODING)) || (CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING) +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL uint8_t const *p_desc_parse_for_params = p_desc; #endif // From this point forward follow the EP descriptors associated to the current alternate setting interface - Open EPs if necessary @@ -1868,57 +1148,36 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p usbd_edpt_clear_stall(rhport, ep_addr); #if CFG_TUD_AUDIO_ENABLE_EP_IN - if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.usage == 0x00)// Check if usage is data EP + // For data or data with implicit feedback IN EP + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2)) { // Save address audio->ep_in = ep_addr; audio->ep_in_as_intf_num = itf; audio->ep_in_sz = tu_edpt_packet_size(desc_ep); - // If software encoding is enabled, parse for the corresponding parameters - doing this here means only AS interfaces with EPs get scanned for parameters - #if CFG_TUD_AUDIO_ENABLE_ENCODING || CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL - audiod_parse_for_AS_params(audio, p_desc_parse_for_params, p_desc_end, itf); - - // Reconfigure size of support FIFOs - this is necessary to avoid samples to get split in case of a wrap - #if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING - const uint16_t active_fifo_depth = (uint16_t) ((audio->tx_supp_ff_sz_max / (audio->n_channels_per_ff_tx * audio->n_bytes_per_sample_tx)) * (audio->n_channels_per_ff_tx * audio->n_bytes_per_sample_tx)); - for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) { - tu_fifo_config(&audio->tx_supp_ff[cnt], audio->tx_supp_ff[cnt].buffer, active_fifo_depth, 1, true); - } - audio->n_ff_used_tx = audio->n_channels_tx / audio->n_channels_per_ff_tx; - TU_ASSERT(audio->n_ff_used_tx <= audio->n_tx_supp_ff); - #endif + // If flow control is enabled, parse for the corresponding parameters - doing this here means only AS interfaces with EPs get scanned for parameters + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audiod_parse_flow_control_params(audio, p_desc_parse_for_params); + #endif + // Schedule first transmit if alternate interface is not zero, as sample data is available a ZLP is loaded + #if USE_LINEAR_BUFFER_TX + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, 0)); + #else + // Send everything in ISO EP FIFO + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, 0)); #endif - - // Schedule first transmit if alternate interface is not zero i.e. streaming is disabled - in case no sample data is available a ZLP is loaded - // It is necessary to trigger this here since the refill is done with an RX FIFO empty interrupt which can only trigger if something was in there - TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_fct[func_id])); } #endif// CFG_TUD_AUDIO_ENABLE_EP_IN #if CFG_TUD_AUDIO_ENABLE_EP_OUT - - if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT)// Checking usage not necessary - { + // Checking usage not necessary + if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { // Save address audio->ep_out = ep_addr; audio->ep_out_as_intf_num = itf; audio->ep_out_sz = tu_edpt_packet_size(desc_ep); - #if CFG_TUD_AUDIO_ENABLE_DECODING - audiod_parse_for_AS_params(audio, p_desc_parse_for_params, p_desc_end, itf); - - // Reconfigure size of support FIFOs - this is necessary to avoid samples to get split in case of a wrap - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING - const uint16_t active_fifo_depth = (audio->rx_supp_ff_sz_max / audio->n_bytes_per_sample_rx) * audio->n_bytes_per_sample_rx; - for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) { - tu_fifo_config(&audio->rx_supp_ff[cnt], audio->rx_supp_ff[cnt].buffer, active_fifo_depth, 1, true); - } - audio->n_ff_used_rx = audio->n_channels_rx / audio->n_channels_per_ff_rx; - TU_ASSERT(audio->n_ff_used_rx <= audio->n_rx_supp_ff); - #endif - #endif - // Prepare for incoming data #if USE_LINEAR_BUFFER_RX TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); @@ -1928,10 +1187,12 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p } #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP - if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.usage == 1)// Check if usage is explicit data feedback - { + // Check if usage is explicit data feedback + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.usage == 1) { audio->ep_fb = ep_addr; audio->feedback.frame_shift = desc_ep->bInterval - 1; + // Schedule first feedback transmit + audiod_fb_send(audio); } #endif #endif// CFG_TUD_AUDIO_ENABLE_EP_OUT @@ -1971,12 +1232,7 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: { // Initialize the threshold level to half filled - uint16_t fifo_lvl_thr; - #if CFG_TUD_AUDIO_ENABLE_DECODING - fifo_lvl_thr = tu_fifo_depth(&audio->rx_supp_ff[0]) / 2; - #else - fifo_lvl_thr = tu_fifo_depth(&audio->ep_out_ff) / 2; - #endif + uint16_t fifo_lvl_thr = tu_fifo_depth(&audio->ep_out_ff) / 2; audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_lvl_thr; audio->feedback.compute.fifo_count.fifo_lvl_avg = ((uint32_t) fifo_lvl_thr) << 16; // Avoid 64bit division @@ -2179,12 +1435,11 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 (void) result; (void) xferred_bytes; + #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP // Search for interface belonging to given end point address and proceed as required for (uint8_t func_id = 0; func_id < CFG_TUD_AUDIO; func_id++) { audiod_function_t *audio = &_audiod_fct[func_id]; -#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP - // Data transmission of control interrupt finished if (audio->ep_int == ep_addr) { // According to USB2 specification, maximum payload of interrupt EP is 8 bytes on low speed, 64 bytes on full speed, and 1024 bytes on high speed (but only if an alternate interface other than 0 is used - see specification p. 49) @@ -2194,16 +1449,33 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 // I assume here, that things above are handled by PHY // All transmission is done - what remains to do is to inform job was completed - tud_audio_int_done_cb(rhport); + tud_audio_int_xfer_cb(rhport); return true; } -#endif + } + #else + (void) rhport; + (void) ep_addr; + #endif + + return false; +} + +bool audiod_xfer_isr(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + (void) xferred_bytes; + + // Search for interface belonging to given end point address and proceed as required + for (uint8_t func_id = 0; func_id < CFG_TUD_AUDIO; func_id++) + { + audiod_function_t* audio = &_audiod_fct[func_id]; #if CFG_TUD_AUDIO_ENABLE_EP_IN // Data transmission of audio packet finished - if (audio->ep_in == ep_addr && audio->alt_setting != 0) { + if (audio->ep_in == ep_addr && audio->alt_setting != 0) + { // USB 2.0, section 5.6.4, third paragraph, states "An isochronous endpoint must specify its required bus access period. However, an isochronous endpoint must be prepared to handle poll rates faster than the one specified." // That paragraph goes on to say "An isochronous IN endpoint must return a zero-length packet whenever data is requested at a faster interval than the specified interval and data is not available." // This can only be solved reliably if we load a ZLP after every IN transmission since we can not say if the host requests samples earlier than we declared! Once all samples are collected we overwrite the loaded ZLP. @@ -2213,32 +1485,25 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 // This is the only place where we can fill something into the EPs buffer! // Load new data - TU_VERIFY(audiod_tx_done_cb(rhport, audio)); - - // Transmission of ZLP is done by audiod_tx_done_cb() + audiod_tx_xfer_isr(rhport, audio, (uint16_t) xferred_bytes); return true; } #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT - // New audio packet received - if (audio->ep_out == ep_addr) { - TU_VERIFY(audiod_rx_done_cb(rhport, audio, (uint16_t) xferred_bytes)); + if (audio->ep_out == ep_addr) + { + audiod_rx_xfer_isr(rhport, audio, (uint16_t) xferred_bytes); return true; } - - #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP // Transmission of feedback EP finished if (audio->ep_fb == ep_addr) { - tud_audio_fb_done_cb(func_id); - // Schedule a transmit with the new value if EP is not busy - if (usbd_edpt_claim(rhport, audio->ep_fb)) { - // Schedule next transmission - value is changed bytud_audio_n_fb_set() in the meantime or the old value gets sent - return audiod_fb_send(audio); - } + // Schedule next transmission - value is changed bytud_audio_n_fb_set() in the meantime or the old value gets sent + audiod_fb_send(audio); + return true; } #endif #endif @@ -2299,11 +1564,6 @@ static void audiod_fb_fifo_count_update(audiod_function_t *audio, uint16_t lvl_n if (feedback > audio->feedback.max_value) feedback = audio->feedback.max_value; if (feedback < audio->feedback.min_value) feedback = audio->feedback.min_value; audio->feedback.value = feedback; - - // Schedule a transmit with the new value if EP is not busy - this triggers repetitive scheduling of the feedback value - if (usbd_edpt_claim(audio->rhport, audio->ep_fb)) { - audiod_fb_send(audio); - } } uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles) { @@ -2345,11 +1605,6 @@ bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback) { _audiod_fct[func_id].feedback.value = feedback; - // Schedule a transmit with the new value if EP is not busy - this triggers repetitive scheduling of the feedback value - if (usbd_edpt_claim(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_fb)) { - return audiod_fb_send(&_audiod_fct[func_id]); - } - return true; } #endif @@ -2555,86 +1810,22 @@ static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id) { return false; } -#if (CFG_TUD_AUDIO_ENABLE_EP_IN && (CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL || CFG_TUD_AUDIO_ENABLE_ENCODING)) || (CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING) -// p_desc points to the AS interface of alternate setting zero -// itf is the interface number of the corresponding interface - we check if the interface belongs to EP in or EP out to see if it is a TX or RX parameter -// Currently, only AS interfaces with an EP (in or out) are supposed to be parsed for! -static void audiod_parse_for_AS_params(audiod_function_t *audio, uint8_t const *p_desc, uint8_t const *p_desc_end, uint8_t const as_itf) { - #if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT - if (as_itf != audio->ep_in_as_intf_num && as_itf != audio->ep_out_as_intf_num) return;// Abort, this interface has no EP, this driver does not support this currently - #endif - #if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_EP_OUT - if (as_itf != audio->ep_in_as_intf_num) return; - #endif - #if !CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT - if (as_itf != audio->ep_out_as_intf_num) return; - #endif +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +static void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc) { p_desc = tu_desc_next(p_desc);// Exclude standard AS interface descriptor of current alternate interface descriptor - // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning - while (p_desc_end - p_desc > 0) { - // Abort if follow up descriptor is a new standard interface descriptor - indicates the last AS descriptor was already finished - if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) break; - - // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format type and format and also to get number of physical channels - if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_AS_GENERAL) { - #if CFG_TUD_AUDIO_ENABLE_EP_IN - if (as_itf == audio->ep_in_as_intf_num) { - audio->n_channels_tx = ((audio_desc_cs_as_interface_t const *) p_desc)->bNrChannels; - audio->format_type_tx = (audio_format_type_t) (((audio_desc_cs_as_interface_t const *) p_desc)->bFormatType); - - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING - audio->format_type_I_tx = (audio_data_format_type_I_t) (((audio_desc_cs_as_interface_t const *) p_desc)->bmFormats); - #endif - } - #endif - - #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - if (as_itf == audio->ep_out_as_intf_num) { - audio->n_channels_rx = ((audio_desc_cs_as_interface_t const *) p_desc)->bNrChannels; - audio->format_type_rx = ((audio_desc_cs_as_interface_t const *) p_desc)->bFormatType; - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING - audio->format_type_I_rx = ((audio_desc_cs_as_interface_t const *) p_desc)->bmFormats; - #endif - } - #endif - } + // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format type and format and also to get number of physical channels + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_AS_GENERAL) { + audio->n_channels_tx = ((audio_desc_cs_as_interface_t const *) p_desc)->bNrChannels; + audio->format_type_tx = (audio_format_type_t) (((audio_desc_cs_as_interface_t const *) p_desc)->bFormatType); // Look for a Type I Format Type Descriptor(2.3.1.6 - Audio Formats) - #if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING || CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL || CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + p_desc = tu_desc_next(p_desc); if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_FORMAT_TYPE && ((audio_desc_type_I_format_t const *) p_desc)->bFormatType == AUDIO_FORMAT_TYPE_I) { - #if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT - if (as_itf != audio->ep_in_as_intf_num && as_itf != audio->ep_out_as_intf_num) break;// Abort loop, this interface has no EP, this driver does not support this currently - #endif - #if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_EP_OUT - if (as_itf != audio->ep_in_as_intf_num) break; - #endif - #if !CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT - if (as_itf != audio->ep_out_as_intf_num) break; - #endif - - #if CFG_TUD_AUDIO_ENABLE_EP_IN - if (as_itf == audio->ep_in_as_intf_num) { - audio->n_bytes_per_sample_tx = ((audio_desc_type_I_format_t const *) p_desc)->bSubslotSize; - } - #endif - - #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - if (as_itf == audio->ep_out_as_intf_num) { - audio->n_bytes_per_sample_rx = ((audio_desc_type_I_format_t const *) p_desc)->bSubslotSize; - } - #endif + audio->n_bytes_per_sample_tx = ((audio_desc_type_I_format_t const *) p_desc)->bSubslotSize; } - #endif - - // Other format types are not supported yet - - p_desc = tu_desc_next(p_desc); } } -#endif - -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL static bool audiod_calc_tx_packet_sz(audiod_function_t *audio) { TU_VERIFY(audio->format_type_tx == AUDIO_FORMAT_TYPE_I); diff --git a/src/class/audio/audio_device.h b/src/class/audio/audio_device.h index 0a7bff212..6bcedc88c 100644 --- a/src/class/audio/audio_device.h +++ b/src/class/audio/audio_device.h @@ -26,8 +26,8 @@ * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_AUDIO_DEVICE_H_ -#define _TUSB_AUDIO_DEVICE_H_ +#ifndef TUSB_AUDIO_DEVICE_H_ +#define TUSB_AUDIO_DEVICE_H_ #include "audio.h" @@ -206,153 +206,6 @@ // Audio control interrupt EP - 6 Bytes according to UAC 2 specification (p. 74) #define CFG_TUD_AUDIO_INTERRUPT_EP_SZ 6 -// Use software encoding/decoding - -// The software coding feature of the driver is not mandatory. It is useful if, for instance, you have two I2S streams which need to be interleaved -// into a single PCM stream as SAMPLE_1 | SAMPLE_2 | SAMPLE_3 | SAMPLE_4. -// -// Currently, only PCM type I encoding/decoding is supported! -// -// If the coding feature is to be used, support FIFOs need to be configured. Their sizes and numbers are defined below. - -// Encoding/decoding is done in software and thus time consuming. If you can encode/decode your stream more efficiently do not use the -// support FIFOs but write/read directly into/from the EP_X_SW_BUFFER_FIFOs using -// - tud_audio_n_write() or -// - tud_audio_n_read(). -// To write/read to/from the support FIFOs use -// - tud_audio_n_write_support_ff() or -// - tud_audio_n_read_support_ff(). -// -// The encoding/decoding format type done is defined below. -// -// The encoding/decoding starts when the private callback functions -// - audio_tx_done_cb() -// - audio_rx_done_cb() -// are invoked. If support FIFOs are used, the corresponding encoding/decoding functions are called from there. -// Once encoding/decoding is done the result is put directly into the EP_X_SW_BUFFER_FIFOs. You can use the public callback functions -// - tud_audio_tx_done_pre_load_cb() or tud_audio_tx_done_post_load_cb() -// - tud_audio_rx_done_pre_read_cb() or tud_audio_rx_done_post_read_cb() -// if you want to get informed what happened. -// -// If you don't use the support FIFOs you may use the public callback functions -// - tud_audio_tx_done_pre_load_cb() or tud_audio_tx_done_post_load_cb() -// - tud_audio_rx_done_pre_read_cb() or tud_audio_rx_done_post_read_cb() -// to write/read from/into the EP_X_SW_BUFFER_FIFOs at the right time. -// -// If you need a different encoding which is not support so far implement it in the -// - audio_tx_done_cb() -// - audio_rx_done_cb() -// functions. - -// Enable encoding/decodings - for these to work, support FIFOs need to be setup in appropriate numbers and size -// The actual coding parameters of active AS alternate interface is parsed from the descriptors - -// The item size of the FIFO is always fixed to one i.e. bytes! Furthermore, the actively used FIFO depth is reconfigured such that the depth is a multiple -// of the current sample size in order to avoid samples to get split up in case of a wrap in the FIFO ring buffer (depth = (max_depth / sample_sz) * sample_sz)! -// This is important to remind in case you use DMAs! If the sample sizes changes, the DMA MUST BE RECONFIGURED just like the FIFOs for a different depth!!! - -// For PCM encoding/decoding - -#ifndef CFG_TUD_AUDIO_ENABLE_ENCODING -#define CFG_TUD_AUDIO_ENABLE_ENCODING 0 -#endif - -#ifndef CFG_TUD_AUDIO_ENABLE_DECODING -#define CFG_TUD_AUDIO_ENABLE_DECODING 0 -#endif - -// This enabling allows to save the current coding parameters e.g. # of bytes per sample etc. - TYPE_I includes common PCM encoding -#ifndef CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING -#define CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING 0 -#endif - -#ifndef CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING -#define CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING 0 -#endif - -// Type I Coding parameters not given within UAC2 descriptors -// It would be possible to allow for a more flexible setting and not fix this parameter as done below. However, this is most often not needed and kept for later if really necessary. The more flexible setting could be implemented within set_interface(), however, how the values are saved per alternate setting is to be determined! -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING -#ifndef CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX -#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO -#endif -#if CFG_TUD_AUDIO > 1 -#ifndef CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_TX -#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO -#endif -#endif -#if CFG_TUD_AUDIO > 2 -#ifndef CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_TX -#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO -#endif -#endif -#endif - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING -#ifndef CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_RX -#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO -#endif -#if CFG_TUD_AUDIO > 1 -#ifndef CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_RX -#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO -#endif -#endif -#if CFG_TUD_AUDIO > 2 -#ifndef CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_RX -#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO -#endif -#endif -#endif - -// Remaining types not support so far - -// Number of support FIFOs to set up - multiple channels can be handled by one FIFO - very common is two channels per FIFO stemming from one I2S interface -#ifndef CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO -#define CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO 0 -#endif -#ifndef CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO -#define CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO 0 -#endif -#ifndef CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO -#define CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO 0 -#endif - -#ifndef CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO -#define CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO 0 -#endif -#ifndef CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO -#define CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO 0 -#endif -#ifndef CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO -#define CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO 0 -#endif - -// Size of support FIFOs IN BYTES - if size > 0 there are as many FIFOs set up as CFG_TUD_AUDIO_FUNC_X_N_TX_SUPP_SW_FIFO and CFG_TUD_AUDIO_FUNC_X_N_RX_SUPP_SW_FIFO -#ifndef CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ -#define CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ 0 // FIFO size - minimum size: ceil(f_s/1000) * max(# of TX channels) / (# of TX support FIFOs) * max(# of bytes per sample) -#endif -#ifndef CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ -#define CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ 0 -#endif -#ifndef CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ -#define CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ 0 -#endif - -#ifndef CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ -#define CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ 0 // FIFO size - minimum size: ceil(f_s/1000) * max(# of RX channels) / (# of RX support FIFOs) * max(# of bytes per sample) -#endif -#ifndef CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ -#define CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ 0 -#endif -#ifndef CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ -#define CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ 0 -#endif - -//static_assert(sizeof(tud_audio_desc_lengths) != CFG_TUD_AUDIO, "Supply audio function descriptor pack length!"); - -// Supported types of this driver: -// AUDIO_DATA_FORMAT_TYPE_I_PCM - Required definitions: CFG_TUD_AUDIO_N_CHANNELS and CFG_TUD_AUDIO_BYTES_PER_CHANNEL - #ifdef __cplusplus extern "C" { #endif @@ -366,75 +219,41 @@ extern "C" { // Application API (Multiple Interfaces) // CFG_TUD_AUDIO > 1 //--------------------------------------------------------------------+ -bool tud_audio_n_mounted (uint8_t func_id); - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING -uint16_t tud_audio_n_available (uint8_t func_id); -uint16_t tud_audio_n_read (uint8_t func_id, void* buffer, uint16_t bufsize); -bool tud_audio_n_clear_ep_out_ff (uint8_t func_id); // Delete all content in the EP OUT FIFO -tu_fifo_t* tud_audio_n_get_ep_out_ff (uint8_t func_id); -#endif +bool tud_audio_n_mounted(uint8_t func_id); -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING -bool tud_audio_n_clear_rx_support_ff (uint8_t func_id, uint8_t ff_idx); // Delete all content in the support RX FIFOs -uint16_t tud_audio_n_available_support_ff (uint8_t func_id, uint8_t ff_idx); -uint16_t tud_audio_n_read_support_ff (uint8_t func_id, uint8_t ff_idx, void* buffer, uint16_t bufsize); -tu_fifo_t* tud_audio_n_get_rx_support_ff (uint8_t func_id, uint8_t ff_idx); -#endif - -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING -uint16_t tud_audio_n_write (uint8_t func_id, const void * data, uint16_t len); -bool tud_audio_n_clear_ep_in_ff (uint8_t func_id); // Delete all content in the EP IN FIFO -tu_fifo_t* tud_audio_n_get_ep_in_ff (uint8_t func_id); +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +uint16_t tud_audio_n_available (uint8_t func_id); +uint16_t tud_audio_n_read (uint8_t func_id, void* buffer, uint16_t bufsize); +bool tud_audio_n_clear_ep_out_ff (uint8_t func_id); +tu_fifo_t* tud_audio_n_get_ep_out_ff (uint8_t func_id); #endif -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING -uint16_t tud_audio_n_flush_tx_support_ff (uint8_t func_id); // Force all content in the support TX FIFOs to be written into EP SW FIFO -bool tud_audio_n_clear_tx_support_ff (uint8_t func_id, uint8_t ff_idx); -uint16_t tud_audio_n_write_support_ff (uint8_t func_id, uint8_t ff_idx, const void * data, uint16_t len); -tu_fifo_t* tud_audio_n_get_tx_support_ff (uint8_t func_id, uint8_t ff_idx); +#if CFG_TUD_AUDIO_ENABLE_EP_IN +uint16_t tud_audio_n_write (uint8_t func_id, const void * data, uint16_t len); +bool tud_audio_n_clear_ep_in_ff (uint8_t func_id); +tu_fifo_t* tud_audio_n_get_ep_in_ff (uint8_t func_id); #endif #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP bool tud_audio_int_n_write (uint8_t func_id, const audio_interrupt_data_t * data); #endif - //--------------------------------------------------------------------+ // Application API (Interface0) //--------------------------------------------------------------------+ - static inline bool tud_audio_mounted (void); -// RX API - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING -static inline uint16_t tud_audio_available (void); -static inline bool tud_audio_clear_ep_out_ff (void); // Delete all content in the EP OUT FIFO -static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); -static inline tu_fifo_t* tud_audio_get_ep_out_ff (void); -#endif - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING -static inline bool tud_audio_clear_rx_support_ff (uint8_t ff_idx); -static inline uint16_t tud_audio_available_support_ff (uint8_t ff_idx); -static inline uint16_t tud_audio_read_support_ff (uint8_t ff_idx, void* buffer, uint16_t bufsize); -static inline tu_fifo_t* tud_audio_get_rx_support_ff (uint8_t ff_idx); -#endif - -// TX API - -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING -static inline uint16_t tud_audio_write (const void * data, uint16_t len); -static inline bool tud_audio_clear_ep_in_ff (void); -static inline tu_fifo_t* tud_audio_get_ep_in_ff (void); +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +static inline uint16_t tud_audio_available (void); +static inline bool tud_audio_clear_ep_out_ff (void); +static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); +static inline tu_fifo_t* tud_audio_get_ep_out_ff (void); #endif -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING -static inline uint16_t tud_audio_flush_tx_support_ff (void); -static inline uint16_t tud_audio_clear_tx_support_ff (uint8_t ff_idx); -static inline uint16_t tud_audio_write_support_ff (uint8_t ff_idx, const void * data, uint16_t len); -static inline tu_fifo_t* tud_audio_get_tx_support_ff (uint8_t ff_idx); +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static inline uint16_t tud_audio_write (const void * data, uint16_t len); +static inline bool tud_audio_clear_ep_in_ff (void); +static inline tu_fifo_t* tud_audio_get_ep_in_ff (void); #endif // INT CTR API @@ -456,18 +275,18 @@ bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_req //--------------------------------------------------------------------+ #if CFG_TUD_AUDIO_ENABLE_EP_IN -bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting); -bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting); +// Invoked in ISR context once an audio packet was sent successfully. +// Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), call tud_audio_write() in I2S receive callback. +bool tud_audio_tx_done_isr(uint8_t rhport, uint16_t n_bytes_sent, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting); #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT -bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting); -bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting); +// Invoked in ISR context once an audio packet was received successfully. +// Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), call tud_audio_read() in I2S transmit callback. +bool tud_audio_rx_done_isr(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting); #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP -void tud_audio_fb_done_cb(uint8_t func_id); - // Note about feedback calculation // @@ -562,7 +381,10 @@ void tud_audio_int_done_cb(uint8_t rhport); bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); // Invoked when audio set interface request received which closes an EP -bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request); +bool tud_audio_set_itf_close_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// backward compatible for typo +#define tud_audio_set_itf_close_EP_cb tud_audio_set_itf_close_ep_cb // Invoked when audio class specific set request received for an EP bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); @@ -586,120 +408,56 @@ bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * // Inline Functions //--------------------------------------------------------------------+ -static inline bool tud_audio_mounted(void) -{ +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_mounted(void) { return tud_audio_n_mounted(0); } -// RX API - -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_ENABLE_EP_OUT -static inline uint16_t tud_audio_available(void) -{ +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_available(void) { return tud_audio_n_available(0); } -static inline uint16_t tud_audio_read(void* buffer, uint16_t bufsize) -{ +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_read(void* buffer, uint16_t bufsize) { return tud_audio_n_read(0, buffer, bufsize); } -static inline bool tud_audio_clear_ep_out_ff(void) -{ +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_out_ff(void) { return tud_audio_n_clear_ep_out_ff(0); } -static inline tu_fifo_t* tud_audio_get_ep_out_ff(void) -{ +TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_out_ff(void) { return tud_audio_n_get_ep_out_ff(0); } #endif -#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING - -static inline bool tud_audio_clear_rx_support_ff(uint8_t ff_idx) -{ - return tud_audio_n_clear_rx_support_ff(0, ff_idx); -} - -static inline uint16_t tud_audio_available_support_ff(uint8_t ff_idx) -{ - return tud_audio_n_available_support_ff(0, ff_idx); -} - -static inline uint16_t tud_audio_read_support_ff(uint8_t ff_idx, void* buffer, uint16_t bufsize) -{ - return tud_audio_n_read_support_ff(0, ff_idx, buffer, bufsize); -} - -static inline tu_fifo_t* tud_audio_get_rx_support_ff(uint8_t ff_idx) -{ - return tud_audio_n_get_rx_support_ff(0, ff_idx); -} - -#endif - -// TX API - -#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_ENABLE_EP_IN -static inline uint16_t tud_audio_write(const void * data, uint16_t len) -{ +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_write(const void * data, uint16_t len) { return tud_audio_n_write(0, data, len); } -static inline bool tud_audio_clear_ep_in_ff(void) -{ +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_in_ff(void) { return tud_audio_n_clear_ep_in_ff(0); } -static inline tu_fifo_t* tud_audio_get_ep_in_ff(void) -{ +TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_in_ff(void) { return tud_audio_n_get_ep_in_ff(0); } #endif -#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING - -static inline uint16_t tud_audio_flush_tx_support_ff(void) -{ - return tud_audio_n_flush_tx_support_ff(0); -} - -static inline uint16_t tud_audio_clear_tx_support_ff(uint8_t ff_idx) -{ - return tud_audio_n_clear_tx_support_ff(0, ff_idx); -} - -static inline uint16_t tud_audio_write_support_ff(uint8_t ff_idx, const void * data, uint16_t len) -{ - return tud_audio_n_write_support_ff(0, ff_idx, data, len); -} - -static inline tu_fifo_t* tud_audio_get_tx_support_ff(uint8_t ff_idx) -{ - return tud_audio_n_get_tx_support_ff(0, ff_idx); -} - -#endif - #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP -static inline bool tud_audio_int_write(const audio_interrupt_data_t * data) -{ +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_int_write(const audio_interrupt_data_t * data) { return tud_audio_int_n_write(0, data); } #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP - -static inline bool tud_audio_fb_set(uint32_t feedback) -{ +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_fb_set(uint32_t feedback) { return tud_audio_n_fb_set(0, feedback); } - #endif //--------------------------------------------------------------------+ @@ -711,6 +469,7 @@ void audiod_reset (uint8_t rhport); uint16_t audiod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); bool audiod_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); +bool audiod_xfer_isr (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); void audiod_sof_isr (uint8_t rhport, uint32_t frame_count); #ifdef __cplusplus diff --git a/src/class/cdc/cdc.h b/src/class/cdc/cdc.h index 5cbd658fe..10ba16a7c 100644 --- a/src/class/cdc/cdc.h +++ b/src/class/cdc/cdc.h @@ -192,6 +192,11 @@ typedef enum { CDC_LINE_CODING_STOP_BITS_2 = 2, // 2 bits } cdc_line_coding_stopbits_t; +#define CDC_LINE_CODING_STOP_BITS_TEXT(STOP_BITS) ( \ + STOP_BITS == CDC_LINE_CODING_STOP_BITS_1 ? "1" : \ + STOP_BITS == CDC_LINE_CODING_STOP_BITS_1_5 ? "1.5" : \ + STOP_BITS == CDC_LINE_CODING_STOP_BITS_2 ? "2" : "?" ) + // TODO Backward compatible for typos. Maybe removed in the future release #define CDC_LINE_CONDING_STOP_BITS_1 CDC_LINE_CODING_STOP_BITS_1 #define CDC_LINE_CONDING_STOP_BITS_1_5 CDC_LINE_CODING_STOP_BITS_1_5 @@ -205,22 +210,31 @@ typedef enum { CDC_LINE_CODING_PARITY_SPACE = 4, } cdc_line_coding_parity_t; +#define CDC_LINE_CODING_PARITY_CHAR(PARITY) ( \ + PARITY == CDC_LINE_CODING_PARITY_NONE ? 'N' : \ + PARITY == CDC_LINE_CODING_PARITY_ODD ? 'O' : \ + PARITY == CDC_LINE_CODING_PARITY_EVEN ? 'E' : \ + PARITY == CDC_LINE_CODING_PARITY_MARK ? 'M' : \ + PARITY == CDC_LINE_CODING_PARITY_SPACE ? 'S' : '?' ) + //--------------------------------------------------------------------+ // Management Element Notification (Notification Endpoint) //--------------------------------------------------------------------+ +#define CDC_REQ_TYPE_NOTIF 0xA1 ///< Direction IN; Type Class; Recipient Interface + /// 6.3 Notification Codes typedef enum { - CDC_NOTIF_NETWORK_CONNECTION = 0x00, ///< This notification allows the device to notify the host about network connection status. - CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, ///< This notification allows the device to notify the hostthat a response is available. This response can be retrieved with a subsequent \ref CDC_REQUEST_GET_ENCAPSULATED_RESPONSE request. + CDC_NOTIF_NETWORK_CONNECTION = 0x00, // notify the host about network connection status. + CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, // notify the host that a response is available. CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, CDC_NOTIF_RING_DETECT = 0x09, CDC_NOTIF_SERIAL_STATE = 0x20, CDC_NOTIF_CALL_STATE_CHANGE = 0x28, CDC_NOTIF_LINE_STATE_CHANGE = 0x29, - CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A, ///< This notification allows the device to inform the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred + CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A, // notify the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred CDC_NOTIF_MDLM_SEMANTIC_MODEL_NOTIFICATION = 0x40, -}cdc_notification_request_t; +} cdc_notify_request_t; //--------------------------------------------------------------------+ // Class Specific Functional Descriptor (Communication Interface) @@ -231,8 +245,7 @@ TU_ATTR_PACKED_BEGIN TU_ATTR_BIT_FIELD_ORDER_BEGIN /// Header Functional Descriptor (Communication Interface) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUNC_DESC_ @@ -240,8 +253,7 @@ typedef struct TU_ATTR_PACKED }cdc_desc_func_header_t; /// Union Functional Descriptor (Communication Interface) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -259,14 +271,13 @@ typedef struct TU_ATTR_PACKED } /// Country Selection Functional Descriptor (Communication Interface) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ uint8_t iCountryCodeRelDate ; ///< Index of a string giving the release date for the implemented ISO 3166 Country Codes. uint16_t wCountryCode ; ///< Country code in the format as defined in [ISO3166], release date as specified inoffset 3 for the first supported country. -}cdc_desc_func_country_selection_t; +} cdc_desc_func_country_selection_t; #define cdc_desc_func_country_selection_n_t(no_country) \ struct TU_ATTR_PACKED { \ @@ -283,8 +294,7 @@ typedef struct TU_ATTR_PACKED /// \brief Call Management Functional Descriptor /// \details This functional descriptor describes the processing of calls for the Communications Class interface. -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -298,8 +308,7 @@ typedef struct TU_ATTR_PACKED uint8_t bDataInterface; }cdc_desc_func_call_management_t; -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t support_comm_request : 1; ///< Device supports the request combination of Set_Comm_Feature, Clear_Comm_Feature, and Get_Comm_Feature. uint8_t support_line_request : 1; ///< Device supports the request combination of Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, and the notification Serial_State. uint8_t support_send_break : 1; ///< Device supports the request Send_Break @@ -311,8 +320,7 @@ TU_VERIFY_STATIC(sizeof(cdc_acm_capability_t) == 1, "mostly problem with compile /// Abstract Control Management Functional Descriptor /// This functional descriptor describes the commands supported by by the Communications Class interface with SubClass code of \ref CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -321,8 +329,7 @@ typedef struct TU_ATTR_PACKED /// \brief Direct Line Management Functional Descriptor /// \details This functional descriptor describes the commands supported by the Communications Class interface with SubClass code of \ref CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -384,16 +391,14 @@ typedef struct TU_ATTR_PACKED }cdc_desc_func_telephone_call_state_reporting_capabilities_t; // TODO remove -static inline uint8_t cdc_functional_desc_typeof(uint8_t const * p_desc) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t cdc_functional_desc_typeof(uint8_t const * p_desc) { return p_desc[2]; } //--------------------------------------------------------------------+ // Requests //--------------------------------------------------------------------+ -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint32_t bit_rate; uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space @@ -402,15 +407,58 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC(sizeof(cdc_line_coding_t) == 7, "size is not correct"); -typedef struct TU_ATTR_PACKED -{ - uint16_t dtr : 1; - uint16_t rts : 1; - uint16_t : 6; - uint16_t : 8; +typedef union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint8_t dtr : 1; + uint8_t rts : 1; + uint8_t : 6; + }; + uint8_t value; } cdc_line_control_state_t; -TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 2, "size is not correct"); +TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 1, "size is not correct"); + +//--------------------------------------------------------------------+ +// Notifications +//--------------------------------------------------------------------+ +// PSTN 1.2 section 6.5.4 table 31 +typedef union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint16_t bRxCarrier : 1; // DCD + uint16_t bTxCarrier : 1; // DSR + uint16_t bBreak : 1; // Break Detected + uint16_t bRingSignal : 1; + uint16_t bFraming : 1; + uint16_t bParity : 1; + uint16_t bOverRun : 1; + uint16_t : 9; + }; + struct TU_ATTR_PACKED { + uint16_t dcd : 1; + uint16_t dsr : 1; + uint16_t brk : 1; + uint16_t :13; + }; + uint16_t value; +} cdc_notify_uart_state_t; + +TU_VERIFY_STATIC(sizeof(cdc_notify_uart_state_t) == 2, "size is not correct"); + +// CDC 1.2 section 6.3.3 table 21 +typedef struct TU_ATTR_PACKED { + uint32_t upstream_bitrate; + uint32_t downstream_bitrate; +} cdc_notify_conn_speed_change_t; + +typedef struct TU_ATTR_PACKED { + tusb_control_request_t request; + union { + cdc_notify_uart_state_t serial_state; + cdc_notify_conn_speed_change_t conn_speed_change; + }; +} cdc_notify_msg_t; + +TU_VERIFY_STATIC(sizeof(cdc_notify_msg_t) == 16, "size is not correct"); TU_ATTR_PACKED_END // End of all packed definitions TU_ATTR_BIT_FIELD_ORDER_END diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c index efb672201..4e4e01eaf 100644 --- a/src/class/cdc/cdc_device.c +++ b/src/class/cdc/cdc_device.c @@ -46,13 +46,13 @@ #define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) typedef struct { + uint8_t rhport; uint8_t itf_num; - uint8_t ep_notif; uint8_t ep_in; uint8_t ep_out; - // Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send) - uint8_t line_state; + uint8_t ep_notify; + uint8_t line_state; // Bit 0: DTR, Bit 1: RTS /*------------- From this point, data is not cleared by bus reset -------------*/ char wanted_char; @@ -74,6 +74,10 @@ typedef struct { typedef struct { TUD_EPBUF_DEF(epout, CFG_TUD_CDC_EP_BUFSIZE); TUD_EPBUF_DEF(epin, CFG_TUD_CDC_EP_BUFSIZE); + + #if CFG_TUD_CDC_NOTIFY + TUD_EPBUF_TYPE_DEF(cdc_notify_msg_t, epnotify); + #endif } cdcd_epbuf_t; //--------------------------------------------------------------------+ @@ -82,7 +86,7 @@ typedef struct { static cdcd_interface_t _cdcd_itf[CFG_TUD_CDC]; CFG_TUD_MEM_SECTION static cdcd_epbuf_t _cdcd_epbuf[CFG_TUD_CDC]; -static tud_cdc_configure_fifo_t _cdcd_fifo_cfg; +static tud_cdc_configure_t _cdcd_cfg = TUD_CDC_CONFIGURE_DEFAULT(); static bool _prep_out_transaction(uint8_t itf) { const uint8_t rhport = 0; @@ -101,7 +105,7 @@ static bool _prep_out_transaction(uint8_t itf) { TU_VERIFY(available >= CFG_TUD_CDC_EP_BUFSIZE); // claim endpoint - TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_out)); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_out)); // fifo can be changed before endpoint is claimed available = tu_fifo_remaining(&p_cdc->rx_ff); @@ -110,7 +114,7 @@ static bool _prep_out_transaction(uint8_t itf) { return usbd_edpt_xfer(rhport, p_cdc->ep_out, p_epbuf->epout, CFG_TUD_CDC_EP_BUFSIZE); } else { // Release endpoint since we don't make any transfer - usbd_edpt_release(rhport, p_cdc->ep_out); + usbd_edpt_release(p_cdc->rhport, p_cdc->ep_out); return false; } } @@ -118,10 +122,9 @@ static bool _prep_out_transaction(uint8_t itf) { //--------------------------------------------------------------------+ // APPLICATION API //--------------------------------------------------------------------+ - -bool tud_cdc_configure_fifo(const tud_cdc_configure_fifo_t* cfg) { - TU_VERIFY(cfg); - _cdcd_fifo_cfg = (*cfg); +bool tud_cdc_configure(const tud_cdc_configure_t* driver_cfg) { + TU_VERIFY(driver_cfg); + _cdcd_cfg = *driver_cfg; return true; } @@ -142,6 +145,42 @@ void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding) { (*coding) = _cdcd_itf[itf].line_coding; } +#if CFG_TUD_CDC_NOTIFY +bool tud_cdc_n_notify_uart_state (uint8_t itf, const cdc_notify_uart_state_t *state) { + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; + TU_VERIFY(tud_ready() && p_cdc->ep_notify != 0); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_notify)); + + cdc_notify_msg_t* notify_msg = &p_epbuf->epnotify; + notify_msg->request.bmRequestType = CDC_REQ_TYPE_NOTIF; + notify_msg->request.bRequest = CDC_NOTIF_SERIAL_STATE; + notify_msg->request.wValue = 0; + notify_msg->request.wIndex = p_cdc->itf_num; + notify_msg->request.wLength = sizeof(cdc_notify_uart_state_t); + notify_msg->serial_state = *state; + + return usbd_edpt_xfer(p_cdc->rhport, p_cdc->ep_notify, (uint8_t *)notify_msg, 8 + sizeof(cdc_notify_uart_state_t)); +} + +bool tud_cdc_n_notify_conn_speed_change(uint8_t itf, const cdc_notify_conn_speed_change_t* conn_speed_change) { + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; + TU_VERIFY(tud_ready() && p_cdc->ep_notify != 0); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_notify)); + + cdc_notify_msg_t* notify_msg = &p_epbuf->epnotify; + notify_msg->request.bmRequestType = CDC_REQ_TYPE_NOTIF; + notify_msg->request.bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE; + notify_msg->request.wValue = 0; + notify_msg->request.wIndex = p_cdc->itf_num; + notify_msg->request.wLength = sizeof(cdc_notify_conn_speed_change_t); + notify_msg->conn_speed_change = *conn_speed_change; + + return usbd_edpt_xfer(p_cdc->rhport, p_cdc->ep_notify, (uint8_t *)notify_msg, 8 + sizeof(cdc_notify_conn_speed_change_t)); +} +#endif + void tud_cdc_n_set_wanted_char(uint8_t itf, char wanted) { _cdcd_itf[itf].wanted_char = wanted; } @@ -175,7 +214,7 @@ void tud_cdc_n_read_flush(uint8_t itf) { //--------------------------------------------------------------------+ uint32_t tud_cdc_n_write(uint8_t itf, const void* buffer, uint32_t bufsize) { cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; - uint16_t ret = tu_fifo_write_n(&p_cdc->tx_ff, buffer, (uint16_t) TU_MIN(bufsize, UINT16_MAX)); + uint16_t wr_count = tu_fifo_write_n(&p_cdc->tx_ff, buffer, (uint16_t) TU_MIN(bufsize, UINT16_MAX)); // flush if queue more than packet size if (tu_fifo_count(&p_cdc->tx_ff) >= BULK_PACKET_SIZE @@ -186,36 +225,31 @@ uint32_t tud_cdc_n_write(uint8_t itf, const void* buffer, uint32_t bufsize) { tud_cdc_n_write_flush(itf); } - return ret; + return wr_count; } uint32_t tud_cdc_n_write_flush(uint8_t itf) { cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; - - // Skip if usb is not ready yet - TU_VERIFY(tud_ready(), 0); + TU_VERIFY(tud_ready(), 0); // Skip if usb is not ready yet // No data to send if (!tu_fifo_count(&p_cdc->tx_ff)) { return 0; } - const uint8_t rhport = 0; - - // Claim the endpoint - TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_in), 0); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_in), 0); // Claim the endpoint // Pull data from FIFO const uint16_t count = tu_fifo_read_n(&p_cdc->tx_ff, p_epbuf->epin, CFG_TUD_CDC_EP_BUFSIZE); if (count) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_cdc->ep_in, p_epbuf->epin, count), 0); + TU_ASSERT(usbd_edpt_xfer(p_cdc->rhport, p_cdc->ep_in, p_epbuf->epin, count), 0); return count; } else { // Release endpoint since we don't make any transfer // Note: data is dropped if terminal is not connected - usbd_edpt_release(rhport, p_cdc->ep_in); + usbd_edpt_release(p_cdc->rhport, p_cdc->ep_in); return 0; } } @@ -233,8 +267,6 @@ bool tud_cdc_n_write_clear(uint8_t itf) { //--------------------------------------------------------------------+ void cdcd_init(void) { tu_memclr(_cdcd_itf, sizeof(_cdcd_itf)); - tu_memclr(&_cdcd_fifo_cfg, sizeof(_cdcd_fifo_cfg)); - for (uint8_t i = 0; i < CFG_TUD_CDC; i++) { cdcd_interface_t* p_cdc = &_cdcd_itf[i]; @@ -249,10 +281,10 @@ void cdcd_init(void) { // Config RX fifo tu_fifo_config(&p_cdc->rx_ff, p_cdc->rx_ff_buf, TU_ARRAY_SIZE(p_cdc->rx_ff_buf), 1, false); - // Config TX fifo as overwritable at initialization and will be changed to non-overwritable - // if terminal supports DTR bit. Without DTR we do not know if data is actually polled by terminal. - // In this way, the most current data is prioritized. - tu_fifo_config(&p_cdc->tx_ff, p_cdc->tx_ff_buf, TU_ARRAY_SIZE(p_cdc->tx_ff_buf), 1, true); + // 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_fifo_config(&p_cdc->tx_ff, p_cdc->tx_ff_buf, TU_ARRAY_SIZE(p_cdc->tx_ff_buf), 1, _cdcd_cfg.tx_overwritabe_if_not_connected); #if OSAL_MUTEX_REQUIRED osal_mutex_t mutex_rd = osal_mutex_create(&p_cdc->rx_ff_mutex); @@ -294,13 +326,13 @@ void cdcd_reset(uint8_t rhport) { cdcd_interface_t* p_cdc = &_cdcd_itf[i]; tu_memclr(p_cdc, ITF_MEM_RESET_SIZE); - if (!_cdcd_fifo_cfg.rx_persistent) { + if (!_cdcd_cfg.rx_persistent) { tu_fifo_clear(&p_cdc->rx_ff); } - if (!_cdcd_fifo_cfg.tx_persistent) { + if (!_cdcd_cfg.tx_persistent) { tu_fifo_clear(&p_cdc->tx_ff); } - tu_fifo_set_overwritable(&p_cdc->tx_ff, true); + tu_fifo_set_overwritable(&p_cdc->tx_ff, _cdcd_cfg.tx_overwritabe_if_not_connected); } } @@ -321,6 +353,7 @@ uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16 TU_ASSERT(cdc_id < CFG_TUD_CDC, 0); //------------- Control Interface -------------// + p_cdc->rhport = rhport; p_cdc->itf_num = itf_desc->bInterfaceNumber; uint16_t drv_len = sizeof(tusb_desc_interface_t); @@ -335,9 +368,8 @@ uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16 if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { // notification endpoint const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; - TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); - p_cdc->ep_notif = desc_ep->bEndpointAddress; + p_cdc->ep_notify = desc_ep->bEndpointAddress; drv_len += tu_desc_len(p_desc); p_desc = tu_desc_next(p_desc); @@ -414,8 +446,12 @@ bool cdcd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_requ p_cdc->line_state = (uint8_t) request->wValue; - // Disable fifo overwriting if DTR bit is set - tu_fifo_set_overwritable(&p_cdc->tx_ff, !dtr); + // If enabled: fifo overwriting is disabled if DTR bit is set and vice versa + if (_cdcd_cfg.tx_overwritabe_if_not_connected) { + tu_fifo_set_overwritable(&p_cdc->tx_ff, !dtr); + } else { + tu_fifo_set_overwritable(&p_cdc->tx_ff, false); + } TU_LOG_DRV(" Set Control Line State: DTR = %d, RTS = %d\r\n", dtr, rts); @@ -453,7 +489,7 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_ // Identify which interface to use for (itf = 0; itf < CFG_TUD_CDC; itf++) { p_cdc = &_cdcd_itf[itf]; - if ((ep_addr == p_cdc->ep_out) || (ep_addr == p_cdc->ep_in)) { + if ((ep_addr == p_cdc->ep_out) || (ep_addr == p_cdc->ep_in) || (ep_addr == p_cdc->ep_notify)) { break; } } @@ -496,13 +532,18 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_ // xferred_bytes is multiple of EP Packet size and not zero if (!tu_fifo_count(&p_cdc->tx_ff) && xferred_bytes && (0 == (xferred_bytes & (BULK_PACKET_SIZE - 1)))) { if (usbd_edpt_claim(rhport, p_cdc->ep_in)) { - usbd_edpt_xfer(rhport, p_cdc->ep_in, NULL, 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_cdc->ep_in, NULL, 0)); } } } } - // nothing to do with notif endpoint for now + // Sent notification to host + if (ep_addr == p_cdc->ep_notify) { + if (tud_cdc_notify_complete_cb) { + tud_cdc_notify_complete_cb(itf); + } + } return true; } diff --git a/src/class/cdc/cdc_device.h b/src/class/cdc/cdc_device.h index 2d5ba2575..a34e07e1d 100644 --- a/src/class/cdc/cdc_device.h +++ b/src/class/cdc/cdc_device.h @@ -32,6 +32,10 @@ //--------------------------------------------------------------------+ // Class Driver Configuration //--------------------------------------------------------------------+ +#ifndef CFG_TUD_CDC_NOTIFY + #define CFG_TUD_CDC_NOTIFY 0 +#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 @@ -48,14 +52,24 @@ //--------------------------------------------------------------------+ // Driver Configuration //--------------------------------------------------------------------+ - typedef struct TU_ATTR_PACKED { - uint8_t rx_persistent : 1; // keep rx fifo on bus reset or disconnect - uint8_t tx_persistent : 1; // keep tx fifo on bus reset or disconnect -} tud_cdc_configure_fifo_t; + uint8_t rx_persistent : 1; // keep rx fifo data even with bus reset or disconnect + uint8_t tx_persistent : 1; // keep tx fifo data even with reset or disconnect + uint8_t tx_overwritabe_if_not_connected : 1; // if not connected, tx fifo can be overwritten +} tud_cdc_configure_t; + +#define TUD_CDC_CONFIGURE_DEFAULT() { \ + .rx_persistent = 0, \ + .tx_persistent = 0, \ + .tx_overwritabe_if_not_connected = 1, \ +} + +// Configure CDC driver behavior +bool tud_cdc_configure(const tud_cdc_configure_t* driver_cfg); -// Configure CDC FIFOs behavior -bool tud_cdc_configure_fifo(tud_cdc_configure_fifo_t const* cfg); +// Backward compatible +#define tud_cdc_configure_fifo_t tud_cdc_configure_t +#define tud_cdc_configure_fifo tud_cdc_configure //--------------------------------------------------------------------+ // Application API (Multiple Ports) i.e. CFG_TUD_CDC > 1 @@ -116,6 +130,23 @@ uint32_t tud_cdc_n_write_available(uint8_t itf); // Clear the transmit FIFO bool tud_cdc_n_write_clear(uint8_t itf); + +#if CFG_TUD_CDC_NOTIFY +// Send UART status notification: DCD, DSR etc .. +bool tud_cdc_n_notify_uart_state(uint8_t itf, const cdc_notify_uart_state_t *state); + +// Send connection speed change notification +bool tud_cdc_n_notify_conn_speed_change(uint8_t itf, const cdc_notify_conn_speed_change_t* conn_speed_change); + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_notify_uart_state(const cdc_notify_uart_state_t* state) { + return tud_cdc_n_notify_uart_state(0, state); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_notify_conn_speed_change(const cdc_notify_conn_speed_change_t* conn_speed_change) { + return tud_cdc_n_notify_conn_speed_change(0, conn_speed_change); +} +#endif + //--------------------------------------------------------------------+ // Application API (Single Port) //--------------------------------------------------------------------+ @@ -185,7 +216,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_write_clear(void) { } //--------------------------------------------------------------------+ -// Application Callback API (weak is optional) +// Application Callback API //--------------------------------------------------------------------+ // Invoked when received new data @@ -197,6 +228,9 @@ TU_ATTR_WEAK void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); // Invoked when a TX is complete and therefore space becomes available in TX buffer TU_ATTR_WEAK void tud_cdc_tx_complete_cb(uint8_t itf); +// Invoked when a notification is sent to host +TU_ATTR_WEAK void tud_cdc_notify_complete_cb(uint8_t itf); + // Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE TU_ATTR_WEAK void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 4058857c5..f9a37ed35 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -24,7 +24,7 @@ * This file is part of the TinyUSB stack. * * Contribution - * - Heiko Kuester: CH34x support + * - Heiko Kuester: add support of CH34x & PL2303, improve support of FTDI & CP210x */ #include "tusb_option.h" @@ -35,13 +35,19 @@ #include "host/usbh_pvt.h" #include "cdc_host.h" +#include "serial/ftdi_sio.h" +#include "serial/cp210x.h" +#include "serial/ch34x.h" +#include "serial/pl2303.h" // Level where CFG_TUSB_DEBUG must be at least for this driver is logged #ifndef CFG_TUH_CDC_LOG_LEVEL - #define CFG_TUH_CDC_LOG_LEVEL CFG_TUH_LOG_LEVEL + #define CFG_TUH_CDC_LOG_LEVEL 2 #endif -#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_CDC_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_CDC_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_CDC(_cdc, _format, ...) TU_LOG_DRV("[:%u:%u] CDCh %s " _format "\r\n", _cdc->daddr, _cdc->bInterfaceNumber, \ + serial_drivers[_cdc->serial_drid].name, ##__VA_ARGS__) //--------------------------------------------------------------------+ // Host CDC Interface @@ -56,30 +62,40 @@ typedef struct { uint8_t ep_notif; uint8_t serial_drid; // Serial Driver ID bool mounted; // Enumeration is complete - cdc_acm_capability_t acm_capability; - TU_ATTR_ALIGNED(4) cdc_line_coding_t line_coding; // Baudrate, stop bits, parity, data width - uint8_t line_state; // DTR (bit0), RTS (bit1) + struct { + TU_ATTR_ALIGNED(4) cdc_line_coding_t coding; // Baudrate, stop bits, parity, data width + cdc_line_control_state_t control_state; // DTR, RTS + } line, requested_line; - #if CFG_TUH_CDC_FTDI || CFG_TUH_CDC_CP210X || CFG_TUH_CDC_CH34X - cdc_line_coding_t requested_line_coding; - // 1 byte padding - #endif + tuh_xfer_cb_t user_complete_cb; // required since we handle request internally first - tuh_xfer_cb_t user_control_cb; + union { + struct { + cdc_acm_capability_t capability; + } acm; + + #if CFG_TUH_CDC_FTDI + ftdi_private_t ftdi; + #endif + + #if CFG_TUH_CDC_PL2303 + pl2303_private_t pl2303; + #endif + }; struct { tu_edpt_stream_t tx; tu_edpt_stream_t rx; uint8_t tx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE]; - uint8_t rx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE]; + uint8_t rx_ff_buf[CFG_TUH_CDC_RX_BUFSIZE]; } stream; } cdch_interface_t; typedef struct { TUH_EPBUF_DEF(tx, CFG_TUH_CDC_TX_EPSIZE); - TUH_EPBUF_DEF(rx, CFG_TUH_CDC_TX_EPSIZE); + TUH_EPBUF_DEF(rx, CFG_TUH_CDC_RX_EPSIZE); } cdch_epbuf_t; static cdch_interface_t cdch_data[CFG_TUH_CDC]; @@ -89,58 +105,70 @@ CFG_TUH_MEM_SECTION static cdch_epbuf_t cdch_epbuf[CFG_TUH_CDC]; // Serial Driver //--------------------------------------------------------------------+ +// General driver +static void cdch_process_set_config(tuh_xfer_t *xfer); +static void cdch_process_line_state_on_enum(tuh_xfer_t *xfer); // invoked after set config is processed +static void cdch_internal_control_complete(tuh_xfer_t *xfer); +static void cdch_set_line_coding_stage1_baudrate_complete(tuh_xfer_t *xfer); +static void cdch_set_line_coding_stage2_data_format_complete(tuh_xfer_t *xfer); + //------------- ACM prototypes -------------// -static bool acm_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); -static void acm_process_config(tuh_xfer_t* xfer); +static bool acm_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool acm_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void acm_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); -static bool acm_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool acm_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool acm_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool acm_set_control_line_state(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool acm_set_line_coding(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool acm_set_control_line_state(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); //------------- FTDI prototypes -------------// #if CFG_TUH_CDC_FTDI -#include "serial/ftdi_sio.h" - static uint16_t const ftdi_vid_pid_list[][2] = {CFG_TUH_CDC_FTDI_VID_PID_LIST}; +static bool ftdi_open(uint8_t daddr, const tusb_desc_interface_t * itf_desc, uint16_t max_len); +static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void ftdi_internal_control_complete(cdch_interface_t* p_cdc, tuh_xfer_t *xfer); -static bool ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); -static void ftdi_process_config(tuh_xfer_t* xfer); - -static bool ftdi_sio_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool ftdi_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool ftdi_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool ftdi_sio_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ftdi_set_baudrate(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ftdi_set_data_format(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ftdi_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); #endif //------------- CP210X prototypes -------------// #if CFG_TUH_CDC_CP210X -#include "serial/cp210x.h" - static uint16_t const cp210x_vid_pid_list[][2] = {CFG_TUH_CDC_CP210X_VID_PID_LIST}; -static bool cp210x_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); -static void cp210x_process_config(tuh_xfer_t* xfer); +static bool cp210x_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool cp210x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void cp210x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); -static bool cp210x_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool cp210x_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool cp210x_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool cp210x_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool cp210x_set_baudrate(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool cp210x_set_data_format(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool cp210x_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); #endif //------------- CH34x prototypes -------------// #if CFG_TUH_CDC_CH34X -#include "serial/ch34x.h" - static uint16_t const ch34x_vid_pid_list[][2] = {CFG_TUH_CDC_CH34X_VID_PID_LIST}; -static bool ch34x_open(uint8_t daddr, tusb_desc_interface_t const* itf_desc, uint16_t max_len); -static void ch34x_process_config(tuh_xfer_t* xfer); +static bool ch34x_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool ch34x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void ch34x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); -static bool ch34x_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool ch34x_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool ch34x_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool ch34x_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ch34x_set_baudrate(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ch34x_set_data_format(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ch34x_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +#endif + +//------------- PL2303 prototypes -------------// +#if CFG_TUH_CDC_PL2303 +static uint16_t const pl2303_vid_pid_list[][2] = {CFG_TUH_CDC_PL2303_VID_PID_LIST}; +static const pl2303_type_data_t pl2303_type_data[PL2303_TYPE_COUNT] = {PL2303_TYPE_DATA}; + +static bool pl2303_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void pl2303_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); + +static bool pl2303_set_line_coding(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool pl2303_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); #endif //------------- Common -------------// @@ -159,31 +187,48 @@ enum { SERIAL_DRIVER_CH34X, #endif +#if CFG_TUH_CDC_PL2303 + SERIAL_DRIVER_PL2303, +#endif + SERIAL_DRIVER_COUNT }; +typedef bool (*serial_driver_func_t)(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + typedef struct { uint16_t const (*vid_pid_list)[2]; uint16_t const vid_pid_count; - bool (*const open)(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); - void (*const process_set_config)(tuh_xfer_t* xfer); - bool (*const set_control_line_state)(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - bool (*const set_baudrate)(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - bool (*const set_data_format)(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - bool (*const set_line_coding)(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + bool (*const open)(uint8_t daddr, const tusb_desc_interface_t * itf_desc, uint16_t max_len); + bool (*const process_set_config)(cdch_interface_t * p_cdc, tuh_xfer_t * xfer); + void (*const request_complete)(cdch_interface_t * p_cdc, tuh_xfer_t * xfer); // internal request complete handler to update line state + + serial_driver_func_t set_control_line_state, set_baudrate, set_data_format, set_line_coding; + + #if CFG_TUSB_DEBUG && CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + const char * name; + #endif } cdch_serial_driver_t; +#if CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + #define DRIVER_NAME_DECLARE(_str) .name = _str +#else + #define DRIVER_NAME_DECLARE(_str) +#endif + // Note driver list must be in the same order as SERIAL_DRIVER enum static const cdch_serial_driver_t serial_drivers[] = { { .vid_pid_list = NULL, .vid_pid_count = 0, .open = acm_open, - .process_set_config = acm_process_config, + .process_set_config = acm_process_set_config, + .request_complete = acm_internal_control_complete, .set_control_line_state = acm_set_control_line_state, - .set_baudrate = acm_set_baudrate, - .set_data_format = acm_set_data_format, - .set_line_coding = acm_set_line_coding + .set_baudrate = acm_set_line_coding, + .set_data_format = acm_set_line_coding, + .set_line_coding = acm_set_line_coding, + DRIVER_NAME_DECLARE("ACM") }, #if CFG_TUH_CDC_FTDI @@ -191,11 +236,13 @@ static const cdch_serial_driver_t serial_drivers[] = { .vid_pid_list = ftdi_vid_pid_list, .vid_pid_count = TU_ARRAY_SIZE(ftdi_vid_pid_list), .open = ftdi_open, - .process_set_config = ftdi_process_config, - .set_control_line_state = ftdi_sio_set_modem_ctrl, - .set_baudrate = ftdi_sio_set_baudrate, + .process_set_config = ftdi_proccess_set_config, + .request_complete = ftdi_internal_control_complete, + .set_control_line_state = ftdi_set_modem_ctrl, + .set_baudrate = ftdi_set_baudrate, .set_data_format = ftdi_set_data_format, - .set_line_coding = ftdi_set_line_coding + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("FTDI") }, #endif @@ -204,11 +251,13 @@ static const cdch_serial_driver_t serial_drivers[] = { .vid_pid_list = cp210x_vid_pid_list, .vid_pid_count = TU_ARRAY_SIZE(cp210x_vid_pid_list), .open = cp210x_open, - .process_set_config = cp210x_process_config, + .process_set_config = cp210x_process_set_config, + .request_complete = cp210x_internal_control_complete, .set_control_line_state = cp210x_set_modem_ctrl, .set_baudrate = cp210x_set_baudrate, .set_data_format = cp210x_set_data_format, - .set_line_coding = cp210x_set_line_coding + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CP210x") }, #endif @@ -217,13 +266,31 @@ static const cdch_serial_driver_t serial_drivers[] = { .vid_pid_list = ch34x_vid_pid_list, .vid_pid_count = TU_ARRAY_SIZE(ch34x_vid_pid_list), .open = ch34x_open, - .process_set_config = ch34x_process_config, + .process_set_config = ch34x_process_set_config, + .request_complete = ch34x_internal_control_complete, + .set_control_line_state = ch34x_set_modem_ctrl, .set_baudrate = ch34x_set_baudrate, .set_data_format = ch34x_set_data_format, - .set_line_coding = ch34x_set_line_coding + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CH34x") }, #endif + + #if CFG_TUH_CDC_PL2303 + { + .vid_pid_list = pl2303_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(pl2303_vid_pid_list), + .open = pl2303_open, + .process_set_config = pl2303_process_set_config, + .request_complete = pl2303_internal_control_complete, + .set_control_line_state = pl2303_set_modem_ctrl, + .set_baudrate = pl2303_set_line_coding, + .set_data_format = pl2303_set_line_coding, + .set_line_coding = pl2303_set_line_coding, + DRIVER_NAME_DECLARE("PL2303") + } + #endif }; TU_VERIFY_STATIC(TU_ARRAY_SIZE(serial_drivers) == SERIAL_DRIVER_COUNT, "Serial driver count mismatch"); @@ -232,17 +299,20 @@ TU_VERIFY_STATIC(TU_ARRAY_SIZE(serial_drivers) == SERIAL_DRIVER_COUNT, "Serial d // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ -static inline cdch_interface_t* get_itf(uint8_t idx) { +TU_ATTR_ALWAYS_INLINE static inline cdch_interface_t * get_itf(uint8_t idx) { TU_ASSERT(idx < CFG_TUH_CDC, NULL); - cdch_interface_t* p_cdc = &cdch_data[idx]; - + cdch_interface_t * p_cdc = &cdch_data[idx]; return (p_cdc->daddr != 0) ? p_cdc : NULL; } +TU_ATTR_ALWAYS_INLINE static inline uint8_t get_idx_by_ptr(cdch_interface_t* p_cdc) { + return (uint8_t) (p_cdc - cdch_data); +} + static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { for(uint8_t i=0; i<CFG_TUH_CDC; i++) { - cdch_interface_t* p_cdc = &cdch_data[i]; - if ( (p_cdc->daddr == daddr) && + cdch_interface_t * p_cdc = &cdch_data[i]; + if ((p_cdc->daddr == daddr) && (ep_addr == p_cdc->ep_notif || ep_addr == p_cdc->stream.rx.ep_addr || ep_addr == p_cdc->stream.tx.ep_addr)) { return i; } @@ -251,15 +321,67 @@ static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { return TUSB_INDEX_INVALID_8; } -static cdch_interface_t* make_new_itf(uint8_t daddr, tusb_desc_interface_t const *itf_desc) { +// determine the interface from the completed transfer +static cdch_interface_t* get_itf_by_xfer(const tuh_xfer_t * xfer) { + TU_VERIFY(xfer->daddr != 0, NULL); + for(uint8_t i=0; i<CFG_TUH_CDC; i++) { + cdch_interface_t * p_cdc = &cdch_data[i]; + if (p_cdc->daddr == xfer->daddr) { + switch (p_cdc->serial_drid) { + #if CFG_TUH_CDC_CP210X + case SERIAL_DRIVER_CP210X: + #endif + case SERIAL_DRIVER_ACM: { + // Driver use wIndex for bInterfaceNumber + const uint8_t itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (p_cdc->bInterfaceNumber == itf_num) { + return p_cdc; + } + break; + } + + #if CFG_TUH_CDC_FTDI + case SERIAL_DRIVER_FTDI: { + // FTDI uses wIndex for channel number, if channel is 0 then it is the default channel + const uint8_t channel = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (p_cdc->ftdi.channel == 0 || p_cdc->ftdi.channel == channel) { + return p_cdc; + } + break; + } + #endif + + #if CFG_TUH_CDC_CH34X + case SERIAL_DRIVER_CH34X: + // ch34x has only one interface + return p_cdc; + #endif + + #if CFG_TUH_CDC_PL2303 + case SERIAL_DRIVER_PL2303: + // pl2303 has only one interface + return p_cdc; + #endif + + default: + break; + } + } + } + + return NULL; +} + +static cdch_interface_t * make_new_itf(uint8_t daddr, tusb_desc_interface_t const * itf_desc) { for(uint8_t i=0; i<CFG_TUH_CDC; i++) { if (cdch_data[i].daddr == 0) { - cdch_interface_t* p_cdc = &cdch_data[i]; + cdch_interface_t * p_cdc = &cdch_data[i]; p_cdc->daddr = daddr; p_cdc->bInterfaceNumber = itf_desc->bInterfaceNumber; p_cdc->bInterfaceSubClass = itf_desc->bInterfaceSubClass; p_cdc->bInterfaceProtocol = itf_desc->bInterfaceProtocol; - p_cdc->line_state = 0; + p_cdc->line.coding = (cdc_line_coding_t) { 0, 0, 0, 0 }; + p_cdc->line.control_state.value = 0; return p_cdc; } } @@ -267,9 +389,7 @@ static cdch_interface_t* make_new_itf(uint8_t daddr, tusb_desc_interface_t const return NULL; } -static bool open_ep_stream_pair(cdch_interface_t* p_cdc , tusb_desc_endpoint_t const *desc_ep); -static void set_config_complete(cdch_interface_t * p_cdc, uint8_t idx, uint8_t itf_num); -static void cdch_internal_control_complete(tuh_xfer_t* xfer); +static bool open_ep_stream_pair(cdch_interface_t * p_cdc , tusb_desc_endpoint_t const *desc_ep); //--------------------------------------------------------------------+ // APPLICATION API @@ -277,21 +397,20 @@ static void cdch_internal_control_complete(tuh_xfer_t* xfer); uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num) { for (uint8_t i = 0; i < CFG_TUH_CDC; i++) { - const cdch_interface_t* p_cdc = &cdch_data[i]; - if (p_cdc->daddr == daddr && p_cdc->bInterfaceNumber == itf_num) return i; + const cdch_interface_t * p_cdc = &cdch_data[i]; + if (p_cdc->daddr == daddr && p_cdc->bInterfaceNumber == itf_num) { return i; } } - return TUSB_INDEX_INVALID_8; } -bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t* info) { - cdch_interface_t* p_cdc = get_itf(idx); +bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t * info) { + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc && info); info->daddr = p_cdc->daddr; - // re-construct descriptor - tusb_desc_interface_t* desc = &info->desc; + // re-construct interface descriptor + tusb_desc_interface_t * desc = &info->desc; desc->bLength = sizeof(tusb_desc_interface_t); desc->bDescriptorType = TUSB_DESC_INTERFACE; @@ -307,31 +426,22 @@ bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t* info) { } bool tuh_cdc_mounted(uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); return p_cdc->mounted; } -bool tuh_cdc_get_dtr(uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_DTR) ? true : false; -} - -bool tuh_cdc_get_rts(uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); +bool tuh_cdc_get_control_line_state_local(uint8_t idx, uint16_t* line_state) { + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - - return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_RTS) ? true : false; + *line_state = p_cdc->line.control_state.value; + return true; } -bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding) { - cdch_interface_t* p_cdc = get_itf(idx); +bool tuh_cdc_get_line_coding_local(uint8_t idx, cdc_line_coding_t * line_coding) { + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - - *line_coding = p_cdc->line_coding; - + *line_coding = p_cdc->line.coding; return true; } @@ -339,31 +449,27 @@ bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding) // Write //--------------------------------------------------------------------+ -uint32_t tuh_cdc_write(uint8_t idx, void const* buffer, uint32_t bufsize) { - cdch_interface_t* p_cdc = get_itf(idx); +uint32_t tuh_cdc_write(uint8_t idx, void const * buffer, uint32_t bufsize) { + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - return tu_edpt_stream_write(p_cdc->daddr, &p_cdc->stream.tx, buffer, bufsize); } uint32_t tuh_cdc_write_flush(uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - return tu_edpt_stream_write_xfer(p_cdc->daddr, &p_cdc->stream.tx); } bool tuh_cdc_write_clear(uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - return tu_edpt_stream_clear(&p_cdc->stream.tx); } uint32_t tuh_cdc_write_available(uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - return tu_edpt_stream_write_available(p_cdc->daddr, &p_cdc->stream.tx); } @@ -371,29 +477,26 @@ uint32_t tuh_cdc_write_available(uint8_t idx) { // Read //--------------------------------------------------------------------+ -uint32_t tuh_cdc_read (uint8_t idx, void* buffer, uint32_t bufsize) { - cdch_interface_t* p_cdc = get_itf(idx); +uint32_t tuh_cdc_read (uint8_t idx, void * buffer, uint32_t bufsize) { + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - return tu_edpt_stream_read(p_cdc->daddr, &p_cdc->stream.rx, buffer, bufsize); } uint32_t tuh_cdc_read_available(uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - return tu_edpt_stream_read_available(&p_cdc->stream.rx); } -bool tuh_cdc_peek(uint8_t idx, uint8_t* ch) { - cdch_interface_t* p_cdc = get_itf(idx); +bool tuh_cdc_peek(uint8_t idx, uint8_t * ch) { + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); - return tu_edpt_stream_peek(&p_cdc->stream.rx, ch); } bool tuh_cdc_read_clear (uint8_t idx) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc); bool ret = tu_edpt_stream_clear(&p_cdc->stream.rx); @@ -405,218 +508,115 @@ bool tuh_cdc_read_clear (uint8_t idx) { // Control Endpoint API //--------------------------------------------------------------------+ -static void process_internal_control_complete(tuh_xfer_t* xfer, uint8_t itf_num) { - uint8_t idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t* p_cdc = get_itf(idx); - TU_ASSERT(p_cdc, ); - uint16_t const value = tu_le16toh(xfer->setup->wValue); - - if (xfer->result == XFER_RESULT_SUCCESS) { - switch (p_cdc->serial_drid) { - case SERIAL_DRIVER_ACM: - switch (xfer->setup->bRequest) { - case CDC_REQUEST_SET_CONTROL_LINE_STATE: - p_cdc->line_state = (uint8_t) value; - break; - - case CDC_REQUEST_SET_LINE_CODING: { - uint16_t const len = tu_min16(sizeof(cdc_line_coding_t), tu_le16toh(xfer->setup->wLength)); - memcpy(&p_cdc->line_coding, xfer->buffer, len); - break; - } - - default: break; - } - break; - - #if CFG_TUH_CDC_FTDI - case SERIAL_DRIVER_FTDI: - switch (xfer->setup->bRequest) { - case FTDI_SIO_MODEM_CTRL: - p_cdc->line_state = (uint8_t) value; - break; - - case FTDI_SIO_SET_BAUD_RATE: - p_cdc->line_coding.bit_rate = p_cdc->requested_line_coding.bit_rate; - break; - - default: break; - } - break; - #endif - - #if CFG_TUH_CDC_CP210X - case SERIAL_DRIVER_CP210X: - switch(xfer->setup->bRequest) { - case CP210X_SET_MHS: - p_cdc->line_state = (uint8_t) value; - break; - - case CP210X_SET_BAUDRATE: { - uint32_t baudrate; - memcpy(&baudrate, xfer->buffer, sizeof(uint32_t)); - p_cdc->line_coding.bit_rate = tu_le32toh(baudrate); - break; - } - - default: break; - } - break; - #endif - - #if CFG_TUH_CDC_CH34X - case SERIAL_DRIVER_CH34X: - switch (xfer->setup->bRequest) { - case CH34X_REQ_WRITE_REG: - // register write request - switch (value) { - case CH34X_REG16_DIVISOR_PRESCALER: - // baudrate - p_cdc->line_coding.bit_rate = p_cdc->requested_line_coding.bit_rate; - break; - - case CH32X_REG16_LCR2_LCR: - // data format - p_cdc->line_coding.stop_bits = p_cdc->requested_line_coding.stop_bits; - p_cdc->line_coding.parity = p_cdc->requested_line_coding.parity; - p_cdc->line_coding.data_bits = p_cdc->requested_line_coding.data_bits; - break; - - default: break; - } - break; - - case CH34X_REQ_MODEM_CTRL: { - // set modem controls RTS/DTR request. Note: signals are inverted - uint16_t const modem_signal = ~value; - if (modem_signal & CH34X_BIT_RTS) { - p_cdc->line_state |= CDC_CONTROL_LINE_STATE_RTS; - } else { - p_cdc->line_state &= (uint8_t) ~CDC_CONTROL_LINE_STATE_RTS; - } - - if (modem_signal & CH34X_BIT_DTR) { - p_cdc->line_state |= CDC_CONTROL_LINE_STATE_DTR; - } else { - p_cdc->line_state &= (uint8_t) ~CDC_CONTROL_LINE_STATE_DTR; - } - break; - } - - default: break; - } - break; - #endif - - default: break; - } - } - - xfer->complete_cb = p_cdc->user_control_cb; - if (xfer->complete_cb) { - xfer->complete_cb(xfer); - } -} - -// internal control complete to update state such as line state, encoding -static void cdch_internal_control_complete(tuh_xfer_t* xfer) { - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - process_internal_control_complete(xfer, itf_num); -} - bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t * p_cdc = get_itf(idx); TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - cdch_serial_driver_t const* driver = &serial_drivers[p_cdc->serial_drid]; + TU_LOG_CDC(p_cdc, "set control line state dtr = %u rts = %u", p_cdc->requested_line.control_state.dtr, p_cdc->requested_line.control_state.rts); + const cdch_serial_driver_t * driver = &serial_drivers[p_cdc->serial_drid]; - if (complete_cb) { - return driver->set_control_line_state(p_cdc, line_state, complete_cb, user_data); - } else { - // blocking - xfer_result_t result = XFER_RESULT_INVALID; - bool ret = driver->set_control_line_state(p_cdc, line_state, complete_cb, (uintptr_t) &result); + p_cdc->requested_line.control_state.value = (uint8_t) line_state; + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_control_line_state(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); - if (user_data) { - // user_data is not NULL, return result via user_data - *((xfer_result_t*) user_data) = result; - } - - TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); - p_cdc->line_state = (uint8_t) line_state; - return true; + if (!complete_cb) { + // blocking, update line state if request was successful + p_cdc->line.control_state.value = (uint8_t) line_state; } + + return true; } bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t *p_cdc = get_itf(idx); TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - cdch_serial_driver_t const* driver = &serial_drivers[p_cdc->serial_drid]; - - if (complete_cb) { - return driver->set_baudrate(p_cdc, baudrate, complete_cb, user_data); - } else { - // blocking - xfer_result_t result = XFER_RESULT_INVALID; - bool ret = driver->set_baudrate(p_cdc, baudrate, complete_cb, (uintptr_t) &result); + TU_LOG_CDC(p_cdc, "set baudrate %lu", baudrate); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; - if (user_data) { - // user_data is not NULL, return result via user_data - *((xfer_result_t*) user_data) = result; - } + p_cdc->requested_line = p_cdc->line; // keep current line coding + p_cdc->requested_line.coding.bit_rate = baudrate; + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_baudrate(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); - TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); - p_cdc->line_coding.bit_rate = baudrate; - return true; + if (!complete_cb) { + p_cdc->line.coding.bit_rate = baudrate; } + + return true; } bool tuh_cdc_set_data_format(uint8_t idx, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - cdch_interface_t* p_cdc = get_itf(idx); + cdch_interface_t *p_cdc = get_itf(idx); TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - cdch_serial_driver_t const* driver = &serial_drivers[p_cdc->serial_drid]; + TU_LOG_CDC(p_cdc, "set data format %u%c%s", + data_bits, CDC_LINE_CODING_PARITY_CHAR(parity), + CDC_LINE_CODING_STOP_BITS_TEXT(stop_bits)); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; - if (complete_cb) { - return driver->set_data_format(p_cdc, stop_bits, parity, data_bits, complete_cb, user_data); - } else { - // blocking - xfer_result_t result = XFER_RESULT_INVALID; - bool ret = driver->set_data_format(p_cdc, stop_bits, parity, data_bits, complete_cb, (uintptr_t) &result); + p_cdc->requested_line = p_cdc->line; // keep current line coding + p_cdc->requested_line.coding.stop_bits = stop_bits; + p_cdc->requested_line.coding.parity = parity; + p_cdc->requested_line.coding.data_bits = data_bits; - if (user_data) { - // user_data is not NULL, return result via user_data - *((xfer_result_t*) user_data) = result; - } + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_data_format(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); - TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); - p_cdc->line_coding.stop_bits = stop_bits; - p_cdc->line_coding.parity = parity; - p_cdc->line_coding.data_bits = data_bits; - return true; + if (!complete_cb) { + // blocking + p_cdc->line.coding.stop_bits = stop_bits; + p_cdc->line.coding.parity = parity; + p_cdc->line.coding.data_bits = data_bits; } + + return true; } -bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - cdch_interface_t* p_cdc = get_itf(idx); +bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const *line_coding, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t *p_cdc = get_itf(idx); TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - cdch_serial_driver_t const* driver = &serial_drivers[p_cdc->serial_drid]; + TU_LOG_CDC(p_cdc, "set line coding %lu %u%c%s", + line_coding->bit_rate, line_coding->data_bits, + CDC_LINE_CODING_PARITY_CHAR(line_coding->parity), + CDC_LINE_CODING_STOP_BITS_TEXT(line_coding->stop_bits)); + cdch_serial_driver_t const *driver = &serial_drivers[p_cdc->serial_drid]; + p_cdc->requested_line.coding = *line_coding; + p_cdc->user_complete_cb = complete_cb; - if ( complete_cb ) { - return driver->set_line_coding(p_cdc, line_coding, complete_cb, user_data); - } else { - // blocking - xfer_result_t result = XFER_RESULT_INVALID; - bool ret = driver->set_line_coding(p_cdc, line_coding, complete_cb, (uintptr_t) &result); + if (driver->set_line_coding) { + // driver support set_line_coding request + TU_VERIFY(driver->set_line_coding(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); - if (user_data) { - // user_data is not NULL, return result via user_data - *((xfer_result_t*) user_data) = result; + if (!complete_cb) { + p_cdc->line.coding = *line_coding; } + } else { + // driver does not support set_line_coding and need 2 stage to set baudrate and data format separately + if (complete_cb) { + // non-blocking + TU_VERIFY(driver->set_baudrate(p_cdc, cdch_set_line_coding_stage1_baudrate_complete, user_data)); + } else { + // blocking + xfer_result_t result = XFER_RESULT_INVALID; + + TU_VERIFY(driver->set_baudrate(p_cdc, NULL, (uintptr_t) &result)); + if (user_data) { + *((xfer_result_t *) user_data) = result; + } + TU_VERIFY(result == XFER_RESULT_SUCCESS); + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; // update baudrate - TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); - p_cdc->line_coding = *line_coding; - return true; + result = XFER_RESULT_INVALID; + TU_VERIFY(driver->set_data_format(p_cdc, NULL, (uintptr_t) &result)); + if (user_data) { + *((xfer_result_t *) user_data) = result; + } + TU_VERIFY(result == XFER_RESULT_SUCCESS); + p_cdc->line.coding = p_cdc->requested_line.coding; // update data format + } } + + return true; } //--------------------------------------------------------------------+ @@ -627,8 +627,8 @@ bool cdch_init(void) { TU_LOG_DRV("sizeof(cdch_interface_t) = %u\r\n", sizeof(cdch_interface_t)); tu_memclr(cdch_data, sizeof(cdch_data)); 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]; + cdch_interface_t *p_cdc = &cdch_data[i]; + cdch_epbuf_t *epbuf = &cdch_epbuf[i]; 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); @@ -643,7 +643,7 @@ bool cdch_init(void) { bool cdch_deinit(void) { for (size_t i = 0; i < CFG_TUH_CDC; i++) { - cdch_interface_t* p_cdc = &cdch_data[i]; + cdch_interface_t *p_cdc = &cdch_data[i]; tu_edpt_stream_deinit(&p_cdc->stream.tx); tu_edpt_stream_deinit(&p_cdc->stream.rx); } @@ -652,9 +652,9 @@ bool cdch_deinit(void) { void cdch_close(uint8_t daddr) { for (uint8_t idx = 0; idx < CFG_TUH_CDC; idx++) { - cdch_interface_t* p_cdc = &cdch_data[idx]; + cdch_interface_t *p_cdc = &cdch_data[idx]; if (p_cdc->daddr == daddr) { - TU_LOG_DRV(" CDCh close addr = %u index = %u\r\n", daddr, idx); + TU_LOG_CDC(p_cdc, "close"); // Invoke application callback if (tuh_cdc_umount_cb) { @@ -672,45 +672,50 @@ void cdch_close(uint8_t daddr) { bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { // TODO handle stall response, retry failed transfer ... - TU_ASSERT(event == XFER_RESULT_SUCCESS); + TU_VERIFY(event == XFER_RESULT_SUCCESS); uint8_t const idx = get_idx_by_ep_addr(daddr, ep_addr); - cdch_interface_t * p_cdc = get_itf(idx); + cdch_interface_t *p_cdc = get_itf(idx); TU_ASSERT(p_cdc); - if ( ep_addr == p_cdc->stream.tx.ep_addr ) { + if (ep_addr == p_cdc->stream.tx.ep_addr) { // invoke tx complete callback to possibly refill tx fifo if (tuh_cdc_tx_complete_cb) { tuh_cdc_tx_complete_cb(idx); } - if ( 0 == tu_edpt_stream_write_xfer(daddr, &p_cdc->stream.tx) ) { + if (0 == tu_edpt_stream_write_xfer(daddr, &p_cdc->stream.tx)) { // If there is no data left, a ZLP should be sent if: // - xferred_bytes is multiple of EP Packet size and not zero tu_edpt_stream_write_zlp_if_needed(daddr, &p_cdc->stream.tx, xferred_bytes); } - } else if ( ep_addr == p_cdc->stream.rx.ep_addr ) { + } else if (ep_addr == p_cdc->stream.rx.ep_addr) { #if CFG_TUH_CDC_FTDI - if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI && xferred_bytes > 2) { + if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI) { // FTDI reserve 2 bytes for status // uint8_t status[2] = {p_cdc->stream.rx.ep_buf[0], p_cdc->stream.rx.ep_buf[1]}; - tu_edpt_stream_read_xfer_complete_with_buf(&p_cdc->stream.rx, p_cdc->stream.rx.ep_buf+2, xferred_bytes-2); - }else + if (xferred_bytes > 2) { + tu_edpt_stream_read_xfer_complete_with_buf(&p_cdc->stream.rx, p_cdc->stream.rx.ep_buf + 2, xferred_bytes - 2); + + if (tuh_cdc_rx_cb) { + tuh_cdc_rx_cb(idx); // invoke receive callback + } + } + } else #endif { tu_edpt_stream_read_xfer_complete(&p_cdc->stream.rx, xferred_bytes); - } - // invoke receive callback - if (tuh_cdc_rx_cb) { - tuh_cdc_rx_cb(idx); + if (tuh_cdc_rx_cb) { + tuh_cdc_rx_cb(idx); // invoke receive callback + } } // prepare for next transfer if needed tu_edpt_stream_read_xfer(daddr, &p_cdc->stream.rx); - }else if ( ep_addr == p_cdc->ep_notif ) { + } else if (ep_addr == p_cdc->ep_notif) { // TODO handle notification endpoint - }else { + } else { TU_ASSERT(false); } @@ -721,7 +726,7 @@ bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t // Enumeration //--------------------------------------------------------------------+ -static bool open_ep_stream_pair(cdch_interface_t* p_cdc, tusb_desc_endpoint_t const* desc_ep) { +static bool open_ep_stream_pair(cdch_interface_t *p_cdc, tusb_desc_endpoint_t const *desc_ep) { for (size_t i = 0; i < 2; i++) { TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer); @@ -733,7 +738,7 @@ static bool open_ep_stream_pair(cdch_interface_t* p_cdc, tusb_desc_endpoint_t co tu_edpt_stream_open(&p_cdc->stream.tx, desc_ep); } - desc_ep = (tusb_desc_endpoint_t const*) tu_desc_next(desc_ep); + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); } return true; @@ -741,10 +746,9 @@ static bool open_ep_stream_pair(cdch_interface_t* p_cdc, tusb_desc_endpoint_t co bool cdch_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { (void) rhport; - // For CDC: only support ACM subclass // Note: Protocol 0xFF can be RNDIS device - if (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + if (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass) { return acm_open(daddr, itf_desc, max_len); } else if (SERIAL_DRIVER_COUNT > 1 && @@ -753,10 +757,12 @@ bool cdch_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *itf_d TU_VERIFY(tuh_vid_pid_get(daddr, &vid, &pid)); for (size_t dr = 1; dr < SERIAL_DRIVER_COUNT; dr++) { - cdch_serial_driver_t const* driver = &serial_drivers[dr]; + const cdch_serial_driver_t *driver = &serial_drivers[dr]; for (size_t i = 0; i < driver->vid_pid_count; i++) { if (driver->vid_pid_list[i][0] == vid && driver->vid_pid_list[i][1] == pid) { - return driver->open(daddr, itf_desc, max_len); + const bool ret = driver->open(daddr, itf_desc, max_len); + TU_LOG_DRV("[:%u:%u] CDCh %s open %s\r\n", daddr, itf_desc->bInterfaceNumber, driver->name, ret ? "OK" : "FAILED"); + return ret; } } } @@ -765,123 +771,173 @@ bool cdch_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *itf_d return false; } -static void set_config_complete(cdch_interface_t * p_cdc, uint8_t idx, uint8_t itf_num) { - TU_LOG_DRV("CDCh Set Configure complete\r\n"); - p_cdc->mounted = true; - if (tuh_cdc_mount_cb) { - tuh_cdc_mount_cb(idx); - } - - // Prepare for incoming data - tu_edpt_stream_read_xfer(p_cdc->daddr, &p_cdc->stream.rx); - - // notify usbh that driver enumeration is complete - usbh_driver_set_config_complete(p_cdc->daddr, itf_num); -} - bool cdch_set_config(uint8_t daddr, uint8_t itf_num) { tusb_control_request_t request; request.wIndex = tu_htole16((uint16_t) itf_num); + uint8_t const idx = tuh_cdc_itf_get_index(daddr, itf_num); + cdch_interface_t *p_cdc = get_itf(idx); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set config"); - // fake transfer to kick-off process + // fake transfer to kick-off process_set_config() tuh_xfer_t xfer; - xfer.daddr = daddr; + xfer.daddr = daddr; xfer.result = XFER_RESULT_SUCCESS; - xfer.setup = &request; - xfer.user_data = 0; // initial state + xfer.setup = &request; + xfer.user_data = 0; // initial state 0 + cdch_process_set_config(&xfer); - uint8_t const idx = tuh_cdc_itf_get_index(daddr, itf_num); - cdch_interface_t * p_cdc = get_itf(idx); - TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - - serial_drivers[p_cdc->serial_drid].process_set_config(&xfer); return true; } -//--------------------------------------------------------------------+ -// ACM -//--------------------------------------------------------------------+ +static void set_config_complete(cdch_interface_t *p_cdc, bool success) { + if (success) { + const uint8_t idx = get_idx_by_ptr(p_cdc); + p_cdc->mounted = true; + if (tuh_cdc_mount_cb) { + tuh_cdc_mount_cb(idx); + } + // Prepare for incoming data + tu_edpt_stream_read_xfer(p_cdc->daddr, &p_cdc->stream.rx); + } else { + // clear the interface entry + p_cdc->daddr = 0; + p_cdc->bInterfaceNumber = 0; + } -enum { - CONFIG_ACM_SET_CONTROL_LINE_STATE = 0, - CONFIG_ACM_SET_LINE_CODING, - CONFIG_ACM_COMPLETE, -}; + // notify usbh that driver enumeration is complete + const uint8_t itf_offset = (p_cdc->serial_drid == SERIAL_DRIVER_ACM) ? 1 : 0; + usbh_driver_set_config_complete(p_cdc->daddr, p_cdc->bInterfaceNumber + itf_offset); +} -static bool acm_open(uint8_t daddr, tusb_desc_interface_t const* itf_desc, uint16_t max_len) { - uint8_t const* p_desc_end = ((uint8_t const*) itf_desc) + max_len; +static void cdch_process_set_config(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" state = %u\r\n", xfer->user_data); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; - cdch_interface_t* p_cdc = make_new_itf(daddr, itf_desc); - TU_VERIFY(p_cdc); - p_cdc->serial_drid = SERIAL_DRIVER_ACM; + if (!driver->process_set_config(p_cdc, xfer)) { + set_config_complete(p_cdc, false); + } +} - //------------- Control Interface -------------// - uint8_t const* p_desc = tu_desc_next(itf_desc); +static bool set_line_state_on_enum(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + enum { + ENUM_SET_LINE_CODING = 0, + ENUM_SET_LINE_CONTROL, + ENUM_SET_LINE_COMPLETE, + }; + const uint8_t idx = get_idx_by_ptr(p_cdc); + const uintptr_t state = xfer->user_data; - // Communication Functional Descriptors - while ((p_desc < p_desc_end) && (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc))) { - if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc)) { - // save ACM bmCapabilities - p_cdc->acm_capability = ((cdc_desc_func_acm_t const*) p_desc)->bmCapabilities; + switch (state) { + case ENUM_SET_LINE_CODING: { + #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM + #if CFG_TUH_CDC_CH34X + // ch34x already set line coding in serial init + if (p_cdc->serial_drid != SERIAL_DRIVER_CH34X) + #endif + { + const cdc_line_coding_t line_coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM; + TU_ASSERT(tuh_cdc_set_line_coding(idx, &line_coding, + cdch_process_line_state_on_enum, ENUM_SET_LINE_CONTROL)); + break; + } + #endif + TU_ATTR_FALLTHROUGH; } - p_desc = tu_desc_next(p_desc); + case ENUM_SET_LINE_CONTROL: + #ifdef CFG_TUH_CDC_LINE_CONTROL_ON_ENUM + TU_ASSERT(tuh_cdc_set_control_line_state(idx, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, + cdch_process_line_state_on_enum, ENUM_SET_LINE_COMPLETE)); + break; + #else + TU_ATTR_FALLTHROUGH; + #endif + + case ENUM_SET_LINE_COMPLETE: + set_config_complete(p_cdc, true); + break; + + default: + return false; } - // Open notification endpoint of control interface if any - if (itf_desc->bNumEndpoints == 1) { - TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)); - tusb_desc_endpoint_t const* desc_ep = (tusb_desc_endpoint_t const*) p_desc; + return true; +} - TU_ASSERT(tuh_edpt_open(daddr, desc_ep)); - p_cdc->ep_notif = desc_ep->bEndpointAddress; +static void cdch_process_line_state_on_enum(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + if (xfer->result != XFER_RESULT_SUCCESS || !set_line_state_on_enum(p_cdc, xfer)) { + set_config_complete(p_cdc, false); + } +} - p_desc = tu_desc_next(p_desc); + +static void cdch_internal_control_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" request result = %u\r\n", xfer->result); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + driver->request_complete(p_cdc, xfer); + + // Invoke application callback + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); } +} - //------------- Data Interface (if any) -------------// - if ((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && - (TUSB_CLASS_CDC_DATA == ((tusb_desc_interface_t const*) p_desc)->bInterfaceClass)) { - // next to endpoint descriptor - p_desc = tu_desc_next(p_desc); +static void cdch_set_line_coding_stage1_baudrate_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" stage1 set baudrate result = %u\r\n", xfer->result); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; - // data endpoints expected to be in pairs - TU_ASSERT(open_ep_stream_pair(p_cdc, (tusb_desc_endpoint_t const*) p_desc)); + if (xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; // update baudrate + TU_ASSERT(driver->set_data_format(p_cdc, cdch_set_line_coding_stage2_data_format_complete, xfer->user_data),); + } else { + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } } +} - return true; +static void cdch_set_line_coding_stage2_data_format_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" stage2 set data format result = %u\r\n", xfer->result); + + if (xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->line.coding = p_cdc->requested_line.coding; // update data format + } + + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } } -static void acm_process_config(tuh_xfer_t* xfer) { - uintptr_t const state = xfer->user_data; - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t* p_cdc = get_itf(idx); - TU_ASSERT(p_cdc,); +//--------------------------------------------------------------------+ +// ACM +//--------------------------------------------------------------------+ - switch (state) { - case CONFIG_ACM_SET_CONTROL_LINE_STATE: - #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM - if (p_cdc->acm_capability.support_line_request) { - TU_ASSERT(acm_set_control_line_state(p_cdc, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, acm_process_config, CONFIG_ACM_SET_LINE_CODING),); - break; - } - #endif - TU_ATTR_FALLTHROUGH; +// internal control complete to update state such as line state, encoding +static void acm_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY (xfer->result == XFER_RESULT_SUCCESS,); + const tusb_control_request_t * setup = xfer->setup; - case CONFIG_ACM_SET_LINE_CODING: - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - if (p_cdc->acm_capability.support_line_request) { - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(acm_set_line_coding(p_cdc, &line_coding, acm_process_config, CONFIG_ACM_COMPLETE),); - break; - } - #endif - TU_ATTR_FALLTHROUGH; + switch (setup->bRequest) { + case CDC_REQUEST_SET_CONTROL_LINE_STATE: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; - case CONFIG_ACM_COMPLETE: - // itf_num+1 to account for data interface as well - set_config_complete(p_cdc, idx, itf_num + 1); + case CDC_REQUEST_SET_LINE_CODING: + p_cdc->line.coding = p_cdc->requested_line.coding; break; default: @@ -889,39 +945,37 @@ static void acm_process_config(tuh_xfer_t* xfer) { } } -static bool acm_set_control_line_state(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_VERIFY(p_cdc->acm_capability.support_line_request); - TU_LOG_DRV("CDC ACM Set Control Line State\r\n"); +static bool acm_set_control_line_state(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->acm.capability.support_line_request); - tusb_control_request_t const request = { + const tusb_control_request_t request = { .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT }, .bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE, - .wValue = tu_htole16(line_state), + .wValue = tu_htole16((uint16_t) p_cdc->requested_line.control_state.value), .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), .wLength = 0 }; - p_cdc->user_control_cb = complete_cb; - tuh_xfer_t xfer = { .daddr = p_cdc->daddr, .ep_addr = 0, .setup = &request, .buffer = NULL, - .complete_cb = complete_cb ? cdch_internal_control_complete : NULL, // complete_cb is NULL for sync call + .complete_cb = complete_cb, .user_data = user_data }; - TU_ASSERT(tuh_control_xfer(&xfer)); - return true; + return tuh_control_xfer(&xfer); } -static bool acm_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_LOG_DRV("CDC ACM Set Line Conding\r\n"); +static bool acm_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->acm.capability.support_line_request); + TU_VERIFY((p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8) || + p_cdc->requested_line.coding.data_bits == 16); tusb_control_request_t const request = { .bmRequestType_bit = { @@ -931,46 +985,93 @@ static bool acm_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const }, .bRequest = CDC_REQUEST_SET_LINE_CODING, .wValue = 0, - .wIndex = tu_htole16(p_cdc->bInterfaceNumber), - .wLength = tu_htole16(sizeof(cdc_line_coding_t)) + .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), + .wLength = tu_htole16((uint16_t) sizeof(cdc_line_coding_t)) }; // use usbh enum buf to hold line coding since user line_coding variable does not live long enough - uint8_t* enum_buf = usbh_get_enum_buf(); - memcpy(enum_buf, line_coding, sizeof(cdc_line_coding_t)); + uint8_t *enum_buf = usbh_get_enum_buf(); + memcpy(enum_buf, &p_cdc->requested_line.coding, sizeof(cdc_line_coding_t)); - p_cdc->user_control_cb = complete_cb; tuh_xfer_t xfer = { .daddr = p_cdc->daddr, .ep_addr = 0, .setup = &request, .buffer = enum_buf, - .complete_cb = complete_cb ? cdch_internal_control_complete : NULL, // complete_cb is NULL for sync call + .complete_cb = complete_cb, .user_data = user_data }; - TU_ASSERT(tuh_control_xfer(&xfer)); - return true; + return tuh_control_xfer(&xfer); } -static bool acm_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_LOG_DRV("CDC ACM Set Data Format\r\n"); +//------------- Enumeration -------------// +enum { + CONFIG_ACM_COMPLETE = 0 +}; + +static bool acm_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + uint8_t const *p_desc_end = ((uint8_t const *) itf_desc) + max_len; + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); - cdc_line_coding_t line_coding; - line_coding.bit_rate = p_cdc->line_coding.bit_rate; - line_coding.stop_bits = stop_bits; - line_coding.parity = parity; - line_coding.data_bits = data_bits; + p_cdc->serial_drid = SERIAL_DRIVER_ACM; + + //------------- Control Interface -------------// + uint8_t const *p_desc = tu_desc_next(itf_desc); + + // Communication Functional Descriptors + while ((p_desc < p_desc_end) && (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc))) { + if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc)) { + // save ACM bmCapabilities + p_cdc->acm.capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; + } - return acm_set_line_coding(p_cdc, &line_coding, complete_cb, user_data); + p_desc = tu_desc_next(p_desc); + } + + // Open notification endpoint of control interface if any + if (itf_desc->bNumEndpoints == 1) { + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)); + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + + TU_ASSERT(tuh_edpt_open(daddr, desc_ep)); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface (if any) -------------// + if ((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && + (TUSB_CLASS_CDC_DATA == ((tusb_desc_interface_t const *) p_desc)->bInterfaceClass)) { + // next to endpoint descriptor + p_desc = tu_desc_next(p_desc); + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, (tusb_desc_endpoint_t const *) p_desc)); + } + + return true; } -static bool acm_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_VERIFY(p_cdc->acm_capability.support_line_request); - cdc_line_coding_t line_coding = p_cdc->line_coding; - line_coding.bit_rate = baudrate; - return acm_set_line_coding(p_cdc, &line_coding, complete_cb, user_data); +static bool acm_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + (void) p_cdc; + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_ACM_COMPLETE: { + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + } + + default: + return false; // invalid state + } + + return true; } //--------------------------------------------------------------------+ @@ -978,50 +1079,26 @@ static bool acm_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfe //--------------------------------------------------------------------+ #if CFG_TUH_CDC_FTDI -enum { - CONFIG_FTDI_RESET = 0, - CONFIG_FTDI_MODEM_CTRL, - CONFIG_FTDI_SET_BAUDRATE, - CONFIG_FTDI_SET_DATA, - CONFIG_FTDI_COMPLETE -}; +static bool ftdi_determine_type(cdch_interface_t *p_cdc); +static uint32_t ftdi_get_divisor(cdch_interface_t *p_cdc); -static bool ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { - // FTDI Interface includes 1 vendor interface + 2 bulk endpoints - TU_VERIFY(itf_desc->bInterfaceSubClass == 0xff && itf_desc->bInterfaceProtocol == 0xff && itf_desc->bNumEndpoints == 2); - TU_VERIFY(sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t) <= max_len); - - cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); - TU_VERIFY(p_cdc); - - TU_LOG_DRV("FTDI opened\r\n"); - p_cdc->serial_drid = SERIAL_DRIVER_FTDI; - - // endpoint pair - tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); - - // data endpoints expected to be in pairs - return open_ep_stream_pair(p_cdc, desc_ep); -} +//------------- Control Request -------------// // set request without data -static bool ftdi_sio_set_request(cdch_interface_t* p_cdc, uint8_t command, uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - tusb_control_request_t const request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_DEVICE, - .type = TUSB_REQ_TYPE_VENDOR, - .direction = TUSB_DIR_OUT - }, - .bRequest = command, - .wValue = tu_htole16(value), - .wIndex = 0, - .wLength = 0 +static bool ftdi_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType = requesttype, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = 0 }; tuh_xfer_t xfer = { .daddr = p_cdc->daddr, .ep_addr = 0, - .setup = &request, + .setup = &request_setup, .buffer = NULL, .complete_cb = complete_cb, .user_data = user_data @@ -1030,162 +1107,380 @@ static bool ftdi_sio_set_request(cdch_interface_t* p_cdc, uint8_t command, uint1 return tuh_control_xfer(&xfer); } -static bool ftdi_sio_reset(cdch_interface_t* p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - return ftdi_sio_set_request(p_cdc, FTDI_SIO_RESET, FTDI_SIO_RESET_SIO, complete_cb, user_data); +#ifdef CFG_TUH_CDC_FTDI_LATENCY +static int8_t ftdi_write_latency_timer(cdch_interface_t * p_cdc, uint16_t latency, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + if (p_cdc->ftdi.chip_type == FTDI_SIO /* || p_cdc->ftdi.chip_type == FT232A */ ) + return FTDI_NOT_POSSIBLE; + return ftdi_set_request(p_cdc, FTDI_SIO_SET_LATENCY_TIMER_REQUEST, FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE, + latency, p_cdc->ftdi.channel, complete_cb, user_data) ? FTDI_REQUESTED : FTDI_FAIL; } +#endif -static bool ftdi_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - (void) p_cdc; - (void) stop_bits; - (void) parity; - (void) data_bits; - (void) complete_cb; - (void) user_data; - // TODO not implemented yet - return false; +static inline bool ftdi_sio_reset(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ftdi_set_request(p_cdc, FTDI_SIO_RESET_REQUEST, FTDI_SIO_RESET_REQUEST_TYPE, FTDI_SIO_RESET_SIO, + p_cdc->ftdi.channel, complete_cb, user_data); } -static bool ftdi_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - (void) p_cdc; - (void) line_coding; - (void) complete_cb; - (void) user_data; - // TODO not implemented yet - return false; -} -static bool ftdi_sio_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_LOG_DRV("CDC FTDI Set Control Line State\r\n"); - p_cdc->user_control_cb = complete_cb; - TU_ASSERT(ftdi_sio_set_request(p_cdc, FTDI_SIO_MODEM_CTRL, 0x0300 | line_state, - complete_cb ? cdch_internal_control_complete : NULL, user_data)); - return true; -} +//------------- Driver API -------------// -static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, uint32_t base) { - const uint8_t divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; - uint32_t divisor; +// internal control complete to update state such as line state, line_coding +static void ftdi_internal_control_complete(cdch_interface_t* p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + const tusb_control_request_t * setup = xfer->setup; + if (xfer->result == XFER_RESULT_SUCCESS) { + if (setup->bRequest == FTDI_SIO_SET_MODEM_CTRL_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE ) { + p_cdc->line.control_state = p_cdc->requested_line.control_state; + } + if (setup->bRequest == FTDI_SIO_SET_DATA_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_DATA_REQUEST_TYPE ) { + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + } + if (setup->bRequest == FTDI_SIO_SET_BAUDRATE_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE ) { + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + } + } +} - /* divisor shifted 3 bits to the left */ - uint32_t divisor3 = base / (2 * baud); - divisor = (divisor3 >> 3); - divisor |= (uint32_t) divfrac[divisor3 & 0x7] << 14; +static bool ftdi_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 7 && p_cdc->requested_line.coding.data_bits <= 8, 0); + uint16_t value = (uint16_t) ((p_cdc->requested_line.coding.data_bits & 0xfUL) | // data bit quantity is stored in bits 0-3 + (p_cdc->requested_line.coding.parity & 0x7UL) << 8 | // parity is stored in bits 8-10, same coding + (p_cdc->requested_line.coding.stop_bits & 0x3UL) << 11); // stop bits quantity is stored in bits 11-12, same coding + // not each FTDI supports 1.5 stop bits + return ftdi_set_request(p_cdc, FTDI_SIO_SET_DATA_REQUEST, FTDI_SIO_SET_DATA_REQUEST_TYPE, + value, p_cdc->ftdi.channel, complete_cb, user_data); +} - /* Deal with special cases for highest baud rates. */ - if (divisor == 1) { /* 1.0 */ - divisor = 0; - } - else if (divisor == 0x4001) { /* 1.5 */ - divisor = 1; +static bool ftdi_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint32_t index_value = ftdi_get_divisor(p_cdc); + TU_VERIFY(index_value); + uint16_t value = (uint16_t) index_value; + uint16_t index = (uint16_t) (index_value >> 16); + if (p_cdc->ftdi.channel) { + index = (uint16_t) ((index << 8) | p_cdc->ftdi.channel); } - return divisor; + return ftdi_set_request(p_cdc, FTDI_SIO_SET_BAUDRATE_REQUEST, FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE, + value, index, complete_cb, user_data); } -static uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud) { - return ftdi_232bm_baud_base_to_divisor(baud, 48000000u); +static bool ftdi_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint16_t line_state = (uint16_t) ((p_cdc->requested_line.control_state.dtr ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW) | + (p_cdc->requested_line.control_state.rts ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW)); + return ftdi_set_request(p_cdc, FTDI_SIO_SET_MODEM_CTRL_REQUEST, FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE, + line_state, p_cdc->ftdi.channel, complete_cb ? cdch_internal_control_complete : NULL, user_data); } -static bool ftdi_sio_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - uint16_t const divisor = (uint16_t) ftdi_232bm_baud_to_divisor(baudrate); - TU_LOG_DRV("CDC FTDI Set BaudRate = %" PRIu32 ", divisor = 0x%04x\r\n", baudrate, divisor); +//------------- Enumeration -------------// +enum { + CONFIG_FTDI_DETERMINE_TYPE = 0, + CONFIG_FTDI_WRITE_LATENCY, + CONFIG_FTDI_SIO_RESET, + CONFIG_FTDI_FLOW_CONTROL, + CONFIG_FTDI_COMPLETE +}; - p_cdc->user_control_cb = complete_cb; - p_cdc->requested_line_coding.bit_rate = baudrate; - TU_ASSERT(ftdi_sio_set_request(p_cdc, FTDI_SIO_SET_BAUD_RATE, divisor, - complete_cb ? cdch_internal_control_complete : NULL, user_data)); +static bool ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + // FTDI Interface includes 1 vendor interface + 2 bulk endpoints + TU_VERIFY(itf_desc->bInterfaceSubClass == 0xff && itf_desc->bInterfaceProtocol == 0xff && + itf_desc->bNumEndpoints == 2); + TU_VERIFY(sizeof(tusb_desc_interface_t) + 2 * sizeof(tusb_desc_endpoint_t) <= max_len); - return true; -} + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); -static void ftdi_process_config(tuh_xfer_t* xfer) { - uintptr_t const state = xfer->user_data; - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t * p_cdc = get_itf(idx); - TU_ASSERT(p_cdc, ); + p_cdc->serial_drid = SERIAL_DRIVER_FTDI; - switch(state) { - // Note may need to read FTDI eeprom - case CONFIG_FTDI_RESET: - TU_ASSERT(ftdi_sio_reset(p_cdc, ftdi_process_config, CONFIG_FTDI_MODEM_CTRL),); - break; + // endpoint pair + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); - case CONFIG_FTDI_MODEM_CTRL: - #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM - TU_ASSERT(ftdi_sio_set_modem_ctrl(p_cdc, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, ftdi_process_config, CONFIG_FTDI_SET_BAUDRATE),); - break; - #else + /* + * NOTE: Some customers have programmed FT232R/FT245R devices + * with an endpoint size of 0 - not good. + */ + TU_ASSERT(desc_ep->wMaxPacketSize != 0); + + // data endpoints expected to be in pairs + return open_ep_stream_pair(p_cdc, desc_ep); +} + +static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + switch (state) { + // from here sequence overtaken from Linux Kernel function ftdi_port_probe() + case CONFIG_FTDI_DETERMINE_TYPE: + // determine type + if (p_cdc->bInterfaceNumber == 0) { + TU_ASSERT(ftdi_determine_type(p_cdc)); + } else { + // other interfaces have same type as interface 0 + uint8_t const idx_itf0 = tuh_cdc_itf_get_index(xfer->daddr, 0); + cdch_interface_t const *p_cdc_itf0 = get_itf(idx_itf0); + TU_ASSERT(p_cdc_itf0); + p_cdc->ftdi.chip_type = p_cdc_itf0->ftdi.chip_type; + } TU_ATTR_FALLTHROUGH; + + case CONFIG_FTDI_WRITE_LATENCY: + #ifdef CFG_TUH_CDC_FTDI_LATENCY + int8_t result = ftdi_write_latency_timer(p_cdc, CFG_TUH_CDC_FTDI_LATENCY, ftdi_process_config, + CONFIG_FTDI_SIO_RESET); + TU_ASSERT(result != FTDI_FAIL); + if (result == FTDI_REQUESTED) { + break; + }// else FTDI_NOT_POSSIBLE => continue directly with next state #endif + TU_ATTR_FALLTHROUGH; - case CONFIG_FTDI_SET_BAUDRATE: { - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(ftdi_sio_set_baudrate(p_cdc, line_coding.bit_rate, ftdi_process_config, CONFIG_FTDI_SET_DATA),); + // from here sequence overtaken from Linux Kernel function ftdi_open() + case CONFIG_FTDI_SIO_RESET: + TU_ASSERT(ftdi_sio_reset(p_cdc, cdch_process_set_config, CONFIG_FTDI_FLOW_CONTROL)); break; - #else - TU_ATTR_FALLTHROUGH; - #endif - } - case CONFIG_FTDI_SET_DATA: { - #if 0 // TODO set data format - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(ftdi_sio_set_data(p_cdc, process_ftdi_config, CONFIG_FTDI_COMPLETE),); + case CONFIG_FTDI_FLOW_CONTROL: + // disable flow control + TU_ASSERT(ftdi_set_request(p_cdc, FTDI_SIO_SET_FLOW_CTRL_REQUEST, FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, FTDI_SIO_DISABLE_FLOW_CTRL, + p_cdc->ftdi.channel, cdch_process_set_config, CONFIG_FTDI_COMPLETE)); break; - #endif - #endif - TU_ATTR_FALLTHROUGH; + case CONFIG_FTDI_COMPLETE: { + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; } - case CONFIG_FTDI_COMPLETE: - set_config_complete(p_cdc, idx, itf_num); + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +static bool ftdi_determine_type(cdch_interface_t *p_cdc) { + tusb_desc_device_t desc_dev; + TU_VERIFY(tuh_descriptor_get_device_local(p_cdc->daddr, &desc_dev)); + uint16_t const version = desc_dev.bcdDevice; + uint8_t const itf_num = p_cdc->bInterfaceNumber; + + p_cdc->ftdi.chip_type = FTDI_UNKNOWN; + + /* Assume Hi-Speed type */ + p_cdc->ftdi.channel = CHANNEL_A + itf_num; + + switch (version) { + case 0x200: + // FT232A not supported to keep it simple (no extra _read_latency_timer()) not testable + // p_cdc->ftdi.chip_type = FT232A; + // p_cdc->ftdi.baud_base = 48000000 / 2; + // p_cdc->ftdi.channel = 0; + // /* + // * FT232B devices have a bug where bcdDevice gets set to 0x200 + // * when iSerialNumber is 0. Assume it is an FT232B in case the + // * latency timer is readable. + // */ + // if (desc->iSerialNumber == 0 && + // _read_latency_timer(port) >= 0) { + // p_cdc->ftdi.chip_type = FTDI_FT232B; + // } break; + case 0x400 : p_cdc->ftdi.chip_type = FTDI_FT232B; p_cdc->ftdi.channel = 0; break; + case 0x500 : p_cdc->ftdi.chip_type = FTDI_FT2232C; break; + case 0x600 : p_cdc->ftdi.chip_type = FTDI_FT232R; p_cdc->ftdi.channel = 0; break; + case 0x700 : p_cdc->ftdi.chip_type = FTDI_FT2232H; break; + case 0x800 : p_cdc->ftdi.chip_type = FTDI_FT4232H; break; + case 0x900 : p_cdc->ftdi.chip_type = FTDI_FT232H; break; + case 0x1000: p_cdc->ftdi.chip_type = FTDI_FTX; break; + case 0x2800: p_cdc->ftdi.chip_type = FTDI_FT2233HP; break; + case 0x2900: p_cdc->ftdi.chip_type = FTDI_FT4233HP; break; + case 0x3000: p_cdc->ftdi.chip_type = FTDI_FT2232HP; break; + case 0x3100: p_cdc->ftdi.chip_type = FTDI_FT4232HP; break; + case 0x3200: p_cdc->ftdi.chip_type = FTDI_FT233HP; break; + case 0x3300: p_cdc->ftdi.chip_type = FTDI_FT232HP; break; + case 0x3600: p_cdc->ftdi.chip_type = FTDI_FT4232HA; break; + default: + if (version < 0x200) { + p_cdc->ftdi.chip_type = FTDI_SIO; + p_cdc->ftdi.channel = 0; + } break; } + + #if CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + const char * ftdi_chip_name[] = { FTDI_CHIP_NAMES }; + TU_LOG_CDC(p_cdc, "%s detected (bcdDevice = 0x%04x)", + ftdi_chip_name[p_cdc->ftdi.chip_type], version); + #endif + + return (p_cdc->ftdi.chip_type != FTDI_UNKNOWN); } -#endif +// FT232A not supported +//static uint32_t ftdi_232am_baud_base_to_divisor(uint32_t baud, uint32_t base) +//{ +// uint32_t divisor; +// /* divisor shifted 3 bits to the left */ +// uint32_t divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud); +// if ((divisor3 & 0x7) == 7) +// divisor3++; /* round x.7/8 up to x+1 */ +// divisor = divisor3 >> 3; +// divisor3 &= 0x7; +// if (divisor3 == 1) +// divisor |= 0xc000; /* +0.125 */ +// else if (divisor3 >= 4) +// divisor |= 0x4000; /* +0.5 */ +// else if (divisor3 != 0) +// divisor |= 0x8000; /* +0.25 */ +// else if (divisor == 1) +// divisor = 0; /* special case for maximum baud rate */ +// return divisor; +//} -//--------------------------------------------------------------------+ -// CP210x -//--------------------------------------------------------------------+ +// FT232A not supported +//static inline uint32_t ftdi_232am_baud_to_divisor(uint32_t baud) +//{ +// return ftdi_232am_baud_base_to_divisor(baud, (uint32_t) 48000000); +//} -#if CFG_TUH_CDC_CP210X +static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, uint32_t base) { + uint8_t divfrac[8] = {0, 3, 2, 4, 1, 5, 6, 7}; + uint32_t divisor; + /* divisor shifted 3 bits to the left */ + uint32_t divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud); + divisor = divisor3 >> 3; + divisor |= (uint32_t) divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) /* 1.0 */ { + divisor = 0; + } else if (divisor == 0x4001) /* 1.5 */ { + divisor = 1; + } + return divisor; +} -enum { - CONFIG_CP210X_IFC_ENABLE = 0, - CONFIG_CP210X_SET_BAUDRATE, - CONFIG_CP210X_SET_LINE_CTL, - CONFIG_CP210X_SET_DTR_RTS, - CONFIG_CP210X_COMPLETE -}; +static inline uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud) { + return ftdi_232bm_baud_base_to_divisor(baud, 48000000); +} -static bool cp210x_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { - // CP210x Interface includes 1 vendor interface + 2 bulk endpoints - TU_VERIFY(itf_desc->bInterfaceSubClass == 0 && itf_desc->bInterfaceProtocol == 0 && itf_desc->bNumEndpoints == 2); - TU_VERIFY(sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t) <= max_len); +static uint32_t ftdi_2232h_baud_base_to_divisor(uint32_t baud, uint32_t base) { + static const unsigned char divfrac[8] = {0, 3, 2, 4, 1, 5, 6, 7}; + uint32_t divisor; + uint32_t divisor3; - cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); - TU_VERIFY(p_cdc); + /* hi-speed baud rate is 10-bit sampling instead of 16-bit */ + divisor3 = DIV_ROUND_CLOSEST(8 * base, 10 * baud); - TU_LOG_DRV("CP210x opened\r\n"); - p_cdc->serial_drid = SERIAL_DRIVER_CP210X; + divisor = divisor3 >> 3; + divisor |= (uint32_t) divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) /* 1.0 */ { + divisor = 0; + } else if (divisor == 0x4001) /* 1.5 */ { + divisor = 1; + } + /* + * Set this bit to turn off a divide by 2.5 on baud rate generator + * This enables baud rates up to 12Mbaud but cannot reach below 1200 + * baud with this bit set + */ + divisor |= 0x00020000; + return divisor; +} - // endpoint pair - tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); +static inline uint32_t ftdi_2232h_baud_to_divisor(uint32_t baud) { + return ftdi_2232h_baud_base_to_divisor(baud, (uint32_t) 120000000); +} - // data endpoints expected to be in pairs - return open_ep_stream_pair(p_cdc, desc_ep); +static inline uint32_t ftdi_get_divisor(cdch_interface_t *p_cdc) { + uint32_t baud = p_cdc->requested_line.coding.bit_rate; + uint32_t div_value = 0; + TU_VERIFY(baud); + + switch (p_cdc->ftdi.chip_type) { + case FTDI_UNKNOWN: + return 0; + case FTDI_SIO: + switch (baud) { + case 300: div_value = ftdi_sio_b300; break; + case 600: div_value = ftdi_sio_b600; break; + case 1200: div_value = ftdi_sio_b1200; break; + case 2400: div_value = ftdi_sio_b2400; break; + case 4800: div_value = ftdi_sio_b4800; break; + case 9600: div_value = ftdi_sio_b9600; break; + case 19200: div_value = ftdi_sio_b19200; break; + case 38400: div_value = ftdi_sio_b38400; break; + case 57600: div_value = ftdi_sio_b57600; break; + case 115200: div_value = ftdi_sio_b115200; break; + default: + // Baudrate not supported + return 0; + break; + } + break; + // FT232A not supported + // case FT232A: + // if (baud <= 3000000) { + // div_value = ftdi_232am_baud_to_divisor(baud); + // } else { + // // Baud rate too high! + // baud = 9600; + // div_value = ftdi_232am_baud_to_divisor(9600); + // div_okay = false; + // } + // break; + case FTDI_FT232B: + case FTDI_FT2232C: + case FTDI_FT232R: + case FTDI_FTX: + TU_VERIFY(baud <= 3000000); // else Baud rate too high! + div_value = ftdi_232bm_baud_to_divisor(baud); + break; + case FTDI_FT232H: + case FTDI_FT2232H: + case FTDI_FT4232H: + case FTDI_FT4232HA: + case FTDI_FT232HP: + case FTDI_FT233HP: + case FTDI_FT2232HP: + case FTDI_FT2233HP: + case FTDI_FT4232HP: + case FTDI_FT4233HP: + default: + TU_VERIFY(baud <= 12000000); // else Baud rate too high! + if (baud >= 1200) { + div_value = ftdi_2232h_baud_to_divisor(baud); + } else { + div_value = ftdi_232bm_baud_to_divisor(baud); + } + break; + } + + TU_LOG_CDC(p_cdc, "Baudrate divisor = 0x%lu", div_value); + + return div_value; } -static bool cp210x_set_request(cdch_interface_t* p_cdc, uint8_t command, uint16_t value, uint8_t* buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +#endif + +//--------------------------------------------------------------------+ +// CP210x +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_CP210X + +//------------- Control Request -------------// + +static bool cp210x_set_request(cdch_interface_t * p_cdc, uint8_t command, uint16_t value, + uint8_t * buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { tusb_control_request_t const request = { .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_INTERFACE, @@ -1194,12 +1489,12 @@ static bool cp210x_set_request(cdch_interface_t* p_cdc, uint8_t command, uint16_ }, .bRequest = command, .wValue = tu_htole16(value), - .wIndex = p_cdc->bInterfaceNumber, + .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), .wLength = tu_htole16(length) }; // use usbh enum buf since application variable does not live long enough - uint8_t* enum_buf = NULL; + uint8_t * enum_buf = NULL; if (buffer && length > 0) { enum_buf = usbh_get_enum_buf(); @@ -1218,91 +1513,103 @@ static bool cp210x_set_request(cdch_interface_t* p_cdc, uint8_t command, uint16_ return tuh_control_xfer(&xfer); } -static bool cp210x_ifc_enable(cdch_interface_t* p_cdc, uint16_t enabled, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +TU_ATTR_ALWAYS_INLINE static inline bool cp210x_ifc_enable(cdch_interface_t *p_cdc, uint16_t enabled, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return cp210x_set_request(p_cdc, CP210X_IFC_ENABLE, enabled, NULL, 0, complete_cb, user_data); } -static bool cp210x_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - // TODO implement later - (void) p_cdc; - (void) line_coding; - (void) complete_cb; - (void) user_data; - return false; +TU_ATTR_ALWAYS_INLINE static inline bool cp210x_set_mhs(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // CP210x has the same bit coding + return cp210x_set_request(p_cdc, CP210X_SET_MHS, + (uint16_t) (CP210X_CONTROL_WRITE_DTR | CP210X_CONTROL_WRITE_RTS | p_cdc->requested_line.control_state.value), + NULL, 0, complete_cb, user_data); } -static bool cp210x_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_LOG_DRV("CDC CP210x Set BaudRate = %" PRIu32 "\r\n", baudrate); - uint32_t baud_le = tu_htole32(baudrate); - p_cdc->user_control_cb = complete_cb; - return cp210x_set_request(p_cdc, CP210X_SET_BAUDRATE, 0, (uint8_t *) &baud_le, 4, - complete_cb ? cdch_internal_control_complete : NULL, user_data); +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void cp210x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + switch (xfer->setup->bRequest) { + case CP210X_SET_MHS: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; + + case CP210X_SET_LINE_CTL: + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + break; + + case CP210X_SET_BAUDRATE: + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + break; + + default: break; + } } -static bool cp210x_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - (void) p_cdc; - (void) stop_bits; - (void) parity; - (void) data_bits; - (void) complete_cb; - (void) user_data; - // TODO not implemented yet - return false; +static bool cp210x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // Not every baud rate is supported. See datasheets and AN205 "CP210x Baud Rate Support" + uint32_t baud_le = tu_htole32(p_cdc->requested_line.coding.bit_rate); + return cp210x_set_request(p_cdc, CP210X_SET_BAUDRATE, 0, (uint8_t *) &baud_le, 4, complete_cb, user_data); } -static bool cp210x_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_LOG_DRV("CDC CP210x Set Control Line State\r\n"); - p_cdc->user_control_cb = complete_cb; - return cp210x_set_request(p_cdc, CP210X_SET_MHS, 0x0300 | line_state, NULL, 0, - complete_cb ? cdch_internal_control_complete : NULL, user_data); +static bool cp210x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8, 0); + uint16_t lcr = (uint16_t) ((p_cdc->requested_line.coding.data_bits & 0xfUL) << 8 | // data bit quantity is stored in bits 8-11 + (p_cdc->requested_line.coding.parity & 0xfUL) << 4 | // parity is stored in bits 4-7, same coding + (p_cdc->requested_line.coding.stop_bits & 0xfUL)); // parity is stored in bits 0-3, same coding + + return cp210x_set_request(p_cdc, CP210X_SET_LINE_CTL, lcr, NULL, 0, complete_cb, user_data); } -static void cp210x_process_config(tuh_xfer_t* xfer) { - uintptr_t const state = xfer->user_data; - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t *p_cdc = get_itf(idx); - TU_ASSERT(p_cdc,); +static bool cp210x_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return cp210x_set_mhs(p_cdc, complete_cb, user_data); +} - switch (state) { - case CONFIG_CP210X_IFC_ENABLE: - TU_ASSERT(cp210x_ifc_enable(p_cdc, 1, cp210x_process_config, CONFIG_CP210X_SET_BAUDRATE),); - break; +//------------- Enumeration -------------// - case CONFIG_CP210X_SET_BAUDRATE: { - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(cp210x_set_baudrate(p_cdc, line_coding.bit_rate, cp210x_process_config, CONFIG_CP210X_SET_LINE_CTL),); - break; - #else - TU_ATTR_FALLTHROUGH; - #endif - } +enum { + CONFIG_CP210X_IFC_ENABLE = 0, + CONFIG_CP210X_COMPLETE +}; - case CONFIG_CP210X_SET_LINE_CTL: { - #if defined(CFG_TUH_CDC_LINE_CODING_ON_ENUM) && 0 // skip for now - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - break; - #else - TU_ATTR_FALLTHROUGH; - #endif - } +static bool cp210x_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + // CP210x Interface includes 1 vendor interface + 2 bulk endpoints + TU_VERIFY(itf_desc->bInterfaceSubClass == 0 && itf_desc->bInterfaceProtocol == 0 && itf_desc->bNumEndpoints == 2); + TU_VERIFY(sizeof(tusb_desc_interface_t) + 2 * sizeof(tusb_desc_endpoint_t) <= max_len); + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); - case CONFIG_CP210X_SET_DTR_RTS: - #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM - TU_ASSERT(cp210x_set_modem_ctrl(p_cdc, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, cp210x_process_config, CONFIG_CP210X_COMPLETE),); + p_cdc->serial_drid = SERIAL_DRIVER_CP210X; + + // endpoint pair + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + // data endpoints expected to be in pairs + return open_ep_stream_pair(p_cdc, desc_ep); +} + +static bool cp210x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_CP210X_IFC_ENABLE: + TU_ASSERT(cp210x_ifc_enable(p_cdc, CP210X_UART_ENABLE, cdch_process_set_config, CONFIG_CP210X_COMPLETE)); break; - #else - TU_ATTR_FALLTHROUGH; - #endif case CONFIG_CP210X_COMPLETE: - set_config_complete(p_cdc, idx, itf_num); + xfer->user_data = 0;// kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); break; - default: break; + default: + return false; } + + return true; } #endif @@ -1313,13 +1620,14 @@ static void cp210x_process_config(tuh_xfer_t* xfer) { #if CFG_TUH_CDC_CH34X -static uint8_t ch34x_get_lcr(uint8_t stop_bits, uint8_t parity, uint8_t data_bits); -static uint16_t ch34x_get_divisor_prescaler(uint32_t baval); +static uint8_t ch34x_get_lcr(cdch_interface_t *p_cdc); +static uint16_t ch34x_get_divisor_prescaler(cdch_interface_t *p_cdc); -//------------- control request -------------// +//------------- Control Request -------------// -static bool ch34x_set_request(cdch_interface_t* p_cdc, uint8_t direction, uint8_t request, uint16_t value, - uint16_t index, uint8_t* buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +static bool ch34x_set_request(cdch_interface_t *p_cdc, uint8_t direction, uint8_t request, + uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { tusb_control_request_t const request_setup = { .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, @@ -1327,13 +1635,13 @@ static bool ch34x_set_request(cdch_interface_t* p_cdc, uint8_t direction, uint8_ .direction = direction & 0x01u }, .bRequest = request, - .wValue = tu_htole16 (value), - .wIndex = tu_htole16 (index), - .wLength = tu_htole16 (length) + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = tu_htole16(length) }; // use usbh enum buf since application variable does not live long enough - uint8_t* enum_buf = NULL; + uint8_t *enum_buf = NULL; if (buffer && length > 0) { enum_buf = usbh_get_enum_buf(); @@ -1354,164 +1662,101 @@ static bool ch34x_set_request(cdch_interface_t* p_cdc, uint8_t direction, uint8_ return tuh_control_xfer(&xfer); } -static inline bool ch34x_control_out(cdch_interface_t* p_cdc, uint8_t request, uint16_t value, uint16_t index, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_out(cdch_interface_t *p_cdc, uint8_t request, uint16_t value, uint16_t index, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return ch34x_set_request(p_cdc, TUSB_DIR_OUT, request, value, index, NULL, 0, complete_cb, user_data); } -static inline bool ch34x_control_in(cdch_interface_t* p_cdc, uint8_t request, uint16_t value, uint16_t index, - uint8_t* buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_in(cdch_interface_t *p_cdc, uint8_t request, uint16_t value, uint16_t index, + uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return ch34x_set_request(p_cdc, TUSB_DIR_IN, request, value, index, buffer, buffersize, complete_cb, user_data); } -static inline bool ch34x_write_reg(cdch_interface_t* p_cdc, uint16_t reg, uint16_t reg_value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_write_reg(cdch_interface_t *p_cdc, uint16_t reg, uint16_t reg_value, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return ch34x_control_out(p_cdc, CH34X_REQ_WRITE_REG, reg, reg_value, complete_cb, user_data); } -//static bool ch34x_read_reg_request ( cdch_interface_t* p_cdc, uint16_t reg, +//static bool ch34x_read_reg_request ( cdch_interface_t * p_cdc, uint16_t reg, // uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data ) //{ // return ch34x_control_in ( p_cdc, CH34X_REQ_READ_REG, reg, 0, buffer, buffersize, complete_cb, user_data ); //} -static bool ch34x_write_reg_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - uint16_t const div_ps = ch34x_get_divisor_prescaler(baudrate); - TU_VERIFY(div_ps); - TU_ASSERT(ch34x_write_reg(p_cdc, CH34X_REG16_DIVISOR_PRESCALER, div_ps, - complete_cb, user_data)); - return true; -} - //------------- Driver API -------------// // internal control complete to update state such as line state, encoding -static void ch34x_control_complete(tuh_xfer_t* xfer) { - // CH34x only has 1 interface and use wIndex as payload and not for bInterfaceNumber - process_internal_control_complete(xfer, 0); -} +static void ch34x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + switch (xfer->setup->bRequest) { + case CH34X_REQ_WRITE_REG: + // register write request + switch (tu_le16toh(xfer->setup->wValue)) { + case CH34X_REG16_DIVISOR_PRESCALER: + // baudrate + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + break; -static bool ch34x_set_data_format(cdch_interface_t* p_cdc, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - p_cdc->requested_line_coding.stop_bits = stop_bits; - p_cdc->requested_line_coding.parity = parity; - p_cdc->requested_line_coding.data_bits = data_bits; - - uint8_t const lcr = ch34x_get_lcr(stop_bits, parity, data_bits); - TU_VERIFY(lcr); - TU_ASSERT (ch34x_control_out(p_cdc, CH34X_REQ_WRITE_REG, CH32X_REG16_LCR2_LCR, lcr, - complete_cb ? ch34x_control_complete : NULL, user_data)); - return true; -} + case CH32X_REG16_LCR2_LCR: + // data format + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + break; -static bool ch34x_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - p_cdc->requested_line_coding.bit_rate = baudrate; - p_cdc->user_control_cb = complete_cb; - TU_ASSERT(ch34x_write_reg_baudrate(p_cdc, baudrate, - complete_cb ? ch34x_control_complete : NULL, user_data)); - return true; -} + default: break; + } + break; -static void ch34x_set_line_coding_stage1_complete(tuh_xfer_t* xfer) { - // CH34x only has 1 interface and use wIndex as payload and not for bInterfaceNumber - uint8_t const itf_num = 0; - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t* p_cdc = get_itf(idx); - TU_ASSERT(p_cdc, ); + case CH34X_REQ_MODEM_CTRL: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; - if (xfer->result == XFER_RESULT_SUCCESS) { - // stage 1 success, continue to stage 2 - p_cdc->line_coding.bit_rate = p_cdc->requested_line_coding.bit_rate; - TU_ASSERT(ch34x_set_data_format(p_cdc, p_cdc->requested_line_coding.stop_bits, p_cdc->requested_line_coding.parity, - p_cdc->requested_line_coding.data_bits, ch34x_control_complete, xfer->user_data), ); - } else { - // stage 1 failed, notify user - xfer->complete_cb = p_cdc->user_control_cb; - if (xfer->complete_cb) { - xfer->complete_cb(xfer); - } + default: break; } } -// 2 stages: set baudrate (stage1) + set data format (stage2) -static bool ch34x_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - p_cdc->requested_line_coding = *line_coding; - p_cdc->user_control_cb = complete_cb; - - if (complete_cb) { - // stage 1 set baudrate - TU_ASSERT(ch34x_write_reg_baudrate(p_cdc, line_coding->bit_rate, - ch34x_set_line_coding_stage1_complete, user_data)); - } else { - // sync call - xfer_result_t result; - - // stage 1 set baudrate - TU_ASSERT(ch34x_write_reg_baudrate(p_cdc, line_coding->bit_rate, NULL, (uintptr_t) &result)); - TU_VERIFY(result == XFER_RESULT_SUCCESS); - p_cdc->line_coding.bit_rate = line_coding->bit_rate; - - // stage 2 set data format - TU_ASSERT(ch34x_set_data_format(p_cdc, line_coding->stop_bits, line_coding->parity, line_coding->data_bits, - NULL, (uintptr_t) &result)); - TU_VERIFY(result == XFER_RESULT_SUCCESS); - p_cdc->line_coding.stop_bits = line_coding->stop_bits; - p_cdc->line_coding.parity = line_coding->parity; - p_cdc->line_coding.data_bits = line_coding->data_bits; - - // update transfer result, user_data is expected to point to xfer_result_t - if (user_data) { - *((xfer_result_t*) user_data) = result; - } - } - - return true; +static bool ch34x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + const uint8_t lcr = ch34x_get_lcr(p_cdc); + TU_VERIFY(lcr); + return ch34x_write_reg(p_cdc, CH32X_REG16_LCR2_LCR, lcr, complete_cb, user_data); } -static bool ch34x_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - uint8_t control = 0; - if (line_state & CDC_CONTROL_LINE_STATE_RTS) { - control |= CH34X_BIT_RTS; - } - if (line_state & CDC_CONTROL_LINE_STATE_DTR) { - control |= CH34X_BIT_DTR; - } +static bool ch34x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + const uint16_t div_ps = ch34x_get_divisor_prescaler(p_cdc); + TU_VERIFY(div_ps); + return ch34x_write_reg(p_cdc, CH34X_REG16_DIVISOR_PRESCALER, div_ps, complete_cb, user_data); +} +static bool ch34x_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { // CH34x signals are inverted - control = ~control; - - p_cdc->user_control_cb = complete_cb; - TU_ASSERT (ch34x_control_out(p_cdc, CH34X_REQ_MODEM_CTRL, control, 0, - complete_cb ? ch34x_control_complete : NULL, user_data)); - return true; + uint8_t control = ~((p_cdc->requested_line.control_state.rts ? CH34X_BIT_RTS : 0) | + (p_cdc->requested_line.control_state.dtr ? CH34X_BIT_DTR : 0)); + return ch34x_control_out(p_cdc, CH34X_REQ_MODEM_CTRL, control, 0, complete_cb, user_data); } //------------- Enumeration -------------// + enum { CONFIG_CH34X_READ_VERSION = 0, CONFIG_CH34X_SERIAL_INIT, CONFIG_CH34X_SPECIAL_REG_WRITE, CONFIG_CH34X_FLOW_CONTROL, - CONFIG_CH34X_MODEM_CONTROL, CONFIG_CH34X_COMPLETE }; -static bool ch34x_open(uint8_t daddr, tusb_desc_interface_t const* itf_desc, uint16_t max_len) { +static bool ch34x_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { // CH34x Interface includes 1 vendor interface + 2 bulk + 1 interrupt endpoints - TU_VERIFY (itf_desc->bNumEndpoints == 3); - TU_VERIFY (sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t) <= max_len); + TU_VERIFY(itf_desc->bNumEndpoints == 3); + TU_VERIFY(sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t) <= max_len); - cdch_interface_t* p_cdc = make_new_itf(daddr, itf_desc); - TU_VERIFY (p_cdc); + cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); - TU_LOG_DRV ("CH34x opened\r\n"); p_cdc->serial_drid = SERIAL_DRIVER_CH34X; - tusb_desc_endpoint_t const* desc_ep = (tusb_desc_endpoint_t const*) tu_desc_next(itf_desc); + tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); // data endpoints expected to be in pairs TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep)); @@ -1526,69 +1771,66 @@ static bool ch34x_open(uint8_t daddr, tusb_desc_interface_t const* itf_desc, uin return true; } -static void ch34x_process_config(tuh_xfer_t* xfer) { - // CH34x only has 1 interface and use wIndex as payload and not for bInterfaceNumber - uint8_t const itf_num = 0; - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t* p_cdc = get_itf(idx); - uintptr_t const state = xfer->user_data; - uint8_t buffer[2]; // TODO remove - TU_ASSERT (p_cdc,); - TU_ASSERT (xfer->result == XFER_RESULT_SUCCESS,); +static bool ch34x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; switch (state) { - case CONFIG_CH34X_READ_VERSION: - TU_LOG_DRV("[%u] CDCh CH34x attempt to read Chip Version\r\n", p_cdc->daddr); - TU_ASSERT (ch34x_control_in(p_cdc, CH34X_REQ_READ_VERSION, 0, 0, buffer, 2, ch34x_process_config, CONFIG_CH34X_SERIAL_INIT),); + case CONFIG_CH34X_READ_VERSION: { + uint8_t* enum_buf = usbh_get_enum_buf(); + TU_ASSERT(ch34x_control_in(p_cdc, CH34X_REQ_READ_VERSION, 0, 0, enum_buf, 2, + cdch_process_set_config, CONFIG_CH34X_SERIAL_INIT)); break; + } case CONFIG_CH34X_SERIAL_INIT: { // handle version read data, set CH34x line coding (incl. baudrate) uint8_t const version = xfer->buffer[0]; - TU_LOG_DRV("[%u] CDCh CH34x Chip Version = %02x\r\n", p_cdc->daddr, version); - // only versions >= 0x30 are tested, below 0x30 seems having other programming, see drivers from WCH vendor, Linux kernel and FreeBSD - TU_ASSERT (version >= 0x30,); - // init CH34x with line coding - cdc_line_coding_t const line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; - uint16_t const div_ps = ch34x_get_divisor_prescaler(line_coding.bit_rate); - TU_ASSERT(div_ps, ); - uint8_t const lcr = ch34x_get_lcr(line_coding.stop_bits, line_coding.parity, line_coding.data_bits); - TU_ASSERT(lcr, ); - TU_ASSERT (ch34x_control_out(p_cdc, CH34X_REQ_SERIAL_INIT, tu_u16(lcr, 0x9c), div_ps, - ch34x_process_config, CONFIG_CH34X_SPECIAL_REG_WRITE),); + TU_LOG_CDC(p_cdc, "Chip Version = 0x%02x", version); + // only versions >= 0x30 are tested, below 0x30 seems having other programming + // see drivers from WCH vendor, Linux kernel and FreeBSD + if (version >= 0x30) { + // init CH34x with line coding + p_cdc->requested_line.coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; + uint16_t const div_ps = ch34x_get_divisor_prescaler(p_cdc); + uint8_t const lcr = ch34x_get_lcr(p_cdc); + TU_ASSERT(div_ps != 0 && lcr != 0); + TU_ASSERT(ch34x_control_out(p_cdc, CH34X_REQ_SERIAL_INIT, tu_u16(lcr, 0x9c), div_ps, + cdch_process_set_config, CONFIG_CH34X_SPECIAL_REG_WRITE)); + } break; } case CONFIG_CH34X_SPECIAL_REG_WRITE: // overtake line coding and do special reg write, purpose unknown, overtaken from WCH driver - p_cdc->line_coding = ((cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X); - TU_ASSERT (ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x0F, CH341_REG_0x2C), 0x0007, ch34x_process_config, CONFIG_CH34X_FLOW_CONTROL),); + p_cdc->line.coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; + TU_ASSERT(ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x0F, CH341_REG_0x2C), 0x0007, + cdch_process_set_config, CONFIG_CH34X_FLOW_CONTROL)); break; case CONFIG_CH34X_FLOW_CONTROL: // no hardware flow control - TU_ASSERT (ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x27, CH341_REG_0x27), 0x0000, ch34x_process_config, CONFIG_CH34X_MODEM_CONTROL),); - break; - - case CONFIG_CH34X_MODEM_CONTROL: - // !always! set modem controls RTS/DTR (CH34x has no reset state after CH34X_REQ_SERIAL_INIT) - TU_ASSERT (ch34x_set_modem_ctrl(p_cdc, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, ch34x_process_config, CONFIG_CH34X_COMPLETE),); + TU_ASSERT(ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x27, CH341_REG_0x27), 0x0000, + cdch_process_set_config, CONFIG_CH34X_COMPLETE)); break; case CONFIG_CH34X_COMPLETE: - set_config_complete(p_cdc, idx, itf_num); + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); break; default: - TU_ASSERT (false,); - break; + return false; } + + return true; } -//------------- CH34x helper -------------// +//------------- Helper -------------// // calculate divisor and prescaler for baudrate, return it as 16-bit combined value -static uint16_t ch34x_get_divisor_prescaler(uint32_t baval) { +static uint16_t ch34x_get_divisor_prescaler(cdch_interface_t *p_cdc) { + uint32_t const baval = p_cdc->requested_line.coding.bit_rate; uint8_t a; uint8_t b; uint32_t c; @@ -1633,16 +1875,20 @@ static uint16_t ch34x_get_divisor_prescaler(uint32_t baval) { // reg divisor = a, reg prescaler = b // According to linux code we need to set bit 7 of UCHCOM_REG_BPS_PRE, // otherwise the chip will buffer data. - return (uint16_t) ((uint16_t)a << 8 | 0x80 | b); + return (uint16_t) ((uint16_t) a << 8 | 0x80 | b); } // calculate lcr value from data coding -static uint8_t ch34x_get_lcr(uint8_t stop_bits, uint8_t parity, uint8_t data_bits) { +static uint8_t ch34x_get_lcr(cdch_interface_t *p_cdc) { + uint8_t const stop_bits = p_cdc->requested_line.coding.stop_bits; + uint8_t const parity = p_cdc->requested_line.coding.parity; + uint8_t const data_bits = p_cdc->requested_line.coding.data_bits; + uint8_t lcr = CH34X_LCR_ENABLE_RX | CH34X_LCR_ENABLE_TX; - TU_VERIFY(data_bits >= 5 && data_bits <= 8, 0); + TU_VERIFY(data_bits >= 5 && data_bits <= 8); lcr |= (uint8_t) (data_bits - 5); - switch(parity) { + switch (parity) { case CDC_LINE_CODING_PARITY_NONE: break; @@ -1666,7 +1912,7 @@ static uint8_t ch34x_get_lcr(uint8_t stop_bits, uint8_t parity, uint8_t data_bit } // 1.5 stop bits not supported - TU_VERIFY(stop_bits != CDC_LINE_CODING_STOP_BITS_1_5, 0); + TU_VERIFY(stop_bits != CDC_LINE_CODING_STOP_BITS_1_5); if (stop_bits == CDC_LINE_CODING_STOP_BITS_2) { lcr |= CH34X_LCR_STOP_BITS_2; } @@ -1674,7 +1920,612 @@ static uint8_t ch34x_get_lcr(uint8_t stop_bits, uint8_t parity, uint8_t data_bit return lcr; } - #endif // CFG_TUH_CDC_CH34X +//--------------------------------------------------------------------+ +// PL2303 +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_PL2303 + +static pl2303_type_t pl2303_detect_type(cdch_interface_t *p_cdc, uint8_t step); +static bool pl2303_encode_baud_rate(cdch_interface_t *p_cdc, uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE]); + +//------------- Control Request -------------// +static bool pl2303_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType = requesttype, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = tu_htole16(length) + }; + + // use usbh enum buf since application variable does not live long enough + uint8_t *enum_buf = NULL; + + if (buffer && length > 0) { + enum_buf = usbh_get_enum_buf(); + if (request_setup.bmRequestType_bit.direction == TUSB_DIR_OUT) { + tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + } + } + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request_setup, + .buffer = enum_buf, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +static bool pl2303_vendor_read(cdch_interface_t *p_cdc, uint16_t value, uint8_t *buf, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t request = p_cdc->pl2303.type == PL2303_TYPE_HXN ? PL2303_VENDOR_READ_NREQUEST : PL2303_VENDOR_READ_REQUEST; + return pl2303_set_request(p_cdc, request, PL2303_VENDOR_READ_REQUEST_TYPE, value, 0, buf, 1, complete_cb, user_data); +} + +static bool pl2303_vendor_write(cdch_interface_t *p_cdc, uint16_t value, uint16_t index, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t request = p_cdc->pl2303.type == PL2303_TYPE_HXN ? PL2303_VENDOR_WRITE_NREQUEST : PL2303_VENDOR_WRITE_REQUEST; + return pl2303_set_request(p_cdc, request, PL2303_VENDOR_WRITE_REQUEST_TYPE, value, index, NULL, 0, complete_cb, user_data); +} + +static inline bool pl2303_supports_hx_status(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t buf = 0; + return pl2303_set_request(p_cdc, PL2303_VENDOR_READ_REQUEST, PL2303_VENDOR_READ_REQUEST_TYPE, PL2303_READ_TYPE_HX_STATUS, 0, + &buf, 1, complete_cb, user_data); +} + +//static bool pl2303_get_line_request(cdch_interface_t * p_cdc, uint8_t buf[PL2303_LINE_CODING_BUFSIZE]) { +// return pl2303_set_request(p_cdc, PL2303_GET_LINE_REQUEST, PL2303_GET_LINE_REQUEST_TYPE, 0, 0, buf, PL2303_LINE_CODING_BUFSIZE); +//} + +//static bool pl2303_set_break(cdch_interface_t * p_cdc, bool enable) { +// uint16_t state = enable ? PL2303_BREAK_ON : PL2303_BREAK_OFF; +// return pl2303_set_request(p_cdc, PL2303_BREAK_REQUEST, PL2303_BREAK_REQUEST_TYPE, state, 0, NULL, 0); +//} + +static inline int pl2303_clear_halt(cdch_interface_t *p_cdc, uint8_t endp, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + /* we don't care if it wasn't halted first. in fact some devices + * (like some ibmcam model 1 units) seem to expect hosts to make + * this request for iso endpoints, which can't halt! + */ + return pl2303_set_request(p_cdc, TUSB_REQ_CLEAR_FEATURE, PL2303_CLEAR_HALT_REQUEST_TYPE, TUSB_REQ_FEATURE_EDPT_HALT, endp, + NULL, 0, complete_cb, user_data); +} + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void pl2303_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + if (xfer->setup->bRequest == PL2303_SET_LINE_REQUEST && + xfer->setup->bmRequestType == PL2303_SET_LINE_REQUEST_TYPE) { + p_cdc->line.coding = p_cdc->requested_line.coding; + } + if (xfer->setup->bRequest == PL2303_SET_CONTROL_REQUEST && + xfer->setup->bmRequestType == PL2303_SET_CONTROL_REQUEST_TYPE) { + p_cdc->line.control_state = p_cdc->requested_line.control_state; + } +} + +static bool pl2303_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // the caller has to precheck, that the new line coding different than the current, else false returned + uint8_t buf[PL2303_LINE_CODING_BUFSIZE]; + /* + * Some PL2303 are known to lose bytes if you change serial settings + * even to the same values as before. Thus we actually need to filter + * in this specific case. + */ + TU_VERIFY(p_cdc->requested_line.coding.data_bits != p_cdc->line.coding.data_bits || + p_cdc->requested_line.coding.stop_bits != p_cdc->line.coding.stop_bits || + p_cdc->requested_line.coding.parity != p_cdc->line.coding.parity || + p_cdc->requested_line.coding.bit_rate != p_cdc->line.coding.bit_rate ); + + /* For reference buf[6] data bits value */ + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8, 0); + buf[6] = p_cdc->requested_line.coding.data_bits; + + /* For reference buf[0]:buf[3] baud rate value */ + TU_VERIFY(pl2303_encode_baud_rate(p_cdc, &buf[0])); + + /* For reference buf[4]=0 is 1 stop bits */ + /* For reference buf[4]=1 is 1.5 stop bits */ + /* For reference buf[4]=2 is 2 stop bits */ + buf[4] = p_cdc->requested_line.coding.stop_bits; // PL2303 has the same coding + + /* For reference buf[5]=0 is none parity */ + /* For reference buf[5]=1 is odd parity */ + /* For reference buf[5]=2 is even parity */ + /* For reference buf[5]=3 is mark parity */ + /* For reference buf[5]=4 is space parity */ + buf[5] = p_cdc->requested_line.coding.parity; // PL2303 has the same coding + + return pl2303_set_request(p_cdc, PL2303_SET_LINE_REQUEST, PL2303_SET_LINE_REQUEST_TYPE, 0, 0, + buf, PL2303_LINE_CODING_BUFSIZE, complete_cb, user_data); +} + +static bool pl2303_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // PL2303 has the same bit coding + return pl2303_set_request(p_cdc, PL2303_SET_CONTROL_REQUEST, PL2303_SET_CONTROL_REQUEST_TYPE, + p_cdc->requested_line.control_state.value, 0, NULL, 0, complete_cb, user_data); +} + +//------------- Enumeration -------------// + +enum { + CONFIG_PL2303_DETECT_TYPE = 0, + CONFIG_PL2303_READ1, + CONFIG_PL2303_WRITE1, + CONFIG_PL2303_READ2, + CONFIG_PL2303_READ3, + CONFIG_PL2303_READ4, + CONFIG_PL2303_WRITE2, + CONFIG_PL2303_READ5, + CONFIG_PL2303_READ6, + CONFIG_PL2303_WRITE3, + CONFIG_PL2303_WRITE4, + CONFIG_PL2303_WRITE5, + CONFIG_PL2303_RESET_ENDP1, + CONFIG_PL2303_RESET_ENDP2, +// CONFIG_PL2303_FLOW_CTRL_READ, +// CONFIG_PL2303_FLOW_CTRL_WRITE, + CONFIG_PL2303_COMPLETE +}; + +static bool pl2303_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + // PL2303 Interface includes 1 vendor interface + 1 interrupt endpoints + 2 bulk + TU_VERIFY(itf_desc->bNumEndpoints == 3); + TU_VERIFY(sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t) <= max_len); + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); + + p_cdc->serial_drid = SERIAL_DRIVER_PL2303; + p_cdc->pl2303.quirks = 0; + p_cdc->pl2303.supports_hx_status = false; + + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + // Interrupt endpoint: not used for now + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(desc_ep) && + TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer); + TU_ASSERT(tuh_edpt_open(daddr, desc_ep)); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + desc_ep += 1; + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep)); + + return true; +} + +static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + // state CONFIG_PL2303_READ1 may have no success due to expected stall by pl2303_supports_hx_status() + const uintptr_t state = xfer->user_data; + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS || state == CONFIG_PL2303_READ1); + uint8_t* enum_buf = usbh_get_enum_buf(); + pl2303_type_t type; + + switch (state) { + // from here sequence overtaken from Linux Kernel function pl2303_startup() + case CONFIG_PL2303_DETECT_TYPE: + // get type and quirks (step 1) + type = pl2303_detect_type(p_cdc, 1); + TU_ASSERT(type != PL2303_TYPE_UNKNOWN); + if (type == PL2303_TYPE_NEED_SUPPORTS_HX_STATUS) { + TU_ASSERT(pl2303_supports_hx_status(p_cdc, cdch_process_set_config, CONFIG_PL2303_READ1)); + break; + } else { + // no transfer triggered and continue with CONFIG_PL2303_READ1 + TU_ATTR_FALLTHROUGH; + } + + case CONFIG_PL2303_READ1: + // get supports_hx_status, type and quirks (step 2), do special read + // will not be true, if coming directly from previous case + if (xfer->user_data == CONFIG_PL2303_READ1 && xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->pl2303.supports_hx_status = true; + } + type = pl2303_detect_type(p_cdc, 2); // step 2 now with supports_hx_status + TU_ASSERT(type != PL2303_TYPE_UNKNOWN); + TU_LOG_DRV(" PL2303 type detected: %u\r\n", type); + + p_cdc->pl2303.type = type; + p_cdc->pl2303.quirks |= pl2303_type_data[type].quirks; + + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE1)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE1: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0x0404, 0, cdch_process_set_config, CONFIG_PL2303_READ2)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ2: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ3)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ3: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ4)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ4: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE2)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE2: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0x0404, 1, cdch_process_set_config, CONFIG_PL2303_READ5)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ5: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ6)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ6: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE3)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE3: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0, 1, cdch_process_set_config, CONFIG_PL2303_WRITE4)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE4: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 1, 0, cdch_process_set_config, CONFIG_PL2303_WRITE5)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE5: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + uint16_t const windex = (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) ? 0x24 : 0x44; + TU_ASSERT(pl2303_vendor_write(p_cdc, 2, windex, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP1)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + // from here sequence overtaken from Linux Kernel function pl2303_open() + case CONFIG_PL2303_RESET_ENDP1: + // step 1 + if (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) { + TU_ASSERT(pl2303_clear_halt(p_cdc, PL2303_OUT_EP, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP2)); + } else { + /* reset upstream data pipes */ + if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, PL2303_HXN_RESET_REG,// skip CONFIG_PL2303_RESET_ENDP2, no 2nd step + PL2303_HXN_RESET_UPSTREAM_PIPE | PL2303_HXN_RESET_DOWNSTREAM_PIPE, + cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } else { + pl2303_vendor_write(p_cdc, 8, 0, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP2); + } + } + break; + + case CONFIG_PL2303_RESET_ENDP2: + // step 2 + if (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) { + TU_ASSERT(pl2303_clear_halt(p_cdc, PL2303_IN_EP, cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } else { + /* reset upstream data pipes */ + if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // here nothing to do, only structure of previous step overtaken for better reading and comparison + } else { + TU_ASSERT(pl2303_vendor_write(p_cdc, 9, 0, cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } + } + break; + + // skipped, because it's not working with each PL230x. flow control can be also set by PL2303 EEPROM Writer Program + // case CONFIG_PL2303_FLOW_CTRL_READ: + // // read flow control register for modify & write back in next step + // if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // TU_LOG_P_CDC ( "1\r\n" ); + // TU_ASSERT(pl2303_vendor_read(p_cdc, PL2303_HXN_FLOWCTRL_REG, &buf, + // cdch_process_set_config, CONFIG_PL2303_FLOW_CTRL_WRITE)); + // } else { + // TU_LOG_P_CDC ( "2\r\n" ); + // TU_ASSERT(pl2303_vendor_read(p_cdc, 0, &buf, cdch_process_set_config, CONFIG_PL2303_FLOW_CTRL_WRITE)); + // } + // break; + // + // case CONFIG_PL2303_FLOW_CTRL_WRITE: + // // no flow control + // buf = xfer->buffer[0]; + // if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // buf &= (uint8_t) ~PL2303_HXN_FLOWCTRL_MASK; + // buf |= PL2303_HXN_FLOWCTRL_NONE; + // TU_ASSERT(pl2303_vendor_write(p_cdc, PL2303_HXN_FLOWCTRL_REG, buf, + // cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + // } else { + // buf &= (uint8_t) ~PL2303_FLOWCTRL_MASK; + // TU_ASSERT(pl2303_vendor_write(p_cdc, 0, buf, + // cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + // } + // break; + + case CONFIG_PL2303_COMPLETE: + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +static pl2303_type_t pl2303_detect_type(cdch_interface_t *p_cdc, uint8_t step) { + tusb_desc_device_t desc_dev; + TU_VERIFY(tuh_descriptor_get_device_local(p_cdc->daddr, &desc_dev), PL2303_TYPE_UNKNOWN); + + // Legacy PL2303H, variants 0 and 1 (difference unknown). + if (desc_dev.bDeviceClass == 0x02) { + return PL2303_TYPE_H; /* variant 0 */ + } + + if (desc_dev.bMaxPacketSize0 != 0x40) { + if (desc_dev.bDeviceClass == 0x00 || desc_dev.bDeviceClass == 0xff) { + return PL2303_TYPE_H; /* variant 1 */ + } + return PL2303_TYPE_H; /* variant 0 */ + } + + switch (desc_dev.bcdUSB) { + case 0x101: + /* USB 1.0.1? Let's assume they meant 1.1... */ + TU_ATTR_FALLTHROUGH; + case 0x110: + switch (desc_dev.bcdDevice) { + case 0x300: return PL2303_TYPE_HX; + case 0x400: return PL2303_TYPE_HXD; + default: return PL2303_TYPE_HX; + } + break; + + case 0x200: + switch (desc_dev.bcdDevice) { + case 0x100: /* GC */ + case 0x105: + return PL2303_TYPE_HXN; + + case 0x300: /* GT / TA */ + if (step == 1) { + // step 1 trigger pl2303_supports_hx_status() request + return PL2303_TYPE_NEED_SUPPORTS_HX_STATUS; + } else { + // step 2 use supports_hx_status + if (p_cdc->pl2303.supports_hx_status) { + return PL2303_TYPE_TA; + } + } + TU_ATTR_FALLTHROUGH; + case 0x305: + case 0x400: /* GL */ + case 0x405: + return PL2303_TYPE_HXN; + + case 0x500: /* GE / TB */ + if (step == 1) { + // step 1 trigger pl2303_supports_hx_status() request + return PL2303_TYPE_NEED_SUPPORTS_HX_STATUS; + } else { + // step 2 use supports_hx_status + if (p_cdc->pl2303.supports_hx_status) { + return PL2303_TYPE_TB; + } + } + TU_ATTR_FALLTHROUGH; + case 0x505: + case 0x600: /* GS */ + case 0x605: + case 0x700: /* GR */ + case 0x705: + return PL2303_TYPE_HXN; + + default: + break; + } + break; + default: break; + } + + TU_LOG_CDC(p_cdc, "unknown device type bcdUSB = 0x%04x", desc_dev.bcdUSB); + return PL2303_TYPE_UNKNOWN; +} + +/* + * Returns the nearest supported baud rate that can be set directly without + * using divisors. + */ +static uint32_t pl2303_get_supported_baud_rate(uint32_t baud) { + static const uint32_t baud_sup[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, + 14400, 19200, 28800, 38400, 57600, 115200, 230400, 460800, + 614400, 921600, 1228800, 2457600, 3000000, 6000000 + }; + + uint8_t i; + for (i = 0; i < TU_ARRAY_SIZE(baud_sup); ++i) { + if (baud_sup[i] > baud) { + break; + } + } + + if (i == TU_ARRAY_SIZE(baud_sup)) { + baud = baud_sup[i - 1]; + } else if (i > 0 && (baud_sup[i] - baud) > (baud - baud_sup[i - 1])) { + baud = baud_sup[i - 1]; + } else { + baud = baud_sup[i]; + } + + return baud; +} + +/* + * NOTE: If unsupported baud rates are set directly, the PL2303 seems to + * use 9600 baud. + */ +static uint32_t pl2303_encode_baud_rate_direct(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baud_le = tu_htole32(baud); + buf[0] = (uint8_t) ( baud_le & 0xff); + buf[1] = (uint8_t) ((baud_le >> 8) & 0xff); + buf[2] = (uint8_t) ((baud_le >> 16) & 0xff); + buf[3] = (uint8_t) ((baud_le >> 24) & 0xff); + + return baud; +} + +static uint32_t pl2303_encode_baud_rate_divisor(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baseline, mantissa, exponent; + + /* + * Apparently the formula is: + * baudrate = 12M * 32 / (mantissa * 4^exponent) + * where + * mantissa = buf[8:0] + * exponent = buf[11:9] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) + mantissa = 1; /* Avoid dividing by zero if baud > 32 * 12M. */ + exponent = 0; + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; /* divide by 4 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 511; + break; + } + } + + buf[3] = 0x80; + buf[2] = 0; + buf[1] = (uint8_t) ((exponent << 1 | mantissa >> 8) & 0xff); + buf[0] = (uint8_t) (mantissa & 0xff); + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> (exponent << 1); + + return baud; +} + +static uint32_t pl2303_encode_baud_rate_divisor_alt(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baseline, mantissa, exponent; + + /* + * Apparently, for the TA version the formula is: + * baudrate = 12M * 32 / (mantissa * 2^exponent) + * where + * mantissa = buf[10:0] + * exponent = buf[15:13 16] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) { + mantissa = 1; /* Avoid dividing by zero if baud > 32 * 12M. */ + } + exponent = 0; + while (mantissa >= 2048) { + if (exponent < 15) { + mantissa >>= 1; /* divide by 2 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 2047; + break; + } + } + + buf[3] = 0x80; + buf[2] = (uint8_t) (exponent & 0x01); + buf[1] = (uint8_t) (((exponent & (uint32_t) ~0x01) << 4 | mantissa >> 8) & 0xff); + buf[0] = (uint8_t) (mantissa & 0xff); + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> exponent; + + return baud; +} + +static bool pl2303_encode_baud_rate(cdch_interface_t *p_cdc, uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE]) { + uint32_t baud = p_cdc->requested_line.coding.bit_rate; + uint32_t baud_sup; + const pl2303_type_data_t* type_data = &pl2303_type_data[p_cdc->pl2303.type]; + + TU_VERIFY(baud && baud <= type_data->max_baud_rate); + /* + * Use direct method for supported baud rates, otherwise use divisors. + * Newer chip types do not support divisor encoding. + */ + if (type_data->no_divisors) { + baud_sup = baud; + } else { + baud_sup = pl2303_get_supported_baud_rate(baud); + } + + if (baud == baud_sup) { + baud = pl2303_encode_baud_rate_direct(buf, baud); + } else if (type_data->alt_divisors) { + baud = pl2303_encode_baud_rate_divisor_alt(buf, baud); + } else { + baud = pl2303_encode_baud_rate_divisor(buf, baud); + } + TU_LOG_CDC(p_cdc, "real baudrate %lu", baud); + + return true; +} + +#endif // CFG_TUH_CDC_PL2303 + #endif diff --git a/src/class/cdc/cdc_host.h b/src/class/cdc/cdc_host.h index 8c5399534..37bfca270 100644 --- a/src/class/cdc/cdc_host.h +++ b/src/class/cdc/cdc_host.h @@ -37,16 +37,6 @@ // Class Driver Configuration //--------------------------------------------------------------------+ -// Set Line Control state on enumeration/mounted: DTR ( bit 0), RTS (bit 1) -#ifndef CFG_TUH_CDC_LINE_CONTROL_ON_ENUM -#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0 -#endif - -// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t -//#ifndef CFG_TUH_CDC_LINE_CODING_ON_ENUM -//#define CFG_TUH_CDC_LINE_CODING_ON_ENUM { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } -//#endif - // RX FIFO size #ifndef CFG_TUH_CDC_RX_BUFSIZE #define CFG_TUH_CDC_RX_BUFSIZE TUH_EPSIZE_BULK_MPS @@ -79,14 +69,27 @@ uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num); // return true if index is correct and interface is currently mounted bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t* info); -// Check if a interface is mounted +// Check if an interface is mounted bool tuh_cdc_mounted(uint8_t idx); +// Get local (cached) line state +// This function should return correct values if tuh_cdc_set_control_line_state() / tuh_cdc_get_control_line_state() +// are invoked previously or CFG_TUH_CDC_LINE_STATE_ON_ENUM is defined. +bool tuh_cdc_get_control_line_state_local(uint8_t idx, uint16_t* line_state); + // Get current DTR status -bool tuh_cdc_get_dtr(uint8_t idx); +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_get_dtr(uint8_t idx) { + uint16_t line_state; + TU_VERIFY(tuh_cdc_get_control_line_state_local(idx, &line_state)); + return (line_state & CDC_CONTROL_LINE_STATE_DTR) != 0; +} // Get current RTS status -bool tuh_cdc_get_rts(uint8_t idx); +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_get_rts(uint8_t idx) { + uint16_t line_state; + TU_VERIFY(tuh_cdc_get_control_line_state_local(idx, &line_state)); + return (line_state & CDC_CONTROL_LINE_STATE_RTS) != 0; +} // Check if interface is connected (DTR active) TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connected(uint8_t idx) { @@ -97,7 +100,9 @@ TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connected(uint8_t idx) { // This function should return correct values if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() // are invoked previously or CFG_TUH_CDC_LINE_CODING_ON_ENUM is defined. // NOTE: This function does not make any USB transfer request to device. -bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding); +bool tuh_cdc_get_line_coding_local(uint8_t idx, cdc_line_coding_t* line_coding); + +#define tuh_cdc_get_local_line_coding tuh_cdc_get_line_coding_local // backward compatibility //--------------------------------------------------------------------+ // Write API @@ -132,18 +137,31 @@ bool tuh_cdc_peek(uint8_t idx, uint8_t* ch); bool tuh_cdc_read_clear (uint8_t idx); //--------------------------------------------------------------------+ -// Control Endpoint (Request) API +// Control Request API // Each Function will make a USB control transfer request to/from device // - If complete_cb is provided, the function will return immediately and invoke // the callback when request is complete. // - If complete_cb is NULL, the function will block until request is complete. -// - In this case, user_data should be pointed to xfer_result_t to hold the transfer result. -// - The function will return true if transfer is successful, false otherwise. +// In this case, user_data should be usb_xfer_result_t* to hold the transfer result. //--------------------------------------------------------------------+ // Request to Set Control Line State: DTR (bit 0), RTS (bit 1) bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +// Request to Set DTR +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_set_dtr(uint8_t idx, bool dtr_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdc_line_control_state_t line_state = { .dtr = dtr_state }; + line_state.rts = tuh_cdc_get_rts(idx); + return tuh_cdc_set_control_line_state(idx, line_state.value, complete_cb, user_data); +} + +// Request to Set RTS +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_set_rts(uint8_t idx, bool rts_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdc_line_control_state_t line_state = { .rts = rts_state }; + line_state.dtr = tuh_cdc_get_dtr(idx); + return tuh_cdc_set_control_line_state(idx, line_state.value, complete_cb, user_data); +} + // Request to set baudrate bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); @@ -160,18 +178,53 @@ bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const* line_coding, // bool tuh_cdc_get_line_coding(uint8_t idx, cdc_line_coding_t* coding); // Connect by set both DTR, RTS -TU_ATTR_ALWAYS_INLINE static inline -bool tuh_cdc_connect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return tuh_cdc_set_control_line_state(idx, CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS, complete_cb, user_data); } // Disconnect by clear both DTR, RTS -TU_ATTR_ALWAYS_INLINE static inline -bool tuh_cdc_disconnect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_disconnect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return tuh_cdc_set_control_line_state(idx, 0x00, complete_cb, user_data); } //--------------------------------------------------------------------+ +// Control Request Sync API +// Each Function will make a USB control transfer request to/from device the function will block until request is +// complete. The function will return the transfer request result +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_control_line_state_sync(uint8_t idx, uint16_t line_state) { + TU_API_SYNC(tuh_cdc_set_control_line_state, idx, line_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_dtr_sync(uint8_t idx, bool dtr_state) { + TU_API_SYNC(tuh_cdc_set_dtr, idx, dtr_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_rts_sync(uint8_t idx, bool rts_state) { + TU_API_SYNC(tuh_cdc_set_rts, idx, rts_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_baudrate_sync(uint8_t idx, uint32_t baudrate) { + TU_API_SYNC(tuh_cdc_set_baudrate, idx, baudrate); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_data_format_sync(uint8_t idx, uint8_t stop_bits, uint8_t parity, uint8_t data_bits) { + TU_API_SYNC(tuh_cdc_set_data_format, idx, stop_bits, parity, data_bits); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_line_coding_sync(uint8_t idx, cdc_line_coding_t const* line_coding) { + TU_API_SYNC(tuh_cdc_set_line_coding, idx, line_coding); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_connect_sync(uint8_t idx) { + TU_API_SYNC(tuh_cdc_connect, idx); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_disconnect_sync(uint8_t idx) { + TU_API_SYNC(tuh_cdc_disconnect, idx); +} + +//--------------------------------------------------------------------+ // CDC APPLICATION CALLBACKS //--------------------------------------------------------------------+ diff --git a/src/class/cdc/serial/ch34x.h b/src/class/cdc/serial/ch34x.h index c18066f57..0e08b0acd 100644 --- a/src/class/cdc/serial/ch34x.h +++ b/src/class/cdc/serial/ch34x.h @@ -24,8 +24,8 @@ * This file is part of the TinyUSB stack. */ -#ifndef _CH34X_H_ -#define _CH34X_H_ +#ifndef TUSB_CH34X_H +#define TUSB_CH34X_H // There is no official documentation for the CH34x (CH340, CH341) chips. Reference can be found // - https://github.com/WCHSoftGroup/ch341ser_linux @@ -34,51 +34,51 @@ // set line_coding @ enumeration #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM -#define CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X CFG_TUH_CDC_LINE_CODING_ON_ENUM + #define CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X CFG_TUH_CDC_LINE_CODING_ON_ENUM #else // this default is necessary to work properly -#define CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X { 9600, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + #define CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X { 9600, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } #endif // USB requests -#define CH34X_REQ_READ_VERSION 0x5F // dec 95 -#define CH34X_REQ_WRITE_REG 0x9A // dec 154 -#define CH34X_REQ_READ_REG 0x95 // dec 149 -#define CH34X_REQ_SERIAL_INIT 0xA1 // dec 161 -#define CH34X_REQ_MODEM_CTRL 0xA4 // dev 164 +#define CH34X_REQ_READ_VERSION 0x5F // dec 95 +#define CH34X_REQ_WRITE_REG 0x9A // dec 154 +#define CH34X_REQ_READ_REG 0x95 // dec 149 +#define CH34X_REQ_SERIAL_INIT 0xA1 // dec 161 +#define CH34X_REQ_MODEM_CTRL 0xA4 // dev 164 // registers -#define CH34X_REG_BREAK 0x05 -#define CH34X_REG_PRESCALER 0x12 -#define CH34X_REG_DIVISOR 0x13 -#define CH34X_REG_LCR 0x18 -#define CH34X_REG_LCR2 0x25 -#define CH34X_REG_MCR_MSR 0x06 -#define CH34X_REG_MCR_MSR2 0x07 -#define CH34X_NBREAK_BITS 0x01 +#define CH34X_REG_BREAK 0x05 +#define CH34X_REG_PRESCALER 0x12 +#define CH34X_REG_DIVISOR 0x13 +#define CH34X_REG_LCR 0x18 +#define CH34X_REG_LCR2 0x25 +#define CH34X_REG_MCR_MSR 0x06 +#define CH34X_REG_MCR_MSR2 0x07 +#define CH34X_NBREAK_BITS 0x01 -#define CH341_REG_0x0F 0x0F // undocumented register -#define CH341_REG_0x2C 0x2C // undocumented register -#define CH341_REG_0x27 0x27 // hardware flow control (cts/rts) +#define CH341_REG_0x0F 0x0F // undocumented register +#define CH341_REG_0x2C 0x2C // undocumented register +#define CH341_REG_0x27 0x27 // hardware flow control (cts/rts) -#define CH34X_REG16_DIVISOR_PRESCALER TU_U16(CH34X_REG_DIVISOR, CH34X_REG_PRESCALER) -#define CH32X_REG16_LCR2_LCR TU_U16(CH34X_REG_LCR2, CH34X_REG_LCR) +#define CH34X_REG16_DIVISOR_PRESCALER TU_U16(CH34X_REG_DIVISOR, CH34X_REG_PRESCALER) +#define CH32X_REG16_LCR2_LCR TU_U16(CH34X_REG_LCR2, CH34X_REG_LCR) // modem control bits -#define CH34X_BIT_RTS ( 1 << 6 ) -#define CH34X_BIT_DTR ( 1 << 5 ) +#define CH34X_BIT_RTS (1 << 6) +#define CH34X_BIT_DTR (1 << 5) // line control bits -#define CH34X_LCR_ENABLE_RX 0x80 -#define CH34X_LCR_ENABLE_TX 0x40 -#define CH34X_LCR_MARK_SPACE 0x20 -#define CH34X_LCR_PAR_EVEN 0x10 -#define CH34X_LCR_ENABLE_PAR 0x08 -#define CH34X_LCR_PAR_MASK 0x38 // all parity bits -#define CH34X_LCR_STOP_BITS_2 0x04 -#define CH34X_LCR_CS8 0x03 -#define CH34X_LCR_CS7 0x02 -#define CH34X_LCR_CS6 0x01 -#define CH34X_LCR_CS5 0x00 -#define CH34X_LCR_CS_MASK 0x03 // all CSx bits +#define CH34X_LCR_ENABLE_RX 0x80 +#define CH34X_LCR_ENABLE_TX 0x40 +#define CH34X_LCR_MARK_SPACE 0x20 +#define CH34X_LCR_PAR_EVEN 0x10 +#define CH34X_LCR_ENABLE_PAR 0x08 +#define CH34X_LCR_PAR_MASK 0x38 // all parity bits +#define CH34X_LCR_STOP_BITS_2 0x04 +#define CH34X_LCR_CS8 0x03 +#define CH34X_LCR_CS7 0x02 +#define CH34X_LCR_CS6 0x01 +#define CH34X_LCR_CS5 0x00 +#define CH34X_LCR_CS_MASK 0x03 // all CSx bits -#endif /* _CH34X_H_ */ +#endif // TUSB_CH34X_H diff --git a/src/class/cdc/serial/cp210x.h b/src/class/cdc/serial/cp210x.h index 2c749f522..a0eff9e40 100644 --- a/src/class/cdc/serial/cp210x.h +++ b/src/class/cdc/serial/cp210x.h @@ -28,9 +28,10 @@ // Protocol details can be found at AN571: CP210x Virtual COM Port Interface // https://www.silabs.com/documents/public/application-notes/AN571.pdf -#define TU_CP210X_VID 0x10C4 +// parts are overtaken from vendors driver +// https://www.silabs.com/documents/public/software/cp210x-3.1.0.tar.gz -/* Config request codes */ +// Config request codes #define CP210X_IFC_ENABLE 0x00 #define CP210X_SET_BAUDDIV 0x01 #define CP210X_GET_BAUDDIV 0x02 @@ -59,4 +60,55 @@ #define CP210X_SET_BAUDRATE 0x1E #define CP210X_VENDOR_SPECIFIC 0xFF // GPIO, Recipient must be Device +// SILABSER_IFC_ENABLE_REQUEST_CODE +#define CP210X_UART_ENABLE 0x0001 +#define CP210X_UART_DISABLE 0x0000 + +// SILABSER_SET_BAUDDIV_REQUEST_CODE +#define CP210X_BAUD_RATE_GEN_FREQ 0x384000 + +// SILABSER_SET_LINE_CTL_REQUEST_CODE +#define CP210X_BITS_DATA_MASK 0x0f00 +#define CP210X_BITS_DATA_5 0x0500 +#define CP210X_BITS_DATA_6 0x0600 +#define CP210X_BITS_DATA_7 0x0700 +#define CP210X_BITS_DATA_8 0x0800 +#define CP210X_BITS_DATA_9 0x0900 + +#define CP210X_BITS_PARITY_MASK 0x00f0 +#define CP210X_BITS_PARITY_NONE 0x0000 +#define CP210X_BITS_PARITY_ODD 0x0010 +#define CP210X_BITS_PARITY_EVEN 0x0020 +#define CP210X_BITS_PARITY_MARK 0x0030 +#define CP210X_BITS_PARITY_SPACE 0x0040 + +#define CP210X_BITS_STOP_MASK 0x000f +#define CP210X_BITS_STOP_1 0x0000 +#define CP210X_BITS_STOP_1_5 0x0001 +#define CP210X_BITS_STOP_2 0x0002 + +// SILABSER_SET_BREAK_REQUEST_CODE +#define CP210X_BREAK_ON 0x0001 +#define CP210X_BREAK_OFF 0x0000 + +// SILABSER_SET_MHS_REQUEST_CODE +#define CP210X_MCR_DTR 0x0001 +#define CP210X_MCR_RTS 0x0002 +#define CP210X_MCR_ALL 0x0003 +#define CP210X_MSR_CTS 0x0010 +#define CP210X_MSR_DSR 0x0020 +#define CP210X_MSR_RING 0x0040 +#define CP210X_MSR_DCD 0x0080 +#define CP210X_MSR_ALL 0x00F0 + +#define CP210X_CONTROL_WRITE_DTR 0x0100UL +#define CP210X_CONTROL_WRITE_RTS 0x0200UL + +#define CP210X_LSR_BREAK 0x0001 +#define CP210X_LSR_FRAMING_ERROR 0x0002 +#define CP210X_LSR_HW_OVERRUN 0x0004 +#define CP210X_LSR_QUEUE_OVERRUN 0x0008 +#define CP210X_LSR_PARITY_ERROR 0x0010 +#define CP210X_LSR_ALL 0x001F + #endif //TUSB_CP210X_H diff --git a/src/class/cdc/serial/ftdi_sio.h b/src/class/cdc/serial/ftdi_sio.h index 0825f0719..8abf74f11 100644 --- a/src/class/cdc/serial/ftdi_sio.h +++ b/src/class/cdc/serial/ftdi_sio.h @@ -25,222 +25,207 @@ #ifndef TUSB_FTDI_SIO_H #define TUSB_FTDI_SIO_H -// VID for matching FTDI devices -#define TU_FTDI_VID 0x0403 +#include <stdint.h> // Commands -#define FTDI_SIO_RESET 0 /* Reset the port */ -#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ -#define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */ -#define FTDI_SIO_SET_BAUD_RATE 3 /* Set baud rate */ -#define FTDI_SIO_SET_DATA 4 /* Set the data characteristics of the port */ -#define FTDI_SIO_GET_MODEM_STATUS 5 /* Retrieve current value of modem status register */ -#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ -#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ -#define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ -#define FTDI_SIO_GET_LATENCY_TIMER 0x0a /* Get the latency timer */ -#define FTDI_SIO_SET_BITMODE 0x0b /* Set bitbang mode */ -#define FTDI_SIO_READ_PINS 0x0c /* Read immediate value of pins */ -#define FTDI_SIO_READ_EEPROM 0x90 /* Read EEPROM */ +#define FTDI_SIO_RESET 0 // Reset the port +#define FTDI_SIO_MODEM_CTRL 1 // Set the modem control register +#define FTDI_SIO_SET_FLOW_CTRL 2 // Set flow control register +#define FTDI_SIO_SET_BAUD_RATE 3 // Set baud rate +#define FTDI_SIO_SET_DATA 4 // Set the data characteristics of the port +#define FTDI_SIO_GET_MODEM_STATUS 5 // Retrieve current value of modem status register +#define FTDI_SIO_SET_EVENT_CHAR 6 // Set the event character +#define FTDI_SIO_SET_ERROR_CHAR 7 // Set the error character +#define FTDI_SIO_SET_LATENCY_TIMER 9 // Set the latency timer +#define FTDI_SIO_GET_LATENCY_TIMER 10 // Get the latency timer +#define FTDI_SIO_SET_BITMODE 11 // Set bitbang mode +#define FTDI_SIO_READ_PINS 12 // Read immediate value of pins +#define FTDI_SIO_READ_EEPROM 0x90 // Read EEPROM -/* FTDI_SIO_RESET */ -#define FTDI_SIO_RESET_SIO 0 -#define FTDI_SIO_RESET_PURGE_RX 1 -#define FTDI_SIO_RESET_PURGE_TX 2 +// Channel indices for FT2232, FT2232H and FT4232H devices +#define CHANNEL_A 1 +#define CHANNEL_B 2 +#define CHANNEL_C 3 +#define CHANNEL_D 4 -/* - * BmRequestType: 0100 0000B - * bRequest: FTDI_SIO_RESET - * wValue: Control Value - * 0 = Reset SIO - * 1 = Purge RX buffer - * 2 = Purge TX buffer - * wIndex: Port - * wLength: 0 - * Data: None - * - * The Reset SIO command has this effect: - * - * Sets flow control set to 'none' - * Event char = $0D - * Event trigger = disabled - * Purge RX buffer - * Purge TX buffer - * Clear DTR - * Clear RTS - * baud and data format not reset - * - * The Purge RX and TX buffer commands affect nothing except the buffers - * - */ +// Port Identifier Table +#define PIT_DEFAULT 0 // SIOA +#define PIT_SIOA 1 // SIOA +// The device this driver is tested with one has only one port +#define PIT_SIOB 2 // SIOB +#define PIT_PARALLEL 3 // Parallel -/* FTDI_SIO_MODEM_CTRL */ -/* - * BmRequestType: 0100 0000B - * bRequest: FTDI_SIO_MODEM_CTRL - * wValue: ControlValue (see below) - * wIndex: Port - * wLength: 0 - * Data: None - * - * NOTE: If the device is in RTS/CTS flow control, the RTS set by this - * command will be IGNORED without an error being returned - * Also - you can not set DTR and RTS with one control message - */ +// FTDI_SIO_RESET +#define FTDI_SIO_RESET_REQUEST FTDI_SIO_RESET +#define FTDI_SIO_RESET_REQUEST_TYPE 0x40 +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 -#define FTDI_SIO_SET_DTR_MASK 0x1 -#define FTDI_SIO_SET_DTR_HIGH ((FTDI_SIO_SET_DTR_MASK << 8) | 1) -#define FTDI_SIO_SET_DTR_LOW ((FTDI_SIO_SET_DTR_MASK << 8) | 0) -#define FTDI_SIO_SET_RTS_MASK 0x2 -#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2) -#define FTDI_SIO_SET_RTS_LOW ((FTDI_SIO_SET_RTS_MASK << 8) | 0) +// FTDI_SIO_SET_BAUDRATE +#define FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BAUDRATE_REQUEST 3 -/* - * ControlValue - * B0 DTR state - * 0 = reset - * 1 = set - * B1 RTS state - * 0 = reset - * 1 = set - * B2..7 Reserved - * B8 DTR state enable - * 0 = ignore - * 1 = use DTR state - * B9 RTS state enable - * 0 = ignore - * 1 = use RTS state - * B10..15 Reserved - */ +enum ftdi_sio_baudrate { + ftdi_sio_b300 = 0, + ftdi_sio_b600 = 1, + ftdi_sio_b1200 = 2, + ftdi_sio_b2400 = 3, + ftdi_sio_b4800 = 4, + ftdi_sio_b9600 = 5, + ftdi_sio_b19200 = 6, + ftdi_sio_b38400 = 7, + ftdi_sio_b57600 = 8, + ftdi_sio_b115200 = 9 +}; -/* FTDI_SIO_SET_FLOW_CTRL */ -#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 -#define FTDI_SIO_RTS_CTS_HS (0x1 << 8) -#define FTDI_SIO_DTR_DSR_HS (0x2 << 8) -#define FTDI_SIO_XON_XOFF_HS (0x4 << 8) +// FTDI_SIO_SET_DATA +#define FTDI_SIO_SET_DATA_REQUEST FTDI_SIO_SET_DATA +#define FTDI_SIO_SET_DATA_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) // same coding as ACM +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) // 1.5 not supported, for future use? +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) -/* - * BmRequestType: 0100 0000b - * bRequest: FTDI_SIO_SET_FLOW_CTRL - * wValue: Xoff/Xon - * wIndex: Protocol/Port - hIndex is protocol / lIndex is port - * wLength: 0 - * Data: None - * - * hIndex protocol is: - * B0 Output handshaking using RTS/CTS - * 0 = disabled - * 1 = enabled - * B1 Output handshaking using DTR/DSR - * 0 = disabled - * 1 = enabled - * B2 Xon/Xoff handshaking - * 0 = disabled - * 1 = enabled - * - * A value of zero in the hIndex field disables handshaking - * - * If Xon/Xoff handshaking is specified, the hValue field should contain the - * XOFF character and the lValue field contains the XON character. - */ +// FTDI_SIO_MODEM_CTRL +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST FTDI_SIO_MODEM_CTRL -/* FTDI_SIO_SET_BAUD_RATE */ -/* - * BmRequestType: 0100 0000B - * bRequest: FTDI_SIO_SET_BAUDRATE - * wValue: BaudDivisor value - see below - * wIndex: Port - * wLength: 0 - * Data: None - * The BaudDivisor values are calculated as follows (too complicated): - */ +#define FTDI_SIO_SET_DTR_MASK 0x1UL +#define FTDI_SIO_SET_DTR_HIGH ((FTDI_SIO_SET_DTR_MASK << 8) | 1UL) +#define FTDI_SIO_SET_DTR_LOW ((FTDI_SIO_SET_DTR_MASK << 8) | 0UL) +#define FTDI_SIO_SET_RTS_MASK 0x2UL +#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2UL) +#define FTDI_SIO_SET_RTS_LOW ((FTDI_SIO_SET_RTS_MASK << 8) | 0UL) -/* FTDI_SIO_SET_DATA */ -#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) -#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) -#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) -#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) -#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) -#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) -#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) -#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) -#define FTDI_SIO_SET_BREAK (0x1 << 14) +// FTDI_SIO_SET_FLOW_CTRL +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST FTDI_SIO_SET_FLOW_CTRL +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS (0x1 << 8) +#define FTDI_SIO_DTR_DSR_HS (0x2 << 8) +#define FTDI_SIO_XON_XOFF_HS (0x4 << 8) -/* - * BmRequestType: 0100 0000B - * bRequest: FTDI_SIO_SET_DATA - * wValue: Data characteristics (see below) - * wIndex: Port - * wLength: 0 - * Data: No - * - * Data characteristics - * - * B0..7 Number of data bits - * B8..10 Parity - * 0 = None - * 1 = Odd - * 2 = Even - * 3 = Mark - * 4 = Space - * B11..13 Stop Bits - * 0 = 1 - * 1 = 1.5 - * 2 = 2 - * B14 - * 1 = TX ON (break) - * 0 = TX OFF (normal state) - * B15 Reserved - * - */ +// FTDI_SIO_GET_LATENCY_TIMER +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST FTDI_SIO_GET_LATENCY_TIMER +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE 0xC0 -/* -* DATA FORMAT -* -* IN Endpoint -* -* The device reserves the first two bytes of data on this endpoint to contain -* the current values of the modem and line status registers. In the absence of -* data, the device generates a message consisting of these two status bytes - * every 40 ms - * - * Byte 0: Modem Status -* -* Offset Description -* B0 Reserved - must be 1 -* B1 Reserved - must be 0 -* B2 Reserved - must be 0 -* B3 Reserved - must be 0 -* B4 Clear to Send (CTS) -* B5 Data Set Ready (DSR) -* B6 Ring Indicator (RI) -* B7 Receive Line Signal Detect (RLSD) -* -* Byte 1: Line Status -* -* Offset Description -* B0 Data Ready (DR) -* B1 Overrun Error (OE) -* B2 Parity Error (PE) -* B3 Framing Error (FE) -* B4 Break Interrupt (BI) -* B5 Transmitter Holding Register (THRE) -* B6 Transmitter Empty (TEMT) -* B7 Error in RCVR FIFO -* -*/ -#define FTDI_RS0_CTS (1 << 4) -#define FTDI_RS0_DSR (1 << 5) -#define FTDI_RS0_RI (1 << 6) -#define FTDI_RS0_RLSD (1 << 7) +// FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40 + +// FTDI_SIO_SET_EVENT_CHAR +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST FTDI_SIO_SET_EVENT_CHAR +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST_TYPE 0x40 + +// FTDI_SIO_GET_MODEM_STATUS +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST FTDI_SIO_GET_MODEM_STATUS +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 + +// FTDI_SIO_SET_BITMODE +#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE + +// Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST +#define FTDI_SIO_BITMODE_RESET 0x00 +#define FTDI_SIO_BITMODE_CBUS 0x20 + +// FTDI_SIO_READ_PINS +#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS + +// FTDI_SIO_READ_EEPROM +#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM + +#define FTDI_FTX_CBUS_MUX_GPIO 0x8 +#define FTDI_FT232R_CBUS_MUX_GPIO 0xa + +#define FTDI_RS0_CTS (1 << 4) +#define FTDI_RS0_DSR (1 << 5) +#define FTDI_RS0_RI (1 << 6) +#define FTDI_RS0_RLSD (1 << 7) + +#define FTDI_RS_DR 1 +#define FTDI_RS_OE (1 << 1) +#define FTDI_RS_PE (1 << 2) +#define FTDI_RS_FE (1 << 3) +#define FTDI_RS_BI (1 << 4) +#define FTDI_RS_THRE (1 << 5) +#define FTDI_RS_TEMT (1 << 6) +#define FTDI_RS_FIFO (1 << 7) + +// chip types and names +typedef enum ftdi_chip_type { + FTDI_SIO = 0, +// FTDI_FT232A, + FTDI_FT232B, + FTDI_FT2232C, + FTDI_FT232R, + FTDI_FT232H, + FTDI_FT2232H, + FTDI_FT4232H, + FTDI_FT4232HA, + FTDI_FT232HP, + FTDI_FT233HP, + FTDI_FT2232HP, + FTDI_FT2233HP, + FTDI_FT4232HP, + FTDI_FT4233HP, + FTDI_FTX, + FTDI_UNKNOWN +} ftdi_chip_type_t; + +#define FTDI_CHIP_NAMES \ + [FTDI_SIO] = "SIO", /* the serial part of FT8U100AX */ \ +/* [FTDI_FT232A] = "FT232A", */ \ + [FTDI_FT232B] = "FT232B", \ + [FTDI_FT2232C] = "FT2232C/D", \ + [FTDI_FT232R] = "FT232R", \ + [FTDI_FT232H] = "FT232H", \ + [FTDI_FT2232H] = "FTDI_FT2232H", \ + [FTDI_FT4232H] = "FT4232H", \ + [FTDI_FT4232HA] = "FT4232HA", \ + [FTDI_FT232HP] = "FT232HP", \ + [FTDI_FT233HP] = "FT233HP", \ + [FTDI_FT2232HP] = "FT2232HP", \ + [FTDI_FT2233HP] = "FT2233HP", \ + [FTDI_FT4232HP] = "FT4232HP", \ + [FTDI_FT4233HP] = "FT4233HP", \ + [FTDI_FTX] = "FT-X", \ + [FTDI_UNKNOWN] = "UNKNOWN" + +// private interface data +typedef struct ftdi_private { + ftdi_chip_type_t chip_type; + uint8_t channel; // channel index, or 0 for legacy types +} ftdi_private_t; + +#define FTDI_OK true +#define FTDI_FAIL false +#define FTDI_NOT_POSSIBLE -1 +#define FTDI_REQUESTED -2 -#define FTDI_RS_DR 1 -#define FTDI_RS_OE (1<<1) -#define FTDI_RS_PE (1<<2) -#define FTDI_RS_FE (1<<3) -#define FTDI_RS_BI (1<<4) -#define FTDI_RS_THRE (1<<5) -#define FTDI_RS_TEMT (1<<6) -#define FTDI_RS_FIFO (1<<7) +// division and round function overtaken from math.h +#define DIV_ROUND_CLOSEST(x, divisor)( \ +{ \ + typeof(x) __x = x; \ + typeof(divisor) __d = divisor; \ + (((typeof(x))-1) > 0 || \ + ((typeof(divisor))-1) > 0 || \ + (((__x) > 0) == ((__d) > 0))) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +} \ +) #endif //TUSB_FTDI_SIO_H diff --git a/src/class/cdc/serial/pl2303.h b/src/class/cdc/serial/pl2303.h new file mode 100644 index 000000000..63910c7bb --- /dev/null +++ b/src/class/cdc/serial/pl2303.h @@ -0,0 +1,159 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Heiko Kuester + * + * 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_PL2303_H +#define TUSB_PL2303_H + +#include <stdbool.h> +#include <stdint.h> + +// There is no official documentation for the PL2303 chips. +// Reference can be found +// - https://github.com/torvalds/linux/blob/master/drivers/usb/serial/pl2303.h and +// https://github.com/torvalds/linux/blob/master/drivers/usb/serial/pl2303.c +// - https://github.com/freebsd/freebsd-src/blob/main/sys/dev/usb/serial/uplcom.c + +// quirks +#define PL2303_QUIRK_UART_STATE_IDX0 1 +#define PL2303_QUIRK_LEGACY 2 +#define PL2303_QUIRK_ENDPOINT_HACK 4 + +// requests and bits +#define PL2303_SET_LINE_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_SET_LINE_REQUEST 0x20 // dec 32 + +#define PL2303_SET_CONTROL_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_SET_CONTROL_REQUEST 0x22 // dec 34 +#define PL2303_CONTROL_DTR 0x01 // dec 1 +#define PL2303_CONTROL_RTS 0x02 // dec 2 + +#define PL2303_BREAK_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_BREAK_REQUEST 0x23 // dec 35 +#define PL2303_BREAK_ON 0xffff +#define PL2303_BREAK_OFF 0x0000 + +#define PL2303_GET_LINE_REQUEST_TYPE 0xa1 // class request device to host interface +#define PL2303_GET_LINE_REQUEST 0x21 // dec 33 + +#define PL2303_VENDOR_WRITE_REQUEST_TYPE 0x40 // vendor request host to device interface +#define PL2303_VENDOR_WRITE_REQUEST 0x01 // dec 1 +#define PL2303_VENDOR_WRITE_NREQUEST 0x80 // dec 128 + +#define PL2303_VENDOR_READ_REQUEST_TYPE 0xc0 // vendor request device to host interface +#define PL2303_VENDOR_READ_REQUEST 0x01 // dec 1 +#define PL2303_VENDOR_READ_NREQUEST 0x81 // dec 129 + +#define PL2303_UART_STATE_INDEX 8 +#define PL2303_UART_STATE_MSR_MASK 0x8b +#define PL2303_UART_STATE_TRANSIENT_MASK 0x74 +#define PL2303_UART_DCD 0x01 +#define PL2303_UART_DSR 0x02 +#define PL2303_UART_BREAK_ERROR 0x04 +#define PL2303_UART_RING 0x08 +#define PL2303_UART_FRAME_ERROR 0x10 +#define PL2303_UART_PARITY_ERROR 0x20 +#define PL2303_UART_OVERRUN_ERROR 0x40 +#define PL2303_UART_CTS 0x80 + +#define PL2303_FLOWCTRL_MASK 0xf0 + +#define PL2303_CLEAR_HALT_REQUEST_TYPE 0x02 // standard request host to device endpoint + +// registers via vendor read/write requests +#define PL2303_READ_TYPE_HX_STATUS 0x8080 + +#define PL2303_HXN_RESET_REG 0x07 +#define PL2303_HXN_RESET_UPSTREAM_PIPE 0x02 +#define PL2303_HXN_RESET_DOWNSTREAM_PIPE 0x01 + +#define PL2303_HXN_FLOWCTRL_REG 0x0a +#define PL2303_HXN_FLOWCTRL_MASK 0x1c +#define PL2303_HXN_FLOWCTRL_NONE 0x1c +#define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 +#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + +// type data +typedef enum pl2303_type { + PL2303_TYPE_H = 0, // 0 + PL2303_TYPE_HX, // 1 + PL2303_TYPE_TA, // 2 + PL2303_TYPE_TB, // 3 + PL2303_TYPE_HXD, // 4 + PL2303_TYPE_HXN, // 5 + PL2303_TYPE_COUNT, + PL2303_TYPE_NEED_SUPPORTS_HX_STATUS, + PL2303_TYPE_UNKNOWN, +} pl2303_type_t; + +typedef struct pl2303_type_data { + uint32_t max_baud_rate; + uint8_t quirks; + uint8_t no_autoxonxoff : 1; + uint8_t no_divisors : 1; + uint8_t alt_divisors : 1; +} pl2303_type_data_t; + +#define PL2303_TYPE_DATA \ + [PL2303_TYPE_H] = { \ + .max_baud_rate = 1228800, .quirks = PL2303_QUIRK_LEGACY, \ + .no_autoxonxoff = 1, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_HX] = { \ + .max_baud_rate = 6000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_TA] = { \ + .max_baud_rate = 6000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 1 \ + }, \ + [PL2303_TYPE_TB] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 1 \ + }, \ + [PL2303_TYPE_HXD] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_HXN] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 1, .alt_divisors = 0 \ + } + +typedef struct TU_ATTR_PACKED { + pl2303_type_t type; + uint8_t quirks; + bool supports_hx_status; +} pl2303_private_t; + +// buffer sizes for line coding data +#define PL2303_LINE_CODING_BUFSIZE 7 +#define PL2303_LINE_CODING_BAUDRATE_BUFSIZE 4 + +// bulk endpoints +#define PL2303_OUT_EP 0x02 +#define PL2303_IN_EP 0x83 + +#endif // TUSB_PL2303_H diff --git a/src/class/hid/hid.h b/src/class/hid/hid.h index c2434c20d..bbc58af80 100644 --- a/src/class/hid/hid.h +++ b/src/class/hid/hid.h @@ -893,14 +893,51 @@ enum { /// HID Usage Table: Consumer Page (0x0C) /// Only contains controls that supported by Windows (whole list is too long) enum { + HID_USAGE_CONSUMER_UNASSIGNED = 0x0000, + // Generic Control HID_USAGE_CONSUMER_CONTROL = 0x0001, + HID_USAGE_CONSUMER_NUMERIC_KEY_PAD = 0x0002, + HID_USAGE_CONSUMER_PROGRAMMABLE_BUTTONS = 0x0003, + HID_USAGE_CONSUMER_MICROPHONE = 0x0004, + HID_USAGE_CONSUMER_HEADPHONE = 0x0005, + HID_USAGE_CONSUMER_GRAPHIC_EQUALIZER = 0x0006, + // 07-1F Reserved + + HID_USAGE_CONSUMER_PLUS_10 = 0x0020, + HID_USAGE_CONSUMER_PLUS_100 = 0x0021, + HID_USAGE_CONSUMER_AM_PM = 0x0022, + // 23-3F Reserved // Power Control HID_USAGE_CONSUMER_POWER = 0x0030, HID_USAGE_CONSUMER_RESET = 0x0031, HID_USAGE_CONSUMER_SLEEP = 0x0032, + HID_USAGE_CONSUMER_SLEEP_AFTER = 0x0033, + HID_USAGE_CONSUMER_SLEEP_MODE = 0x0034, + HID_USAGE_CONSUMER_ILLUMINATION = 0x0035, + HID_USAGE_CONSUMER_FUNCTION_BUTTONS = 0x0036, + // 37-3F Reserved + HID_USAGE_CONSUMER_MENU = 0x0040, + HID_USAGE_CONSUMER_MENU_PICK = 0x0041, + HID_USAGE_CONSUMER_MENU_UP = 0x0042, + HID_USAGE_CONSUMER_MENU_DOWN = 0x0043, + HID_USAGE_CONSUMER_MENU_LEFT = 0x0044, + HID_USAGE_CONSUMER_MENU_RIGHT = 0x0045, + HID_USAGE_CONSUMER_MENU_ESCAPE = 0x0046, + HID_USAGE_CONSUMER_MENU_VALUE_INCREASE = 0x0047, + HID_USAGE_CONSUMER_MENU_VALUE_DECREASE = 0x0048, + // 49-5F Reserved + HID_USAGE_CONSUMER_DATA_ON_SCREEN = 0x0060, + HID_USAGE_CONSUMER_CLOSED_CAPTION = 0x0061, + HID_USAGE_CONSUMER_CLOSED_CAPTION_SELECT = 0x0062, + HID_USAGE_CONSUMER_VCR_TV = 0x0063, + HID_USAGE_CONSUMER_BROADCAST_MODE = 0x0064, + HID_USAGE_CONSUMER_SNAPSHOT = 0x0065, + HID_USAGE_CONSUMER_STILL = 0x0066, + + // 67-7F Reserved // Screen Brightness HID_USAGE_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F, HID_USAGE_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070, @@ -912,40 +949,382 @@ enum { HID_USAGE_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, HID_USAGE_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, + HID_USAGE_CONSUMER_SELECTION = 0x0080, + HID_USAGE_CONSUMER_ASSIGN_SELECTION = 0x0081, + HID_USAGE_CONSUMER_MODE_STEP = 0x0082, + HID_USAGE_CONSUMER_RECALL_LAST = 0x0083, + HID_USAGE_CONSUMER_ENTER_CHANNEL = 0x0084, + HID_USAGE_CONSUMER_ORDER_MOVIE = 0x0085, + HID_USAGE_CONSUMER_CHANNEL = 0x0086, + HID_USAGE_CONSUMER_MEDIA_SELECTION = 0x0087, + HID_USAGE_CONSUMER_MEDIA_SELECT_COMPUTER = 0x0088, + HID_USAGE_CONSUMER_MEDIA_SELECT_TV = 0x0089, + HID_USAGE_CONSUMER_MEDIA_SELECT_WWW = 0x008A, + HID_USAGE_CONSUMER_MEDIA_SELECT_DVD = 0x008B, + HID_USAGE_CONSUMER_MEDIA_SELECT_TELEPHONE = 0x008C, + HID_USAGE_CONSUMER_MEDIA_SELECT_PROGRAM_GUIDE = 0x008D, + HID_USAGE_CONSUMER_MEDIA_SELECT_VIDEO_PHONE = 0x008E, + HID_USAGE_CONSUMER_MEDIA_SELECT_GAMES = 0x008F, + HID_USAGE_CONSUMER_MEDIA_SELECT_MESSAGES = 0x0090, + HID_USAGE_CONSUMER_MEDIA_SELECT_CD = 0x0091, + HID_USAGE_CONSUMER_MEDIA_SELECT_VCR = 0x0092, + HID_USAGE_CONSUMER_MEDIA_SELECT_TUNER = 0x0093, + HID_USAGE_CONSUMER_QUIT = 0x0094, + HID_USAGE_CONSUMER_HELP = 0x0095, + HID_USAGE_CONSUMER_MEDIA_SELECT_TAPE = 0x0096, + HID_USAGE_CONSUMER_MEDIA_SELECT_CABLE = 0x0097, + HID_USAGE_CONSUMER_MEDIA_SELECT_SATELLITE = 0x0098, + HID_USAGE_CONSUMER_MEDIA_SELECT_SECURITY = 0x0099, + HID_USAGE_CONSUMER_MEDIA_SELECT_HOME = 0x009A, + HID_USAGE_CONSUMER_MEDIA_SELECT_CALL = 0x009B, + HID_USAGE_CONSUMER_CHANNEL_INCREMENT = 0x009C, + HID_USAGE_CONSUMER_CHANNEL_DECREMENT = 0x009D, + HID_USAGE_CONSUMER_MEDIA_SELECT_SAP = 0x009E, + // 9F Reserved + HID_USAGE_CONSUMER_VCR_PLUS = 0x00A0, + HID_USAGE_CONSUMER_ONCE = 0x00A1, + HID_USAGE_CONSUMER_DAILY = 0x00A2, + HID_USAGE_CONSUMER_WEEKLY = 0x00A3, + HID_USAGE_CONSUMER_MONTHLY = 0x00A4, + // A5-AF Reserved + + HID_USAGE_CONSUMER_PLAY = 0x00B0, + HID_USAGE_CONSUMER_PAUSE = 0x00B1, + HID_USAGE_CONSUMER_RECORD = 0x00B2, + HID_USAGE_CONSUMER_FAST_FORWARD = 0x00B3, + HID_USAGE_CONSUMER_REWIND = 0x00B4, + HID_USAGE_CONSUMER_SCAN_NEXT_TRACK = 0x00B5, + HID_USAGE_CONSUMER_SCAN_PREVIOUS_TRACK = 0x00B6, + HID_USAGE_CONSUMER_STOP = 0x00B7, + HID_USAGE_CONSUMER_EJECT = 0x00B8, + HID_USAGE_CONSUMER_RANDOM_PLAY = 0x00B9, + HID_USAGE_CONSUMER_SELECT_DISC = 0x00BA, + HID_USAGE_CONSUMER_ENTER_DISC = 0x00BB, + HID_USAGE_CONSUMER_REPEAT = 0x00BC, + HID_USAGE_CONSUMER_TRACKING = 0x00BD, + HID_USAGE_CONSUMER_TRACK_NORMAL = 0x00BE, + HID_USAGE_CONSUMER_SLOW_TRACKING = 0x00BF, + HID_USAGE_CONSUMER_FRAME_FORWARD = 0x00C0, + HID_USAGE_CONSUMER_FRAME_BACK = 0x00C1, + HID_USAGE_CONSUMER_MARK = 0x00C2, + HID_USAGE_CONSUMER_CLEAR_MARK = 0x00C3, + HID_USAGE_CONSUMER_REPEAT_FROM_MARK = 0x00C4, + HID_USAGE_CONSUMER_RETURN_TO_MARK = 0x00C5, + HID_USAGE_CONSUMER_SEARCH_MARK_FORWARD = 0x00C6, + HID_USAGE_CONSUMER_SEARCH_MARK_BACKWARDS = 0x00C7, + HID_USAGE_CONSUMER_COUNTER_RESET = 0x00C8, + HID_USAGE_CONSUMER_SHOW_COUNTER = 0x00C9, + HID_USAGE_CONSUMER_TRACKING_INCREMENT = 0x00CA, + HID_USAGE_CONSUMER_TRACKING_DECREMENT = 0x00CB, + HID_USAGE_CONSUMER_STOP_EJECT = 0x00CC, + + // Media Control HID_USAGE_CONSUMER_PLAY_PAUSE = 0x00CD, - HID_USAGE_CONSUMER_SCAN_NEXT = 0x00B5, - HID_USAGE_CONSUMER_SCAN_PREVIOUS = 0x00B6, - HID_USAGE_CONSUMER_STOP = 0x00B7, + + HID_USAGE_CONSUMER_PLAY_SKIP = 0x00CE, + + // CF-DF Reserved HID_USAGE_CONSUMER_VOLUME = 0x00E0, + HID_USAGE_CONSUMER_BALANCE = 0x00E1, HID_USAGE_CONSUMER_MUTE = 0x00E2, HID_USAGE_CONSUMER_BASS = 0x00E3, HID_USAGE_CONSUMER_TREBLE = 0x00E4, HID_USAGE_CONSUMER_BASS_BOOST = 0x00E5, + HID_USAGE_CONSUMER_SURROUND_MODE = 0x00E6, + HID_USAGE_CONSUMER_LOUDNESS = 0x00E7, + HID_USAGE_CONSUMER_MPX = 0x00E8, HID_USAGE_CONSUMER_VOLUME_INCREMENT = 0x00E9, HID_USAGE_CONSUMER_VOLUME_DECREMENT = 0x00EA, + // EB-EF Reserved + HID_USAGE_CONSUMER_SPEED_SELECT = 0x00F0, + HID_USAGE_CONSUMER_PLAYBACK_SPEED = 0x00F1, + HID_USAGE_CONSUMER_STANDARD_PLAY = 0x00F2, + HID_USAGE_CONSUMER_LONG_PLAY = 0x00F3, + HID_USAGE_CONSUMER_EXTENDED_PLAY = 0x00F4, + HID_USAGE_CONSUMER_SLOW = 0x00F5, + // F6-FF Reserved + HID_USAGE_CONSUMER_FAN_ENABLE = 0x0100, + HID_USAGE_CONSUMER_FAN_SPEED = 0x0101, + HID_USAGE_CONSUMER_LIGHT_ENABLE = 0x0102, + HID_USAGE_CONSUMER_LIGHT_ILLUMINATION_LEVEL = 0x0103, + HID_USAGE_CONSUMER_CLIMATE_CONTROL_ENABLE = 0x0104, + HID_USAGE_CONSUMER_ROOM_TEMPERATURE = 0x0105, + HID_USAGE_CONSUMER_SECURITY_ENABLE = 0x0106, + HID_USAGE_CONSUMER_FIRE_ALARM = 0x0107, + HID_USAGE_CONSUMER_POLICE_ALARM = 0x0108, + HID_USAGE_CONSUMER_PROXIMITY = 0x0109, + HID_USAGE_CONSUMER_MOTION = 0x010A, + HID_USAGE_CONSUMER_DURESS_ALARM = 0x010B, + HID_USAGE_CONSUMER_HOLDUP_ALARM = 0x010C, + HID_USAGE_CONSUMER_MEDICAL_ALARM = 0x010D, + // 10E-14F Reserved + HID_USAGE_CONSUMER_BALANCE_RIGHT = 0x0150, + HID_USAGE_CONSUMER_BALANCE_LEFT = 0x0151, HID_USAGE_CONSUMER_BASS_INCREMENT = 0x0152, HID_USAGE_CONSUMER_BASS_DECREMENT = 0x0153, HID_USAGE_CONSUMER_TREBLE_INCREMENT = 0x0154, HID_USAGE_CONSUMER_TREBLE_DECREMENT = 0x0155, - // Application Launcher + // 156-15F Reserved + HID_USAGE_CONSUMER_SPEAKER_SYSTEM = 0x0160, + HID_USAGE_CONSUMER_CHANNEL_LEFT = 0x0161, + HID_USAGE_CONSUMER_CHANNEL_RIGHT = 0x0162, + HID_USAGE_CONSUMER_CHANNEL_CENTER = 0x0163, + HID_USAGE_CONSUMER_CHANNEL_FRONT = 0x0164, + HID_USAGE_CONSUMER_CHANNEL_CENTER_FRONT = 0x0165, + HID_USAGE_CONSUMER_CHANNEL_SIDE = 0x0166, + HID_USAGE_CONSUMER_CHANNEL_SURROUND = 0x0167, + HID_USAGE_CONSUMER_CHANNEL_LOW_FREQUENCY = 0x0168, + // Enhancement + // CL 15.12.1 + HID_USAGE_CONSUMER_CHANNEL_TOP = 0x0169, + HID_USAGE_CONSUMER_CHANNEL_UNKNOWN = 0x016A, + // 16B-16F Reserved + HID_USAGE_CONSUMER_SUB_CHANNEL = 0x0170, + HID_USAGE_CONSUMER_SUB_CHANNEL_INCREMENT = 0x0171, + HID_USAGE_CONSUMER_SUB_CHANNEL_DECREMENT = 0x0172, + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_INCREMENT = 0x0173, + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_DECREMENT = 0x0174, + // 175-17F Reserved + HID_USAGE_CONSUMER_APPLICATION_LAUNCH_BUTTONS = 0x0180, + HID_USAGE_CONSUMER_AL_LAUNCH_BUTTON_CONFIGURATION = 0x0181, + // Tool + // Sel 15.15 + HID_USAGE_CONSUMER_AL_PROGRAMMABLE_BUTTON = 0x0182, + // Configuration + // Sel 15.15 HID_USAGE_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, + // Configuration + // Sel 15.15 + HID_USAGE_CONSUMER_AL_WORD_PROCESSOR = 0x0184, + HID_USAGE_CONSUMER_AL_TEXT_EDITOR = 0x0185, + HID_USAGE_CONSUMER_AL_SPREADSHEET = 0x0186, + HID_USAGE_CONSUMER_AL_GRAPHICS_EDITOR = 0x0187, + HID_USAGE_CONSUMER_AL_PRESENTATION_APP = 0x0188, + HID_USAGE_CONSUMER_AL_DATABASE_APP = 0x0189, HID_USAGE_CONSUMER_AL_EMAIL_READER = 0x018A, + HID_USAGE_CONSUMER_AL_NEWSREADER = 0x018B, + HID_USAGE_CONSUMER_AL_VOICEMAIL = 0x018C, + HID_USAGE_CONSUMER_AL_CONTACTS_ADDRESS_BOOK = 0x018D, + HID_USAGE_CONSUMER_AL_CALENDAR_SCHEDULE = 0x018E, + HID_USAGE_CONSUMER_AL_TASK_PROJECT_MANAGER = 0x018F, + HID_USAGE_CONSUMER_AL_LOG_JOURNAL_TIMECARD = 0x0190, + HID_USAGE_CONSUMER_AL_CHECKBOOK_FINANCE = 0x0191, HID_USAGE_CONSUMER_AL_CALCULATOR = 0x0192, - HID_USAGE_CONSUMER_AL_LOCAL_BROWSER = 0x0194, - + HID_USAGE_CONSUMER_AL_A_V_CAPTURE_PLAYBACK = 0x0193, + HID_USAGE_CONSUMER_AL_LOCAL_MACHINE_BROWSER = 0x0194, + HID_USAGE_CONSUMER_AL_LAN_WAN_BROWSER = 0x0195, + HID_USAGE_CONSUMER_AL_INTERNET_BROWSER = 0x0196, + HID_USAGE_CONSUMER_AL_REMOTE_NETWORKING_ISP = 0x0197, + // Connect + // Sel 15.15 + HID_USAGE_CONSUMER_AL_NETWORK_CONFERENCE = 0x0198, + HID_USAGE_CONSUMER_AL_NETWORK_CHAT = 0x0199, + HID_USAGE_CONSUMER_AL_TELEPHONY_DIALER = 0x019A, + HID_USAGE_CONSUMER_AL_LOGON = 0x019B, + HID_USAGE_CONSUMER_AL_LOGOFF = 0x019C, + HID_USAGE_CONSUMER_AL_LOGON_LOGOFF = 0x019D, + HID_USAGE_CONSUMER_AL_TERMINAL_LOCK_SCREENSAVER = 0x019E, + HID_USAGE_CONSUMER_AL_CONTROL_PANEL = 0x019F, + HID_USAGE_CONSUMER_AL_COMMAND_LINE_PROCESSOR_RUN = 0x01A0, + HID_USAGE_CONSUMER_AL_PROCESS_TASK_MANAGER = 0x01A1, + HID_USAGE_CONSUMER_AL_SELECT_TASK_APPLICATION = 0x01A2, + HID_USAGE_CONSUMER_AL_NEXT_TASK_APPLICATION = 0x01A3, + HID_USAGE_CONSUMER_AL_PREVIOUS_TASK_APPLICATION = 0x01A4, + HID_USAGE_CONSUMER_AL_PREEMPTIVE_HALT = 0x01A5, + // Task_Application + // Sel 15.15 + HID_USAGE_CONSUMER_AL_INTEGRATED_HELP_CENTER = 0x01A6, + HID_USAGE_CONSUMER_AL_DOCUMENTS = 0x01A7, + HID_USAGE_CONSUMER_AL_THESAURUS = 0x01A8, + HID_USAGE_CONSUMER_AL_DICTIONARY = 0x01A9, + HID_USAGE_CONSUMER_AL_DESKTOP = 0x01AA, + HID_USAGE_CONSUMER_AL_SPELL_CHECK = 0x01AB, + HID_USAGE_CONSUMER_AL_GRAMMAR_CHECK = 0x01AC, + HID_USAGE_CONSUMER_AL_WIRELESS_STATUS = 0x01AD, + HID_USAGE_CONSUMER_AL_KEYBOARD_LAYOUT = 0x01AE, + HID_USAGE_CONSUMER_AL_VIRUS_PROTECTION = 0x01AF, + HID_USAGE_CONSUMER_AL_ENCRYPTION = 0x01B0, + HID_USAGE_CONSUMER_AL_SCREEN_SAVER = 0x01B1, + HID_USAGE_CONSUMER_AL_ALARMS = 0x01B2, + HID_USAGE_CONSUMER_AL_CLOCK = 0x01B3, + HID_USAGE_CONSUMER_AL_FILE_BROWSER = 0x01B4, + HID_USAGE_CONSUMER_AL_POWER_STATUS = 0x01B5, + HID_USAGE_CONSUMER_AL_IMAGE_BROWSER = 0x01B6, + HID_USAGE_CONSUMER_AL_AUDIO_BROWSER = 0x01B7, + HID_USAGE_CONSUMER_AL_MOVIE_BROWSER = 0x01B8, + HID_USAGE_CONSUMER_AL_DIGITAL_RIGHTS_MANAGER = 0x01B9, + HID_USAGE_CONSUMER_AL_DIGITAL_WALLET = 0x01BA, + // 1BB Reserved + HID_USAGE_CONSUMER_AL_INSTANT_MESSAGING = 0x01BC, + HID_USAGE_CONSUMER_AL_OEM_FEATURES_TIPS_TUTORIAL = 0x01BD, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_OEM_HELP = 0x01BE, + HID_USAGE_CONSUMER_AL_ONLINE_COMMUNITY = 0x01BF, + HID_USAGE_CONSUMER_AL_ENTERTAINMENT_CONTENT = 0x01C0, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_ONLINE_SHOPPING_BROWSER = 0x01C1, + HID_USAGE_CONSUMER_AL_SMARTCARD_INFORMATION_HELP = 0x01C2, + HID_USAGE_CONSUMER_AL_MARKET_MONITOR_FINANCE = 0x01C3, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_CUSTOMIZED_CORPORATE_NEWS = 0x01C4, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_ONLINE_ACTIVITY_BROWSER = 0x01C5, + HID_USAGE_CONSUMER_AL_RESEARCH_SEARCH_BROWSER = 0x01C6, + HID_USAGE_CONSUMER_AL_AUDIO_PLAYER = 0x01C7, + // 1C8-1FF Reserved + HID_USAGE_CONSUMER_GENERIC_GUI_APPLICATION = 0x0200, + // ' Controls + // ' + HID_USAGE_CONSUMER_AC_NEW = 0x0201, + HID_USAGE_CONSUMER_AC_OPEN = 0x0202, + HID_USAGE_CONSUMER_AC_CLOSE = 0x0203, + HID_USAGE_CONSUMER_AC_EXIT = 0x0204, + HID_USAGE_CONSUMER_AC_MAXIMIZE = 0x0205, + HID_USAGE_CONSUMER_AC_MINIMIZE = 0x0206, + HID_USAGE_CONSUMER_AC_SAVE = 0x0207, + HID_USAGE_CONSUMER_AC_PRINT = 0x0208, + HID_USAGE_CONSUMER_AC_PROPERTIES = 0x0209, + HID_USAGE_CONSUMER_AC_UNDO = 0x021A, + HID_USAGE_CONSUMER_AC_COPY = 0x021B, + HID_USAGE_CONSUMER_AC_CUT = 0x021C, + HID_USAGE_CONSUMER_AC_PASTE = 0x021D, + HID_USAGE_CONSUMER_AC_SELECT_ALL = 0x021E, + HID_USAGE_CONSUMER_AC_FIND = 0x021F, + HID_USAGE_CONSUMER_AC_FIND_AND_REPLACE = 0x0220, // Browser/Explorer Specific HID_USAGE_CONSUMER_AC_SEARCH = 0x0221, + HID_USAGE_CONSUMER_AC_GO_TO = 0x0222, HID_USAGE_CONSUMER_AC_HOME = 0x0223, HID_USAGE_CONSUMER_AC_BACK = 0x0224, HID_USAGE_CONSUMER_AC_FORWARD = 0x0225, HID_USAGE_CONSUMER_AC_STOP = 0x0226, HID_USAGE_CONSUMER_AC_REFRESH = 0x0227, + HID_USAGE_CONSUMER_AC_PREVIOUS_LINK = 0x0228, + HID_USAGE_CONSUMER_AC_NEXT_LINK = 0x0229, HID_USAGE_CONSUMER_AC_BOOKMARKS = 0x022A, - + HID_USAGE_CONSUMER_AC_HISTORY = 0x022B, + HID_USAGE_CONSUMER_AC_SUBSCRIPTIONS = 0x022C, + HID_USAGE_CONSUMER_AC_ZOOM_IN = 0x022D, + HID_USAGE_CONSUMER_AC_ZOOM_OUT = 0x022E, + HID_USAGE_CONSUMER_AC_ZOOM = 0x022F, + HID_USAGE_CONSUMER_AC_FULL_SCREEN_VIEW = 0x0230, + HID_USAGE_CONSUMER_AC_NORMAL_VIEW = 0x0231, + HID_USAGE_CONSUMER_AC_VIEW_TOGGLE = 0x0232, + HID_USAGE_CONSUMER_AC_SCROLL_UP = 0x0233, + HID_USAGE_CONSUMER_AC_SCROLL_DOWN = 0x0234, + HID_USAGE_CONSUMER_AC_SCROLL = 0x0235, + HID_USAGE_CONSUMER_AC_PAN_LEFT = 0x0236, + HID_USAGE_CONSUMER_AC_PAN_RIGHT = 0x0237, // Mouse Horizontal scroll HID_USAGE_CONSUMER_AC_PAN = 0x0238, + HID_USAGE_CONSUMER_AC_NEW_WINDOW = 0x0239, + HID_USAGE_CONSUMER_AC_TILE_HORIZONTALLY = 0x023A, + HID_USAGE_CONSUMER_AC_TILE_VERTICALLY = 0x023B, + HID_USAGE_CONSUMER_AC_FORMAT = 0x023C, + HID_USAGE_CONSUMER_AC_EDIT = 0x023D, + HID_USAGE_CONSUMER_AC_BOLD = 0x023E, + HID_USAGE_CONSUMER_AC_ITALICS = 0x023F, + HID_USAGE_CONSUMER_AC_UNDERLINE = 0x0240, + HID_USAGE_CONSUMER_AC_STRIKETHROUGH = 0x0241, + HID_USAGE_CONSUMER_AC_SUBSCRIPT = 0x0242, + HID_USAGE_CONSUMER_AC_SUPERSCRIPT = 0x0243, + HID_USAGE_CONSUMER_AC_ALL_CAPS = 0x0244, + HID_USAGE_CONSUMER_AC_ROTATE = 0x0245, + HID_USAGE_CONSUMER_AC_RESIZE = 0x0246, + HID_USAGE_CONSUMER_AC_FLIP_HORIZONTAL = 0x0247, + HID_USAGE_CONSUMER_AC_FLIP_VERTICAL = 0x0248, + HID_USAGE_CONSUMER_AC_MIRROR_HORIZONTAL = 0x0249, + HID_USAGE_CONSUMER_AC_MIRROR_VERTICAL = 0x024A, + HID_USAGE_CONSUMER_AC_FONT_SELECT = 0x024B, + HID_USAGE_CONSUMER_AC_FONT_COLOR = 0x024C, + HID_USAGE_CONSUMER_AC_FONT_SIZE = 0x024D, + HID_USAGE_CONSUMER_AC_JUSTIFY_LEFT = 0x024E, + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_H = 0x024F, + HID_USAGE_CONSUMER_AC_JUSTIFY_RIGHT = 0x0250, + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_H = 0x0251, + HID_USAGE_CONSUMER_AC_JUSTIFY_TOP = 0x0252, + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_V = 0x0253, + HID_USAGE_CONSUMER_AC_JUSTIFY_BOTTOM = 0x0254, + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_V = 0x0255, + HID_USAGE_CONSUMER_AC_INDENT_DECREASE = 0x0256, + HID_USAGE_CONSUMER_AC_INDENT_INCREASE = 0x0257, + HID_USAGE_CONSUMER_AC_NUMBERED_LIST = 0x0258, + HID_USAGE_CONSUMER_AC_RESTART_NUMBERING = 0x0259, + HID_USAGE_CONSUMER_AC_BULLETED_LIST = 0x025A, + HID_USAGE_CONSUMER_AC_PROMOTE = 0x025B, + HID_USAGE_CONSUMER_AC_DEMOTE = 0x025C, + HID_USAGE_CONSUMER_AC_YES = 0x025D, + HID_USAGE_CONSUMER_AC_NO = 0x025E, + HID_USAGE_CONSUMER_AC_CANCEL = 0x025F, + HID_USAGE_CONSUMER_AC_CATALOG = 0x0260, + HID_USAGE_CONSUMER_AC_BUY_CHECKOUT = 0x0261, + HID_USAGE_CONSUMER_AC_ADD_TO_CART = 0x0262, + HID_USAGE_CONSUMER_AC_EXPAND = 0x0263, + HID_USAGE_CONSUMER_AC_EXPAND_ALL = 0x0264, + HID_USAGE_CONSUMER_AC_COLLAPSE = 0x0265, + HID_USAGE_CONSUMER_AC_COLLAPSE_ALL = 0x0266, + HID_USAGE_CONSUMER_AC_PRINT_PREVIEW = 0x0267, + HID_USAGE_CONSUMER_AC_PASTE_SPECIAL = 0x0268, + HID_USAGE_CONSUMER_AC_INSERT_MODE = 0x0269, + HID_USAGE_CONSUMER_AC_DELETE = 0x026A, + HID_USAGE_CONSUMER_AC_LOCK = 0x026B, + HID_USAGE_CONSUMER_AC_UNLOCK = 0x026C, + HID_USAGE_CONSUMER_AC_PROTECT = 0x026D, + HID_USAGE_CONSUMER_AC_UNPROTECT = 0x026E, + HID_USAGE_CONSUMER_AC_ATTACH_COMMENT = 0x026F, + HID_USAGE_CONSUMER_AC_DELETE_COMMENT = 0x0270, + HID_USAGE_CONSUMER_AC_VIEW_COMMENT = 0x0271, + HID_USAGE_CONSUMER_AC_SELECT_WORD = 0x0272, + HID_USAGE_CONSUMER_AC_SELECT_SENTENCE = 0x0273, + HID_USAGE_CONSUMER_AC_SELECT_PARAGRAPH = 0x0274, + HID_USAGE_CONSUMER_AC_SELECT_COLUMN = 0x0275, + HID_USAGE_CONSUMER_AC_SELECT_ROW = 0x0276, + HID_USAGE_CONSUMER_AC_SELECT_TABLE = 0x0277, + HID_USAGE_CONSUMER_AC_SELECT_OBJECT = 0x0278, + HID_USAGE_CONSUMER_AC_REDO_REPEAT = 0x0279, + HID_USAGE_CONSUMER_AC_SORT = 0x027A, + HID_USAGE_CONSUMER_AC_SORT_ASCENDING = 0x027B, + HID_USAGE_CONSUMER_AC_SORT_DESCENDING = 0x027C, + HID_USAGE_CONSUMER_AC_FILTER = 0x027D, + HID_USAGE_CONSUMER_AC_SET_CLOCK = 0x027E, + HID_USAGE_CONSUMER_AC_VIEW_CLOCK = 0x027F, + HID_USAGE_CONSUMER_AC_SELECT_TIME_ZONE = 0x0280, + HID_USAGE_CONSUMER_AC_EDIT_TIME_ZONES = 0x0281, + HID_USAGE_CONSUMER_AC_SET_ALARM = 0x0282, + HID_USAGE_CONSUMER_AC_CLEAR_ALARM = 0x0283, + HID_USAGE_CONSUMER_AC_SNOOZE_ALARM = 0x0284, + HID_USAGE_CONSUMER_AC_RESET_ALARM = 0x0285, + HID_USAGE_CONSUMER_AC_SYNCHRONIZE = 0x0286, + HID_USAGE_CONSUMER_AC_SEND_RECEIVE = 0x0287, + HID_USAGE_CONSUMER_AC_SEND_TO = 0x0288, + HID_USAGE_CONSUMER_AC_REPLY = 0x0289, + HID_USAGE_CONSUMER_AC_REPLY_ALL = 0x028A, + HID_USAGE_CONSUMER_AC_FORWARD_MSG = 0x028B, + HID_USAGE_CONSUMER_AC_SEND = 0x028C, + HID_USAGE_CONSUMER_AC_ATTACH_FILE = 0x028D, + HID_USAGE_CONSUMER_AC_UPLOAD = 0x028E, + HID_USAGE_CONSUMER_AC_DOWNLOAD_SAVE_TARGET_AS = 0x028F, + HID_USAGE_CONSUMER_AC_SET_BORDERS = 0x0290, + HID_USAGE_CONSUMER_AC_INSERT_ROW = 0x0291, + HID_USAGE_CONSUMER_AC_INSERT_COLUMN = 0x0292, + HID_USAGE_CONSUMER_AC_INSERT_FILE = 0x0293, + HID_USAGE_CONSUMER_AC_INSERT_PICTURE = 0x0294, + HID_USAGE_CONSUMER_AC_INSERT_OBJECT = 0x0295, + HID_USAGE_CONSUMER_AC_INSERT_SYMBOL = 0x0296, + HID_USAGE_CONSUMER_AC_SAVE_AND_CLOSE = 0x0297, + HID_USAGE_CONSUMER_AC_RENAME = 0x0298, + HID_USAGE_CONSUMER_AC_MERGE = 0x0299, + HID_USAGE_CONSUMER_AC_SPLIT = 0x029A, + HID_USAGE_CONSUMER_AC_DISRIBUTE_HORIZONTALLY = 0x029B, + HID_USAGE_CONSUMER_AC_DISTRIBUTE_VERTICALLY = 0x029C, + // 29D-FFFF Reserved + }; /// HID Usage Table: Digitizer Page (0x0D) diff --git a/src/class/hid/hid_host.c b/src/class/hid/hid_host.c index a3cc7d6d7..a44c83433 100644 --- a/src/class/hid/hid_host.c +++ b/src/class/hid/hid_host.c @@ -444,7 +444,7 @@ bool hidh_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t hidh_epbuf_t* epbuf = get_hid_epbuf(idx); if (dir == TUSB_DIR_IN) { - TU_LOG_DRV(" Get Report callback (%u, %u)\r\n", daddr, idx); + TU_LOG_DRV(" [idx=%u] Get Report callback\r\n", idx); TU_LOG3_MEM(epbuf->epin, xferred_bytes, 2); tuh_hid_report_received_cb(daddr, idx, epbuf->epin, (uint16_t) xferred_bytes); } else { @@ -461,7 +461,9 @@ void hidh_close(uint8_t daddr) { hidh_interface_t* p_hid = &_hidh_itf[i]; if (p_hid->daddr == daddr) { TU_LOG_DRV(" HIDh close addr = %u index = %u\r\n", daddr, i); - if (tuh_hid_umount_cb) tuh_hid_umount_cb(daddr, i); + if (tuh_hid_umount_cb) { + tuh_hid_umount_cb(daddr, i); + } tu_memclr(p_hid, sizeof(hidh_interface_t)); } } @@ -517,7 +519,8 @@ bool hidh_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const* desc_ // Assume bNumDescriptors = 1 p_hid->report_desc_type = desc_hid->bReportType; - p_hid->report_desc_len = tu_unaligned_read16(&desc_hid->wReportLength); + // Use offsetof to avoid pointer to the odd/misaligned address + p_hid->report_desc_len = tu_unaligned_read16((uint8_t const*)desc_hid + offsetof(tusb_hid_descriptor_hid_t, wReportLength)); // Per HID Specs: default is Report protocol, though we will force Boot protocol when set_config p_hid->protocol_mode = _hidh_default_protocol; @@ -660,9 +663,12 @@ uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, uint8_t const tag = header.tag; uint8_t const type = header.type; - uint8_t const size = header.size; + uint8_t size = header.size; + if (size == 3) { + size = 4; // HID 1.11 6.2.2.2 3 is 4 bytes + } - uint8_t const data8 = desc_report[0]; + uint8_t const data8 = (size > 0) ? desc_report[0] : 0; TU_LOG(3, "tag = %d, type = %d, size = %d, data = ", tag, type, size); for (uint32_t i = 0; i < size; i++) { diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 03144eb45..e6ace316c 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -56,6 +56,7 @@ TU_ATTR_WEAK void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) i typedef struct { uint8_t daddr; uint8_t bInterfaceNumber; // interface number of MIDI streaming + uint8_t iInterface; uint8_t itf_count; // number of interface including Audio Control + MIDI streaming uint8_t ep_in; // IN endpoint address @@ -153,9 +154,10 @@ void midih_close(uint8_t daddr) { p_midi->tx_cable_count = 0; p_midi->daddr = 0; p_midi->mounted = false; +#if CFG_TUH_MIDI_STREAM_API tu_memclr(&p_midi->stream_read, sizeof(p_midi->stream_read)); tu_memclr(&p_midi->stream_write, sizeof(p_midi->stream_write)); - +#endif tu_edpt_stream_close(&p_midi->ep_stream.rx); tu_edpt_stream_close(&p_midi->ep_stream.tx); } @@ -209,7 +211,14 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d desc_cb.jack_num = 0; // There can be just a MIDI or an Audio + MIDI interface - // If there is Audio Control Interface + Audio Header descriptor, skip it + // - If there is Audio Control Interface + Audio Header descriptor, then skip it. + // - If there is an Audio Control Interface + Audio Streaming Interface, then ignore the Audio Streaming Interface. + // Future: + // Note that if this driver is used with an USB Audio Streaming host driver, + // then call that driver first. If the MIDI interface comes before the + // audio streaming interface, then the audio driver will have to call this + // driver after parsing the audio control interface and then resume parsing + // the streaming audio interface. if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) { TU_VERIFY(max_len > 2*sizeof(tusb_desc_interface_t) + sizeof(audio_desc_cs_ac_interface_t)); @@ -220,20 +229,31 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d p_desc = tu_desc_next(p_desc); desc_itf = (const tusb_desc_interface_t *)p_desc; - TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); p_midi->itf_count = 1; + // skip non-interface and non-midi streaming descriptors + while (tu_desc_in_bounds(p_desc, p_end) && + (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE || (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO && desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) { + if (desc_itf->bDescriptorType == TUSB_DESC_INTERFACE && desc_itf->bAlternateSetting == 0) { + p_midi->itf_count++; + } + p_desc = tu_desc_next(p_desc); + desc_itf = (tusb_desc_interface_t const *)p_desc; + } + TU_VERIFY(p_desc < p_end); // TODO: If MIDI interface comes after Audio Streaming, then max_len did not include the MIDI interface descriptor + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); } TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass); TU_LOG_DRV("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr); p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber; + p_midi->iInterface = desc_itf->iInterface; p_midi->itf_count++; desc_cb.desc_midi = desc_itf; p_desc = tu_desc_next(p_desc); // next to CS Header bool found_new_interface = false; - while ((p_desc < p_end) && (tu_desc_next(p_desc) <= p_end) && !found_new_interface) { + while (tu_desc_in_bounds(p_desc, p_end) && !found_new_interface) { switch (tu_desc_type(p_desc)) { case TUSB_DESC_INTERFACE: found_new_interface = true; @@ -251,7 +271,9 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d TU_LOG_DRV(" Jack %s %s descriptor \r\n", tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_IN_JACK ? "IN" : "OUT", p_desc[3] == MIDI_JACK_EXTERNAL ? "External" : "Embedded"); - desc_cb.desc_jack[desc_cb.jack_num++] = p_desc; + if (desc_cb.jack_num < TU_ARRAY_SIZE(desc_cb.desc_jack)) { + desc_cb.desc_jack[desc_cb.jack_num++] = p_desc; + } break; } @@ -345,6 +367,28 @@ uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num) { return TUSB_INDEX_INVALID_8; } +bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t* info) { + midih_interface_t* p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi && info); + + info->daddr = p_midi->daddr; + + // re-construct descriptor + tusb_desc_interface_t* desc = &info->desc; + desc->bLength = sizeof(tusb_desc_interface_t); + desc->bDescriptorType = TUSB_DESC_INTERFACE; + + desc->bInterfaceNumber = p_midi->bInterfaceNumber; + desc->bAlternateSetting = 0; + desc->bNumEndpoints = (uint8_t)((p_midi->ep_in != 0 ? 1:0) + (p_midi->ep_out != 0 ? 1:0)); + desc->bInterfaceClass = TUSB_CLASS_AUDIO; + desc->bInterfaceSubClass = AUDIO_SUBCLASS_MIDI_STREAMING; + desc->bInterfaceProtocol = 0; + desc->iInterface = p_midi->iInterface; + + return true; +} + uint8_t tuh_midi_get_tx_cable_count (uint8_t idx) { TU_VERIFY(idx < CFG_TUH_MIDI); midih_interface_t *p_midi = &_midi_host[idx]; @@ -531,6 +575,11 @@ uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buff } } } + else { + // bad packet discard + nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + continue; + } } else if (status < MIDI_STATUS_SYSEX_START) { // then it is a channel message either three bytes or two uint8_t fake_cin = (status & 0xf0) >> 4; @@ -565,14 +614,19 @@ uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buff break; default: break; - cable_sysex_in_progress &= (uint16_t) ~cable_mask; } + cable_sysex_in_progress &= (uint16_t) ~cable_mask; } else { // Real-time message: can be inserted into a sysex message, // so do don't clear cable_sysex_in_progress bit bytes_to_add_to_stream = 1; } } + else { + // bad packet discard + nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + continue; + } for (uint8_t i = 1; i <= bytes_to_add_to_stream; i++) { *p_buffer++ = p_midi->stream_read.buffer[i]; diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h index b06bb43d7..06554a03d 100644 --- a/src/class/midi/midi_host.h +++ b/src/class/midi/midi_host.h @@ -69,7 +69,7 @@ typedef struct { const tusb_desc_endpoint_t* desc_epout; // endpoint OUT descriptor, CS_ENDPOINT is right after uint8_t jack_num; - const uint8_t* desc_jack[16]; // list of jack descriptors (embedded + external) + const uint8_t* desc_jack[32]; // list of jack descriptors (embedded + external) } tuh_midi_descriptor_cb_t; typedef struct { @@ -90,6 +90,10 @@ bool tuh_midi_mounted(uint8_t idx); // return TUSB_INDEX_INVALID_8 (0xFF) if not found uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num); +// Get Interface information +// return true if index is correct and interface is currently mounted +bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t* info); + // return the number of virtual midi cables on the device's IN endpoint uint8_t tuh_midi_get_rx_cable_count(uint8_t idx); diff --git a/src/class/msc/msc.h b/src/class/msc/msc.h index bbfd35a43..b2b44eac4 100644 --- a/src/class/msc/msc.h +++ b/src/class/msc/msc.h @@ -108,8 +108,7 @@ TU_VERIFY_STATIC(sizeof(msc_csw_t) == 13, "size is not correct"); //--------------------------------------------------------------------+ /// SCSI Command Operation Code -typedef enum -{ +typedef enum { SCSI_CMD_TEST_UNIT_READY = 0x00, ///< The SCSI Test Unit Ready command is used to determine if a device is ready to transfer data (read/write), i.e. if a disk has spun up, if a tape is loaded and ready etc. The device does not perform a self-test operation. SCSI_CMD_INQUIRY = 0x12, ///< The SCSI Inquiry command is used to obtain basic information from a target device. SCSI_CMD_MODE_SELECT_6 = 0x15, ///< provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server. Device servers that implement the MODE SELECT(6) command shall also implement the MODE SENSE(6) command. Application clients should issue MODE SENSE(6) prior to each MODE SELECT(6) to determine supported mode pages, page lengths, and other parameters. @@ -124,8 +123,7 @@ typedef enum }scsi_cmd_type_t; /// SCSI Sense Key -typedef enum -{ +typedef enum { SCSI_SENSE_NONE = 0x00, ///< no specific Sense Key. This would be the case for a successful command SCSI_SENSE_RECOVERED_ERROR = 0x01, ///< Indicates the last command completed successfully with some recovery action performed by the disc drive. SCSI_SENSE_NOT_READY = 0x02, ///< Indicates the logical unit addressed cannot be accessed. @@ -141,6 +139,27 @@ typedef enum SCSI_SENSE_MISCOMPARE = 0x0e ///< Indicates that the source data did not match the data read from the medium. }scsi_sense_key_type_t; + +typedef enum { + SCSI_PDT_DIRECT_ACCESS = 0x0, + SCSI_PDT_SEQUENTIAL_ACCESS = 0x1, + SCSI_PDT_PRINTER = 0x2, + SCSI_PDT_PROCESSOR = 0x3, + SCSI_PDT_WRITE_ONCE = 0x4, + SCSI_PDT_CD_DVD = 0x5, + SCSI_PDT_SCANNER = 0x6, + SCSI_PDT_OPTICAL_DEVICE = 0x7, + SCSI_PDT_MEDIUM_CHANGER = 0x8, + SCSI_PDT_COMMUNICATIONS = 0x9, // obsolete + SCSI_PDT_RAID = 0x0c, + SCSI_PDT_ENCLOSURE_SERVICES = 0x0d, + SCSI_PDT_SIMPLIFIED_DIRECT_ACCESS = 0x0e, + SCSI_PDT_OPTICAL_CARD_READER = 0x0f, + SCSI_PDT_BRIDGE = 0x10, ///< Bridge device, e.g. USB to SCSI bridge + SCSI_PDT_OBJECT_BASED_STORAGE = 0x11, ///< Object-based storage device + SCSI_PDT_AUTOMATION_DRIVE_INTERFACE = 0x12, ///< Automation/Drive Interface (ADI) device +} scsi_peripheral_device_type_t; + //--------------------------------------------------------------------+ // SCSI Primary Command (SPC-4) //--------------------------------------------------------------------+ diff --git a/src/class/msc/msc_device.c b/src/class/msc/msc_device.c index 6670045aa..a32014c2d 100644 --- a/src/class/msc/msc_device.c +++ b/src/class/msc/msc_device.c @@ -42,6 +42,17 @@ #define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MSC_LOG_LEVEL, __VA_ARGS__) //--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + (void) lun; (void) vendor_id; (void) product_id; (void) product_rev; +} +TU_ATTR_WEAK uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize) { + (void) lun; (void) inquiry_resp; (void) bufsize; + return 0; +} + +//--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ enum { @@ -53,23 +64,26 @@ enum { }; typedef struct { - TU_ATTR_ALIGNED(4) msc_cbw_t cbw; - TU_ATTR_ALIGNED(4) msc_csw_t csw; + TU_ATTR_ALIGNED(4) msc_cbw_t cbw; // 31 bytes + uint8_t rhport; + TU_ATTR_ALIGNED(4) msc_csw_t csw; // 13 bytes uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; - // Bulk Only Transfer (BOT) Protocol - uint8_t stage; - uint32_t total_len; // byte to be transferred, can be smaller than total_bytes in cbw uint32_t xferred_len; // numbered of bytes transferred so far in the Data Stage - // Sense Response Data + // Bulk Only Transfer (BOT) Protocol + uint8_t stage; + + // SCSI Sense Response Data uint8_t sense_key; uint8_t add_sense_code; uint8_t add_sense_qualifier; + + uint8_t pending_io; // pending async IO }mscd_interface_t; static mscd_interface_t _mscd_itf; @@ -82,31 +96,36 @@ CFG_TUD_MEM_SECTION static struct { // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize); -static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc); - -static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc); -static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes); +static void proc_read10_cmd(mscd_interface_t* p_msc); +static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes); +static void proc_write10_cmd(mscd_interface_t* p_msc); +static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes); +static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes); +static bool proc_stage_status(mscd_interface_t* p_msc); TU_ATTR_ALWAYS_INLINE static inline bool is_data_in(uint8_t dir) { return tu_bit_test(dir, 7); } -static inline bool send_csw(uint8_t rhport, mscd_interface_t* p_msc) { +static inline bool send_csw(mscd_interface_t* p_msc) { // Data residue is always = host expect - actual transferred + uint8_t rhport = p_msc->rhport; p_msc->csw.data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; p_msc->stage = MSC_STAGE_STATUS_SENT; memcpy(_mscd_epbuf.buf, &p_msc->csw, sizeof(msc_csw_t)); return usbd_edpt_xfer(rhport, p_msc->ep_in , _mscd_epbuf.buf, sizeof(msc_csw_t)); } -static inline bool prepare_cbw(uint8_t rhport, mscd_interface_t* p_msc) { +static inline bool prepare_cbw(mscd_interface_t* p_msc) { + uint8_t rhport = p_msc->rhport; p_msc->stage = MSC_STAGE_CMD; return usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, sizeof(msc_cbw_t)); } -static void fail_scsi_op(uint8_t rhport, mscd_interface_t* p_msc, uint8_t status) { +static void fail_scsi_op(mscd_interface_t* p_msc, uint8_t status) { msc_cbw_t const * p_cbw = &p_msc->cbw; msc_csw_t * p_csw = &p_msc->csw; + uint8_t rhport = p_msc->rhport; p_csw->status = status; p_csw->data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; @@ -177,6 +196,33 @@ static uint8_t rdwr10_validate_cmd(msc_cbw_t const* cbw) { return status; } +static bool proc_stage_status(mscd_interface_t *p_msc) { + uint8_t rhport = p_msc->rhport; + msc_cbw_t const *p_cbw = &p_msc->cbw; + + // skip status if epin is currently stalled, will do it when received Clear Stall request + if (!usbd_edpt_stalled(rhport, p_msc->ep_in)) { + if ((p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) { + // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status + // TU_LOG_DRV(" SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len); + usbd_edpt_stall(rhport, p_msc->ep_in); + } else { + TU_ASSERT(send_csw(p_msc)); + } + } + + #if TU_CHECK_MCU(OPT_MCU_CXD56) + // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD. + // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and + // hope everything will work + if (usbd_edpt_stalled(rhport, p_msc->ep_in)) { + usbd_edpt_clear_stall(rhport, p_msc->ep_in); + send_csw(p_msc); + } + #endif + return true; +} + //--------------------------------------------------------------------+ // Debug //--------------------------------------------------------------------+ @@ -214,15 +260,51 @@ bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, u return true; } -static inline void set_sense_medium_not_present(uint8_t lun) { +TU_ATTR_ALWAYS_INLINE static inline void set_sense_medium_not_present(uint8_t lun) { // default sense is NOT READY, MEDIUM NOT PRESENT tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00); } +static void proc_async_io_done(void *bytes_io) { + mscd_interface_t *p_msc = &_mscd_itf; + TU_VERIFY(p_msc->pending_io, ); + const int32_t nbytes = (int32_t) (intptr_t) bytes_io; + const uint8_t cmd = p_msc->cbw.command[0]; + + p_msc->pending_io = 0; + switch (cmd) { + case SCSI_CMD_READ_10: + proc_read_io_data(p_msc, nbytes); + break; + + case SCSI_CMD_WRITE_10: + proc_write_io_data(p_msc, (uint32_t) nbytes, nbytes); + break; + + default: break; + } + + // send status if stage is transitioned to STATUS + if (p_msc->stage == MSC_STAGE_STATUS) { + proc_stage_status(p_msc); + } +} + +bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr) { + // Precheck to avoid queueing multiple RW done callback + TU_VERIFY(_mscd_itf.pending_io); + if (bytes_io == 0) { + bytes_io = TUD_MSC_RET_ERROR; // 0 is treated as error, no reason to call this with BUSY here + } + usbd_defer_func(proc_async_io_done, (void *) (intptr_t) bytes_io, in_isr); + return true; +} + //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ void mscd_init(void) { + TU_LOG_INT(CFG_TUD_MSC_LOG_LEVEL, sizeof(mscd_interface_t)); tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); } @@ -245,12 +327,13 @@ uint16_t mscd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1 mscd_interface_t * p_msc = &_mscd_itf; p_msc->itf_num = itf_desc->bInterfaceNumber; + p_msc->rhport = rhport; // Open endpoint pair TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(itf_desc), 2, TUSB_XFER_BULK, &p_msc->ep_out, &p_msc->ep_in), 0); // Prepare for Command Block Wrapper - TU_ASSERT(prepare_cbw(rhport, p_msc), drv_len); + TU_ASSERT(prepare_cbw(p_msc), drv_len); return drv_len; } @@ -289,14 +372,14 @@ bool mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t if (ep_addr == p_msc->ep_in) { if (p_msc->stage == MSC_STAGE_STATUS) { // resume sending SCSI status if we are in this stage previously before stalled - TU_ASSERT(send_csw(rhport, p_msc)); + TU_ASSERT(send_csw(p_msc)); } } else if (ep_addr == p_msc->ep_out) { if (p_msc->stage == MSC_STAGE_CMD) { // part of reset recovery (probably due to invalid CBW) -> prepare for new command // Note: skip if already queued previously if (usbd_edpt_ready(rhport, p_msc->ep_out)) { - TU_ASSERT(prepare_cbw(rhport, p_msc)); + TU_ASSERT(prepare_cbw(p_msc)); } } } @@ -344,7 +427,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t msc_csw_t * p_csw = &p_msc->csw; switch (p_msc->stage) { - case MSC_STAGE_CMD: + case MSC_STAGE_CMD: { //------------- new CBW received -------------// // Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it if (ep_addr != p_msc->ep_out) { @@ -382,12 +465,12 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t uint8_t const status = rdwr10_validate_cmd(p_cbw); if (status != MSC_CSW_STATUS_PASSED) { - fail_scsi_op(rhport, p_msc, status); + fail_scsi_op(p_msc, status); } else if (p_cbw->total_bytes) { if (SCSI_CMD_READ_10 == p_cbw->command[0]) { - proc_read10_cmd(rhport, p_msc); + proc_read10_cmd(p_msc); } else { - proc_write10_cmd(rhport, p_msc); + proc_write10_cmd(p_msc); } } else { // no data transfer, only exist in complaint test suite @@ -400,7 +483,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if ((p_cbw->total_bytes > 0) && !is_data_in(p_cbw->dir)) { if (p_cbw->total_bytes > CFG_TUD_MSC_EP_BUFSIZE) { TU_LOG_DRV(" SCSI reject non READ10/WRITE10 with large data\r\n"); - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); } else { // Didn't check for case 9 (Ho > Dn), which requires examining scsi command first // but it is OK to just receive data then responded with failed status @@ -418,12 +501,12 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if (resplen < 0) { // unsupported command TU_LOG_DRV(" SCSI unsupported or failed command\r\n"); - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); } else if (resplen == 0) { if (p_cbw->total_bytes) { // 6.7 The 13 Cases: case 4 (Hi > Dn) // TU_LOG_DRV(" SCSI case 4 (Hi > Dn): %lu\r\n", p_cbw->total_bytes); - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); } else { // case 1 Hn = Dn: all good p_msc->stage = MSC_STAGE_STATUS; @@ -432,7 +515,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if (p_cbw->total_bytes == 0) { // 6.7 The 13 Cases: case 2 (Hn < Di) // TU_LOG_DRV(" SCSI case 2 (Hn < Di): %lu\r\n", p_cbw->total_bytes); - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); } else { // cannot return more than host expect p_msc->total_len = tu_min32((uint32_t)resplen, p_cbw->total_bytes); @@ -441,7 +524,8 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } } } - break; + break; + } case MSC_STAGE_DATA: TU_LOG_DRV(" SCSI Data [Lun%u]\r\n", p_cbw->lun); @@ -455,10 +539,10 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t // Data Stage is complete p_msc->stage = MSC_STAGE_STATUS; }else { - proc_read10_cmd(rhport, p_msc); + proc_read10_cmd(p_msc); } } else if (SCSI_CMD_WRITE_10 == p_cbw->command[0]) { - proc_write10_new_data(rhport, p_msc, xferred_bytes); + proc_write10_host_data(p_msc, xferred_bytes); } else { p_msc->xferred_len += xferred_bytes; @@ -469,7 +553,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if ( cb_result < 0 ) { // unsupported command TU_LOG_DRV(" SCSI unsupported command\r\n"); - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); }else { // TODO haven't implement this scenario any further yet } @@ -490,7 +574,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t break; case MSC_STAGE_STATUS_SENT: - // Wait for the Status phase to complete + // Status phase is complete if ((ep_addr == p_msc->ep_in) && (xferred_bytes == sizeof(msc_csw_t))) { TU_LOG_DRV(" SCSI Status [Lun%u] = %u\r\n", p_cbw->lun, p_csw->status); // TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, p_csw, xferred_bytes, 2); @@ -518,9 +602,9 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t break; } - TU_ASSERT(prepare_cbw(rhport, p_msc)); + TU_ASSERT(prepare_cbw(p_msc)); } else { - // Any xfer ended here is consider unknown error, ignore it + // Any xfer ended here is considered unknown error, ignore it TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n"); } break; @@ -529,26 +613,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } if (p_msc->stage == MSC_STAGE_STATUS) { - // skip status if epin is currently stalled, will do it when received Clear Stall request - if (!usbd_edpt_stalled(rhport, p_msc->ep_in)) { - if ((p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) { - // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status - // TU_LOG_DRV(" SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len); - usbd_edpt_stall(rhport, p_msc->ep_in); - } else { - TU_ASSERT(send_csw(rhport, p_msc)); - } - } - - #if TU_CHECK_MCU(OPT_MCU_CXD56) - // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD. - // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and - // hope everything will work - if ( usbd_edpt_stalled(rhport, p_msc->ep_in) ) { - usbd_edpt_clear_stall(rhport, p_msc->ep_in); - send_csw(rhport, p_msc); - } - #endif + TU_ASSERT(proc_stage_status(p_msc)); } return true; @@ -645,8 +710,7 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_ break; case SCSI_CMD_READ_FORMAT_CAPACITY: { - scsi_read_format_capacity_data_t read_fmt_capa = - { + scsi_read_format_capacity_data_t read_fmt_capa = { .list_length = 8, .block_num = 0, .descriptor_type = 2, // formatted media @@ -678,29 +742,24 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_ break; case SCSI_CMD_INQUIRY: { - scsi_inquiry_resp_t inquiry_rsp = - { - .is_removable = 1, - .version = 2, - .response_data_format = 2, - .additional_length = sizeof(scsi_inquiry_resp_t) - 5, - }; - - // vendor_id, product_id, product_rev is space padded string - memset(inquiry_rsp.vendor_id , ' ', sizeof(inquiry_rsp.vendor_id)); - memset(inquiry_rsp.product_id , ' ', sizeof(inquiry_rsp.product_id)); - memset(inquiry_rsp.product_rev, ' ', sizeof(inquiry_rsp.product_rev)); - - tud_msc_inquiry_cb(lun, inquiry_rsp.vendor_id, inquiry_rsp.product_id, inquiry_rsp.product_rev); + scsi_inquiry_resp_t *inquiry_rsp = (scsi_inquiry_resp_t *) buffer; + tu_memclr(inquiry_rsp, sizeof(scsi_inquiry_resp_t)); + inquiry_rsp->is_removable = 1; + inquiry_rsp->version = 2; + inquiry_rsp->response_data_format = 2; + inquiry_rsp->additional_length = sizeof(scsi_inquiry_resp_t) - 5; - resplen = sizeof(inquiry_rsp); - TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &inquiry_rsp, (size_t) resplen)); + resplen = (int32_t) tud_msc_inquiry2_cb(lun, inquiry_rsp, bufsize); + if (resplen == 0) { + // stub callback with no response, use v1 callback + tud_msc_inquiry_cb(lun, inquiry_rsp->vendor_id, inquiry_rsp->product_id, inquiry_rsp->product_rev); + resplen = sizeof(scsi_inquiry_resp_t); + } } break; case SCSI_CMD_MODE_SENSE_6: { - scsi_mode_sense6_resp_t mode_resp = - { + scsi_mode_sense6_resp_t mode_resp = { .data_len = 3, .medium_type = 0, .write_protected = false, @@ -721,8 +780,7 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_ break; case SCSI_CMD_REQUEST_SENSE: { - scsi_sense_fixed_resp_t sense_rsp = - { + scsi_sense_fixed_resp_t sense_rsp = { .response_code = 0x70, // current, fixed format .valid = 1 }; @@ -752,39 +810,49 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_ return resplen; } -static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc) { +static void proc_read10_cmd(mscd_interface_t* p_msc) { msc_cbw_t const* p_cbw = &p_msc->cbw; - - // block size already verified not zero - uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); - - // Adjust lba with transferred bytes + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero + // Adjust lba & offset with transferred bytes uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + uint32_t const offset = p_msc->xferred_len % block_sz; // remaining bytes capped at class buffer int32_t nbytes = (int32_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len); - // Application can consume smaller bytes - uint32_t const offset = p_msc->xferred_len % block_sz; + p_msc->pending_io = 1; nbytes = tud_msc_read10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, (uint32_t)nbytes); + if (nbytes != TUD_MSC_RET_ASYNC) { + p_msc->pending_io = 0; + proc_read_io_data(p_msc, nbytes); + } +} - if (nbytes < 0) { - // negative means error -> endpoint is stalled & status in CSW set to failed - TU_LOG_DRV(" tud_msc_read10_cb() return -1\r\n"); +static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes) { + const uint8_t rhport = p_msc->rhport; + if (nbytes > 0) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) nbytes),); + } else { + // nbytes is status + switch (nbytes) { + case TUD_MSC_RET_ERROR: + // error -> endpoint is stalled & status in CSW set to failed + TU_LOG_DRV(" IO read() failed\r\n"); + set_sense_medium_not_present(p_msc->cbw.lun); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + break; - // set sense - set_sense_medium_not_present(p_cbw->lun); + case TUD_MSC_RET_BUSY: + // not ready yet -> fake a transfer complete so that this driver callback will fire again + dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false); + break; - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); - } else if (nbytes == 0) { - // zero means not ready -> simulate an transfer complete so that this driver callback will fired again - dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false); - } else { - TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) nbytes),); + default: break; + } } } -static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc) { +static void proc_write10_cmd(mscd_interface_t* p_msc) { msc_cbw_t const* p_cbw = &p_msc->cbw; bool writable = true; @@ -796,51 +864,56 @@ static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc) { // Not writable, complete this SCSI op with error // Sense = Write protected tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_DATA_PROTECT, 0x27, 0x00); - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); return; } // remaining bytes capped at class buffer uint16_t nbytes = (uint16_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len); - // Write10 callback will be called later when usb transfer complete - TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, nbytes),); + TU_ASSERT(usbd_edpt_xfer(p_msc->rhport, p_msc->ep_out, _mscd_epbuf.buf, nbytes),); } // process new data arrived from WRITE10 -static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes) { +static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes) { msc_cbw_t const* p_cbw = &p_msc->cbw; + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero - // block size already verified not zero - uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); - - // Adjust lba with transferred bytes + // Adjust lba & offset with transferred bytes uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); - - // Invoke callback to consume new data uint32_t const offset = p_msc->xferred_len % block_sz; - int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, xferred_bytes); - if (nbytes < 0) { - // negative means error -> failed this scsi op - TU_LOG_DRV(" tud_msc_write10_cb() return -1\r\n"); + p_msc->pending_io = 1; + int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, xferred_bytes); + if (nbytes != TUD_MSC_RET_ASYNC) { + p_msc->pending_io = 0; + proc_write_io_data(p_msc, xferred_bytes, nbytes); + } +} - // update actual byte before failed - p_msc->xferred_len += xferred_bytes; +static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes) { + if (nbytes < 0) { + // nbytes is status + switch (nbytes) { + case TUD_MSC_RET_ERROR: + // IO error -> failed this scsi op + TU_LOG_DRV(" IO write() failed\r\n"); + set_sense_medium_not_present(p_msc->cbw.lun); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + break; - set_sense_medium_not_present(p_cbw->lun); - fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + default: break; + } } else { if ((uint32_t)nbytes < xferred_bytes) { - // Application consume less than what we got (including zero) + // Application consume less than what we got including TUD_MSC_RET_BUSY (0) const uint32_t left_over = xferred_bytes - (uint32_t)nbytes; if (nbytes > 0) { - p_msc->xferred_len += (uint16_t)nbytes; memmove(_mscd_epbuf.buf, _mscd_epbuf.buf + nbytes, left_over); } - // simulate a transfer complete with adjusted parameters --> callback will be invoked with adjusted parameter - dcd_event_xfer_complete(rhport, p_msc->ep_out, left_over, XFER_RESULT_SUCCESS, false); + // fake a transfer complete with adjusted parameters --> callback will be invoked with adjusted parameters + dcd_event_xfer_complete(p_msc->rhport, p_msc->ep_out, left_over, XFER_RESULT_SUCCESS, false); } else { // Application consume all bytes in our buffer p_msc->xferred_len += xferred_bytes; @@ -850,7 +923,7 @@ static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint3 p_msc->stage = MSC_STAGE_STATUS; } else { // prepare to receive more data from host - proc_write10_cmd(rhport, p_msc); + proc_write10_cmd(p_msc); } } } diff --git a/src/class/msc/msc_device.h b/src/class/msc/msc_device.h index 29acd280a..144b74f71 100644 --- a/src/class/msc/msc_device.h +++ b/src/class/msc/msc_device.h @@ -48,6 +48,13 @@ #error CFG_TUD_MSC_EP_BUFSIZE must be defined, value of a block size should work well, the more the better #endif +// Return value of callback functions +enum { + TUD_MSC_RET_BUSY = 0, // Busy, e.g disk I/O is not ready + TUD_MSC_RET_ERROR = -1, + TUD_MSC_RET_ASYNC = -2, // Asynchronous IO +}; + TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct"); //--------------------------------------------------------------------+ @@ -57,44 +64,41 @@ TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct"); // Set SCSI sense response bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier); +// Called by Application once asynchronous I/O operation is done +// bytes_io is number of bytes in I/O op, typically the bufsize in read/write_cb() or +// TUD_MSC_RET_ERROR (-1) for error. Note TUD_MSC_RET_BUSY (0) will be treated as error as well. +bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr); + //--------------------------------------------------------------------+ // Application Callbacks (WEAK is optional) //--------------------------------------------------------------------+ -// Invoked when received SCSI READ10 command -// - Address = lba * BLOCK_SIZE + offset -// - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE. -// -// - Application fill the buffer (up to bufsize) with address contents and return number of read byte. If -// - read < bufsize : These bytes are transferred first and callback invoked again for remaining data. -// -// - read == 0 : Indicate application is not ready yet e.g disk I/O busy. -// Callback invoked again with the same parameters later on. -// -// - read < 0 : Indicate application error e.g invalid address. This request will be STALLed -// and return failed status in command status wrapper phase. +/* + Invoked when received SCSI READ10/WRITE10 command + - Address = lba * BLOCK_SIZE + offset + - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE. + - Application fill the buffer (up to bufsize) with address contents and return number of bytes read or status. + - 0 < ret < bufsize: These bytes are transferred first and callback will be invoked again for remaining data. + - TUD_MSC_RET_BUSY + Application is buys e.g disk I/O not ready. Callback will be invoked again with the same parameters later on. + - TUD_MSC_RET_ERROR + error such as invalid address. This request will be STALLed and scsi command will be failed + - TUD_MSC_RET_ASYNC + Data I/O will be done asynchronously in a background task. Application should return immediately. + tud_msc_async_io_done() must be called once IO/ is done to signal completion. +*/ int32_t tud_msc_read10_cb (uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); - -// Invoked when received SCSI WRITE10 command -// - Address = lba * BLOCK_SIZE + offset -// - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE. -// -// - Application write data from buffer to address contents (up to bufsize) and return number of written byte. If -// - write < bufsize : callback invoked again with remaining data later on. -// -// - write == 0 : Indicate application is not ready yet e.g disk I/O busy. -// Callback invoked again with the same parameters later on. -// -// - write < 0 : Indicate application error e.g invalid address. This request will be STALLed -// and return failed status in command status wrapper phase. -// -// TODO change buffer to const uint8_t* int32_t tud_msc_write10_cb (uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize); -// Invoked when received SCSI_CMD_INQUIRY +// Invoked when received SCSI_CMD_INQUIRY, v1, application should use v2 if possible // Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]); +// Invoked when received SCSI_CMD_INQUIRY, v2 with full inquiry response +// Some inquiry_resp's fields are already filled with default values, application can update them +// Return length of inquiry response, typically sizeof(scsi_inquiry_resp_t) (36 bytes), can be longer if included vendor data. +uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize); + // Invoked when received Test Unit Ready command. // return true allowing host to read/write this LUN e.g SD card inserted bool tud_msc_test_unit_ready_cb(uint8_t lun); diff --git a/src/class/net/ecm_rndis_device.c b/src/class/net/ecm_rndis_device.c index a54e6d662..299eb97c8 100644 --- a/src/class/net/ecm_rndis_device.c +++ b/src/class/net/ecm_rndis_device.c @@ -81,6 +81,7 @@ typedef struct { static netd_interface_t _netd_itf; CFG_TUD_MEM_SECTION static netd_epbuf_t _netd_epbuf; static bool can_xmit; +static bool ecm_link_is_up = true; // Store link state for ECM mode void tud_network_recv_renew(void) { usbd_edpt_xfer(0, _netd_itf.ep_out, _netd_epbuf.rx, NETD_PACKET_SIZE); @@ -95,7 +96,11 @@ void netd_report(uint8_t *buf, uint16_t len) { const uint8_t rhport = 0; len = tu_min16(len, sizeof(ecm_notify_t)); - TU_VERIFY(usbd_edpt_claim(rhport, _netd_itf.ep_notif), ); + if (!usbd_edpt_claim(rhport, _netd_itf.ep_notif)) { + TU_LOG1("ECM: Failed to claim notification endpoint\n"); + return; + } + memcpy(_netd_epbuf.notify, buf, len); usbd_edpt_xfer(rhport, _netd_itf.ep_notif, _netd_epbuf.notify, len); } @@ -181,8 +186,6 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1 // Open endpoint pair for RNDIS TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in), 0); - tud_network_init_cb(); - // we are ready to transmit a packet can_xmit = true; @@ -196,11 +199,11 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1 } static void ecm_report(bool nc) { - const ecm_notify_t ecm_notify_nc = { + ecm_notify_t ecm_notify_nc = { .header = { .bmRequestType = 0xA1, .bRequest = 0, /* NETWORK_CONNECTION aka NetworkConnection */ - .wValue = 1, /* Connected */ + .wValue = ecm_link_is_up ? 1 : 0, /* Use current link state */ .wLength = 0, }, }; @@ -259,7 +262,6 @@ bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t // TODO should be merge with RNDIS's after endpoint opened // Also should have opposite callback for application to disable network !! - tud_network_init_cb(); can_xmit = true; // we are ready to transmit a packet tud_network_recv_renew(); // prepare for incoming packets } @@ -286,7 +288,10 @@ bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t /* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */ if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) { tud_control_xfer(rhport, request, NULL, 0); - ecm_report(true); + // Only send connection notification if link is up + if (ecm_link_is_up) { + ecm_report(true); + } } } else { if (request->bmRequestType_bit.direction == TUSB_DIR_IN) { @@ -363,9 +368,8 @@ bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_ } if (_netd_itf.ecm_mode && (ep_addr == _netd_itf.ep_notif)) { - if (sizeof(tusb_control_request_t) == xferred_bytes) { - ecm_report(false); - } + // Notification transfer complete - endpoint is now free + // Don't automatically send speed change notification after link state changes } return true; @@ -398,4 +402,31 @@ void tud_network_xmit(void *ref, uint16_t arg) { do_in_xfer(_netd_epbuf.tx, len); } +// Set the network link state (up/down) and notify the host +void tud_network_link_state(uint8_t rhport, bool is_up) { + (void)rhport; + + if (_netd_itf.ecm_mode) { + ecm_link_is_up = is_up; + + // For ECM mode, send network connection notification only + // Don't trigger speed change notification for link state changes + ecm_notify_t notify = { + .header = { + .bmRequestType = 0xA1, + .bRequest = 0, /* NETWORK_CONNECTION */ + .wValue = is_up ? 1 : 0, /* 0 = disconnected, 1 = connected */ + .wLength = 0, + }, + }; + notify.header.wIndex = _netd_itf.itf_num; + netd_report((uint8_t *)¬ify, sizeof(notify.header)); + } else { + // For RNDIS mode, we would need to implement RNDIS status indication + // This is more complex and requires RNDIS_INDICATE_STATUS_MSG + // For now, RNDIS doesn't support dynamic link state changes + (void)is_up; + } +} + #endif diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c index f9fda0698..02833c5f1 100644 --- a/src/class/net/ncm_device.c +++ b/src/class/net/ncm_device.c @@ -110,6 +110,7 @@ typedef struct { NOTIFICATION_DONE } notification_xmit_state; // state of notification transmission bool notification_xmit_is_running; // notification is currently transmitted + bool link_is_up; // current link state // misc bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations) @@ -218,7 +219,7 @@ static void notification_xmit(uint8_t rhport, bool force_next) { .direction = TUSB_DIR_IN }, .bRequest = CDC_NOTIF_NETWORK_CONNECTION, - .wValue = 1 /* Connected */, + .wValue = ncm_interface.link_is_up ? 1 : 0, /* Dynamic link state */ .wIndex = ncm_interface.itf_num, .wLength = 0, }, @@ -232,6 +233,7 @@ static void notification_xmit(uint8_t rhport, bool force_next) { ncm_interface.notification_xmit_is_running = true; } else { TU_LOG_DRV(" NOTIFICATION_FINISHED\n"); + ncm_interface.notification_xmit_is_running = false; } } // notification_xmit @@ -755,6 +757,32 @@ static void tud_network_recv_renew_r(uint8_t rhport) { tud_network_recv_renew(); } // tud_network_recv_renew +/** + * Set the link state and send notification to host + */ +void tud_network_link_state(uint8_t rhport, bool is_up) { + TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up); + + if (ncm_interface.link_is_up == is_up) { + // No change in link state + return; + } + + ncm_interface.link_is_up = is_up; + + // Only send notification if we have an active data interface + if (ncm_interface.itf_data_alt != 1) { + TU_LOG_DRV(" link state notification skipped (interface not active)\n"); + return; + } + + // Reset notification state to send link state update + ncm_interface.notification_xmit_state = NOTIFICATION_CONNECTED; + + // Trigger notification transmission + notification_xmit(rhport, false); +} + //----------------------------------------------------------------------------- // // all the netd_*() stuff (interface TinyUSB -> driver) @@ -774,6 +802,12 @@ void netd_init(void) { for (int i = 0; i < RECV_NTB_N; ++i) { ncm_interface.recv_free_ntb[i] = &ncm_epbuf.recv[i].ntb; } + // Default link state - can be configured via CFG_TUD_NCM_DEFAULT_LINK_UP + #ifdef CFG_TUD_NCM_DEFAULT_LINK_UP + ncm_interface.link_is_up = CFG_TUD_NCM_DEFAULT_LINK_UP; + #else + ncm_interface.link_is_up = true; // Default to link up if not set. + #endif } // netd_init /** diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h index 4c9a92f2d..fff2623b7 100644 --- a/src/class/net/net_device.h +++ b/src/class/net/net_device.h @@ -87,6 +87,11 @@ void tud_network_init_cb(void); // TODO removed later since it is not part of tinyusb stack extern uint8_t tud_network_mac_address[6]; +//------------- NCM -------------// + +// Set the network link state (up/down) and notify the host +void tud_network_link_state(uint8_t rhport, bool is_up); + //--------------------------------------------------------------------+ // INTERNAL USBD-CLASS DRIVER API //--------------------------------------------------------------------+ diff --git a/src/class/usbtmc/usbtmc_device.c b/src/class/usbtmc/usbtmc_device.c index abde9679f..7ec939b64 100644 --- a/src/class/usbtmc/usbtmc_device.c +++ b/src/class/usbtmc/usbtmc_device.c @@ -45,7 +45,7 @@ */ //Limitations: -// "vendor-specific" commands are not handled. +// "vendor-specific" commands are handled similar to normal messages, except that the MsgID is changed to "vendor-specific". // Dealing with "termchar" must be handled by the application layer, // though additional error checking is does in this module. // talkOnly and listenOnly are NOT supported. They're not permitted @@ -76,11 +76,6 @@ #include "usbtmc_device.h" -#ifdef xDEBUG -#include "uart_util.h" -tu_static char logMsg[150]; -#endif - // Buffer size must be an exact multiple of the max packet size for both // bulk (up to 64 bytes for FS, 512 bytes for HS). In addation, this driver // imposes a minimum buffer size of 32 bytes. @@ -88,7 +83,7 @@ tu_static char logMsg[150]; // Interrupt endpoint buffer size, default to 2 bytes as USB488 specification. #ifndef CFG_TUD_USBTMC_INT_EP_SIZE -#define CFG_TUD_USBTMC_INT_EP_SIZE 2 + #define CFG_TUD_USBTMC_INT_EP_SIZE 2 #endif /* @@ -96,27 +91,26 @@ tu_static char logMsg[150]; * consistent with USBTMC. */ -typedef enum -{ - STATE_CLOSED, // Endpoints have not yet been opened since USB reset - STATE_NAK, // Bulk-out endpoint is in NAK state. - STATE_IDLE, // Bulk-out endpoint is waiting for CMD. - STATE_RCV, // Bulk-out is receiving DEV_DEP message +typedef enum { + STATE_CLOSED,// Endpoints have not yet been opened since USB reset + STATE_NAK, // Bulk-out endpoint is in NAK state. + STATE_IDLE, // Bulk-out endpoint is waiting for CMD. + STATE_RCV, // Bulk-out is receiving DEV_DEP message STATE_TX_REQUESTED, STATE_TX_INITIATED, STATE_TX_SHORTED, STATE_CLEARING, STATE_ABORTING_BULK_IN, - STATE_ABORTING_BULK_IN_SHORTED, // aborting, and short packet has been queued for transmission - STATE_ABORTING_BULK_IN_ABORTED, // aborting, and short packet has been transmitted + STATE_ABORTING_BULK_IN_SHORTED,// aborting, and short packet has been queued for transmission + STATE_ABORTING_BULK_IN_ABORTED,// aborting, and short packet has been transmitted STATE_ABORTING_BULK_OUT, STATE_NUM_STATES } usbtmcd_state_enum; #if (CFG_TUD_USBTMC_ENABLE_488) - typedef usbtmc_response_capabilities_488_t usbtmc_capabilities_specific_t; +typedef usbtmc_response_capabilities_488_t usbtmc_capabilities_specific_t; #else - typedef usbtmc_response_capabilities_t usbtmc_capabilities_specific_t; +typedef usbtmc_response_capabilities_t usbtmc_capabilities_specific_t; #endif @@ -131,15 +125,15 @@ typedef struct uint8_t ep_int_in; uint32_t ep_bulk_in_wMaxPacketSize; uint32_t ep_bulk_out_wMaxPacketSize; - uint32_t transfer_size_remaining; // also used for requested length for bulk IN. - uint32_t transfer_size_sent; // To keep track of data bytes that have been queued in FIFO (not header bytes) + uint32_t transfer_size_remaining;// also used for requested length for bulk IN. + uint32_t transfer_size_sent; // To keep track of data bytes that have been queued in FIFO (not header bytes) - uint8_t lastBulkOutTag; // used for aborts (mostly) + uint8_t lastBulkOutTag;// used for aborts (mostly) uint8_t lastBulkInTag; // used for aborts (mostly) - uint8_t const * devInBuffer; // pointer to application-layer used for transmissions + uint8_t const *devInBuffer;// pointer to application-layer used for transmissions - usbtmc_capabilities_specific_t const * capabilities; + usbtmc_capabilities_specific_t const *capabilities; } usbtmc_interface_state_t; typedef struct { @@ -154,13 +148,13 @@ typedef struct { } usbtmc_epbuf_t; static usbtmc_interface_state_t usbtmc_state = { - .itf_id = 0xFF, + .itf_id = 0xFF, }; CFG_TUD_MEM_SECTION static usbtmc_epbuf_t usbtmc_epbuf; // We need all headers to fit in a single packet in this implementation, 32 bytes will fit all standard USBTMC headers -TU_VERIFY_STATIC(USBTMCD_BUFFER_SIZE >= 32u,"USBTMC dev buffer size too small"); +TU_VERIFY_STATIC(USBTMCD_BUFFER_SIZE >= 32u, "USBTMC dev buffer size too small"); static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len); static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen); @@ -171,26 +165,26 @@ tu_static uint8_t termChar; tu_static uint8_t termCharRequested = false; +tu_static bool usbtmcVendorSpecificRequested = false; + #if OSAL_MUTEX_REQUIRED static OSAL_MUTEX_DEF(usbtmcLockBuffer); #endif osal_mutex_t usbtmcLock; // Our own private lock, mostly for the state variable. -#define criticalEnter() do { (void) osal_mutex_lock(usbtmcLock,OSAL_TIMEOUT_WAIT_FOREVER); } while (0) -#define criticalLeave() do { (void) osal_mutex_unlock(usbtmcLock); } while (0) +#define criticalEnter() \ + do { (void) osal_mutex_lock(usbtmcLock, OSAL_TIMEOUT_WAIT_FOREVER); } while (0) +#define criticalLeave() \ + do { (void) osal_mutex_unlock(usbtmcLock); } while (0) -static bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_enum newState) -{ +static bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_enum newState) { bool ret = true; criticalEnter(); usbtmcd_state_enum oldState = usbtmc_state.state; - if (oldState == expectedState) - { + if (oldState == expectedState) { usbtmc_state.state = newState; - } - else - { + } else { ret = false; } criticalLeave(); @@ -205,54 +199,54 @@ static bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_en // We can't just send the whole thing at once because we need to concatanate the // header with the data. bool tud_usbtmc_transmit_dev_msg_data( - const void * data, size_t len, + const void *data, size_t len, bool endOfMessage, - bool usingTermChar) -{ + bool usingTermChar) { const unsigned int txBufLen = USBTMCD_BUFFER_SIZE; #ifndef NDEBUG TU_ASSERT(len > 0u); TU_ASSERT(len <= usbtmc_state.transfer_size_remaining); TU_ASSERT(usbtmc_state.transfer_size_sent == 0u); - if(usingTermChar) - { + if (usingTermChar) { TU_ASSERT(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); TU_ASSERT(termCharRequested); - TU_ASSERT(((uint8_t const*)data)[len-1u] == termChar); + TU_ASSERT(((uint8_t const *) data)[len - 1u] == termChar); } #endif TU_VERIFY(usbtmc_state.state == STATE_TX_REQUESTED); - usbtmc_msg_dev_dep_msg_in_header_t *hdr = (usbtmc_msg_dev_dep_msg_in_header_t*)usbtmc_epbuf.epin; + usbtmc_msg_dev_dep_msg_in_header_t *hdr = (usbtmc_msg_dev_dep_msg_in_header_t *) usbtmc_epbuf.epin; tu_varclr(hdr); - hdr->header.MsgID = USBTMC_MSGID_DEV_DEP_MSG_IN; + if (usbtmcVendorSpecificRequested) { + hdr->header.MsgID = USBTMC_MSGID_VENDOR_SPECIFIC_IN; + } else { + hdr->header.MsgID = USBTMC_MSGID_DEV_DEP_MSG_IN; + } hdr->header.bTag = usbtmc_state.lastBulkInTag; - hdr->header.bTagInverse = (uint8_t)~(usbtmc_state.lastBulkInTag); + hdr->header.bTagInverse = (uint8_t) ~(usbtmc_state.lastBulkInTag); hdr->TransferSize = len; hdr->bmTransferAttributes.EOM = endOfMessage; hdr->bmTransferAttributes.UsingTermChar = usingTermChar; // Copy in the header const size_t headerLen = sizeof(*hdr); - const size_t dataLen = ((headerLen + hdr->TransferSize) <= txBufLen) ? - len : (txBufLen - headerLen); + const size_t dataLen = ((headerLen + hdr->TransferSize) <= txBufLen) ? len : (txBufLen - headerLen); const size_t packetLen = headerLen + dataLen; - memcpy((uint8_t*)(usbtmc_epbuf.epin) + headerLen, data, dataLen); + memcpy((uint8_t *) (usbtmc_epbuf.epin) + headerLen, data, dataLen); usbtmc_state.transfer_size_remaining = len - dataLen; usbtmc_state.transfer_size_sent = dataLen; - usbtmc_state.devInBuffer = (uint8_t const*) data + (dataLen); + usbtmc_state.devInBuffer = (uint8_t const *) data + (dataLen); bool stateChanged = atomicChangeState(STATE_TX_REQUESTED, (packetLen >= txBufLen) ? STATE_TX_INITIATED : STATE_TX_SHORTED); TU_VERIFY(stateChanged); - TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t)packetLen)); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) packetLen)); return true; } -bool tud_usbtmc_transmit_notification_data(const void * data, size_t len) -{ +bool tud_usbtmc_transmit_notification_data(const void *data, size_t len) { #ifndef NDEBUG TU_ASSERT(len > 0); TU_ASSERT(usbtmc_state.ep_int_in != 0); @@ -260,24 +254,23 @@ bool tud_usbtmc_transmit_notification_data(const void * data, size_t len) TU_VERIFY(usbd_edpt_busy(usbtmc_state.rhport, usbtmc_state.ep_int_in)); TU_VERIFY(tu_memcpy_s(usbtmc_epbuf.epnotif, CFG_TUD_USBTMC_INT_EP_SIZE, data, len) == 0); - TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_int_in, usbtmc_epbuf.epnotif, (uint16_t)len)); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_int_in, usbtmc_epbuf.epnotif, (uint16_t) len)); return true; } -void usbtmcd_init_cb(void) -{ +void usbtmcd_init_cb(void) { usbtmc_state.capabilities = tud_usbtmc_get_capabilities_cb(); #ifndef NDEBUG -# if CFG_TUD_USBTMC_ENABLE_488 + #if CFG_TUD_USBTMC_ENABLE_488 if (usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger) { - TU_ASSERT(&tud_usbtmc_msg_trigger_cb != NULL,); + TU_ASSERT(&tud_usbtmc_msg_trigger_cb != NULL, ); } // Per USB488 spec: table 8 - TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.listenOnly,); - TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.talkOnly,); -# endif + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.listenOnly, ); + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.talkOnly, ); + #endif if (usbtmc_state.capabilities->bmIntfcCapabilities.supportsIndicatorPulse) { - TU_ASSERT(&tud_usbtmc_indicator_pulse_cb != NULL,); + TU_ASSERT(&tud_usbtmc_indicator_pulse_cb != NULL, ); } #endif @@ -285,26 +278,25 @@ void usbtmcd_init_cb(void) } bool usbtmcd_deinit(void) { - #if OSAL_MUTEX_REQUIRED +#if OSAL_MUTEX_REQUIRED osal_mutex_delete(usbtmcLock); - #endif +#endif return true; } -uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) -{ - (void)rhport; +uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + (void) rhport; uint16_t drv_len; - uint8_t const * p_desc; + uint8_t const *p_desc; uint8_t found_endpoints = 0; - TU_VERIFY(itf_desc->bInterfaceClass == TUD_USBTMC_APP_CLASS , 0); + TU_VERIFY(itf_desc->bInterfaceClass == TUD_USBTMC_APP_CLASS, 0); TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_USBTMC_APP_SUBCLASS, 0); #ifndef NDEBUG // Only 2 or 3 endpoints are allowed for USBTMC. - TU_ASSERT((itf_desc->bNumEndpoints == 2) || (itf_desc->bNumEndpoints ==3), 0); + TU_ASSERT((itf_desc->bNumEndpoints == 2) || (itf_desc->bNumEndpoints == 3), 0); #endif TU_ASSERT(usbtmc_state.state == STATE_CLOSED, 0); @@ -316,17 +308,14 @@ uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, usbtmc_state.itf_id = itf_desc->bInterfaceNumber; usbtmc_state.rhport = rhport; - while (found_endpoints < itf_desc->bNumEndpoints && drv_len <= max_len) - { - if ( TUSB_DESC_ENDPOINT == p_desc[DESC_OFFSET_TYPE]) - { - tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *)p_desc; - switch(ep_desc->bmAttributes.xfer) { + while (found_endpoints < itf_desc->bNumEndpoints && drv_len <= max_len) { + if (TUSB_DESC_ENDPOINT == p_desc[DESC_OFFSET_TYPE]) { + tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *) p_desc; + switch (ep_desc->bmAttributes.xfer) { case TUSB_XFER_BULK: // Ensure buffer is an exact multiple of the maxPacketSize TU_ASSERT((USBTMCD_BUFFER_SIZE % tu_edpt_packet_size(ep_desc)) == 0, 0); - if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) - { + if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) { usbtmc_state.ep_bulk_in = ep_desc->bEndpointAddress; usbtmc_state.ep_bulk_in_wMaxPacketSize = tu_edpt_packet_size(ep_desc); } else { @@ -345,33 +334,29 @@ uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, default: TU_ASSERT(false, 0); } - TU_ASSERT( usbd_edpt_open(rhport, ep_desc), 0); + TU_ASSERT(usbd_edpt_open(rhport, ep_desc), 0); found_endpoints++; } drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); + p_desc = tu_desc_next(p_desc); } - // bulk endpoints are required, but interrupt IN is optional +// bulk endpoints are required, but interrupt IN is optional #ifndef NDEBUG - TU_ASSERT(usbtmc_state.ep_bulk_in != 0, 0); + TU_ASSERT(usbtmc_state.ep_bulk_in != 0, 0); TU_ASSERT(usbtmc_state.ep_bulk_out != 0, 0); - if (itf_desc->bNumEndpoints == 2) - { + if (itf_desc->bNumEndpoints == 2) { TU_ASSERT(usbtmc_state.ep_int_in == 0, 0); - } - else if (itf_desc->bNumEndpoints == 3) - { + } else if (itf_desc->bNumEndpoints == 3) { TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); } -#if (CFG_TUD_USBTMC_ENABLE_488) - if(usbtmc_state.capabilities->bmIntfcCapabilities488.is488_2 || - usbtmc_state.capabilities->bmDevCapabilities488.SR1) - { + #if (CFG_TUD_USBTMC_ENABLE_488) + if (usbtmc_state.capabilities->bmIntfcCapabilities488.is488_2 || + usbtmc_state.capabilities->bmDevCapabilities488.SR1) { TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); } -#endif + #endif #endif atomicChangeState(STATE_CLOSED, STATE_NAK); tud_usbtmc_open_cb(itf_desc->iInterface); @@ -384,30 +369,27 @@ uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, // processing a command (such as a clear). Returns true if it was // in the NAK state and successfully transitioned to the ACK wait // state. -bool tud_usbtmc_start_bus_read(void) -{ +bool tud_usbtmc_start_bus_read(void) { usbtmcd_state_enum oldState = usbtmc_state.state; - switch(oldState) - { - // These may transition to IDLE - case STATE_NAK: - case STATE_ABORTING_BULK_IN_ABORTED: - TU_VERIFY(atomicChangeState(oldState, STATE_IDLE)); - break; - // When receiving, let it remain receiving - case STATE_RCV: - break; - default: - return false; + switch (oldState) { + // These may transition to IDLE + case STATE_NAK: + case STATE_ABORTING_BULK_IN_ABORTED: + TU_VERIFY(atomicChangeState(oldState, STATE_IDLE)); + break; + // When receiving, let it remain receiving + case STATE_RCV: + break; + default: + return false; } - TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_out, usbtmc_epbuf.epout, (uint16_t)usbtmc_state.ep_bulk_out_wMaxPacketSize)); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_out, usbtmc_epbuf.epout, (uint16_t) usbtmc_state.ep_bulk_out_wMaxPacketSize)); return true; } -void usbtmcd_reset_cb(uint8_t rhport) -{ - (void)rhport; - usbtmc_capabilities_specific_t const * capabilities = tud_usbtmc_get_capabilities_cb(); +void usbtmcd_reset_cb(uint8_t rhport) { + (void) rhport; + usbtmc_capabilities_specific_t const *capabilities = tud_usbtmc_get_capabilities_cb(); criticalEnter(); tu_varclr(&usbtmc_state); @@ -416,35 +398,32 @@ void usbtmcd_reset_cb(uint8_t rhport) criticalLeave(); } -static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len) -{ - (void)rhport; +static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len) { + (void) rhport; // return true upon failure, as we can assume error is being handled elsewhere. TU_VERIFY(atomicChangeState(STATE_IDLE, STATE_RCV), true); usbtmc_state.transfer_size_sent = 0u; // must be a header, should have been confirmed before calling here. - usbtmc_msg_request_dev_dep_out *msg = (usbtmc_msg_request_dev_dep_out*)data; + usbtmc_msg_request_dev_dep_out *msg = (usbtmc_msg_request_dev_dep_out *) data; usbtmc_state.transfer_size_remaining = msg->TransferSize; TU_VERIFY(tud_usbtmc_msgBulkOut_start_cb(msg)); - TU_VERIFY(handle_devMsgOut(rhport, (uint8_t*)data + sizeof(*msg), len - sizeof(*msg), len)); + TU_VERIFY(handle_devMsgOut(rhport, (uint8_t *) data + sizeof(*msg), len - sizeof(*msg), len)); usbtmc_state.lastBulkOutTag = msg->header.bTag; return true; } -static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen) -{ - (void)rhport; +static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen) { + (void) rhport; // return true upon failure, as we can assume error is being handled elsewhere. - TU_VERIFY(usbtmc_state.state == STATE_RCV,true); + TU_VERIFY(usbtmc_state.state == STATE_RCV, true); bool shortPacket = (packetLen < usbtmc_state.ep_bulk_out_wMaxPacketSize); // Packet is to be considered complete when we get enough data or at a short packet. bool atEnd = false; - if(len >= usbtmc_state.transfer_size_remaining || shortPacket) - { + if (len >= usbtmc_state.transfer_size_remaining || shortPacket) { atEnd = true; TU_VERIFY(atomicChangeState(STATE_RCV, STATE_NAK)); } @@ -455,8 +434,7 @@ static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t pack usbtmc_state.transfer_size_sent += len; // App may (should?) call the wait_for_bus() command at this point - if(!tud_usbtmc_msg_data_cb(data, len, atEnd)) - { + if (!tud_usbtmc_msg_data_cb(data, len, atEnd)) { // TODO: Go to an error state upon failure other than just stalling the EP? return false; } @@ -465,10 +443,9 @@ static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t pack return true; } -static bool handle_devMsgIn(void *data, size_t len) -{ +static bool handle_devMsgIn(void *data, size_t len) { TU_VERIFY(len == sizeof(usbtmc_msg_request_dev_dep_in)); - usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in*)data; + usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in *) data; bool stateChanged = atomicChangeState(STATE_IDLE, STATE_TX_REQUESTED); TU_VERIFY(stateChanged); usbtmc_state.lastBulkInTag = msg->header.bTag; @@ -481,134 +458,135 @@ static bool handle_devMsgIn(void *data, size_t len) termChar = msg->TermChar; #endif - if(termCharRequested) + if (termCharRequested) TU_VERIFY(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); TU_VERIFY(tud_usbtmc_msgBulkIn_request_cb(msg)); return true; } -bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { TU_VERIFY(result == XFER_RESULT_SUCCESS); //uart_tx_str_sync("TMC XFER CB\r\n"); - if(usbtmc_state.state == STATE_CLEARING) { + if (usbtmc_state.state == STATE_CLEARING) { return true; /* I think we can ignore everything here */ } - if(ep_addr == usbtmc_state.ep_bulk_out) - { + if (ep_addr == usbtmc_state.ep_bulk_out) { usbtmc_msg_generic_t *msg = NULL; - switch(usbtmc_state.state) - { - case STATE_IDLE: - { + switch (usbtmc_state.state) { + case STATE_IDLE: { TU_VERIFY(xferred_bytes >= sizeof(usbtmc_msg_generic_t)); - msg = (usbtmc_msg_generic_t*)(usbtmc_epbuf.epout); - uint8_t invInvTag = (uint8_t)~(msg->header.bTagInverse); + msg = (usbtmc_msg_generic_t *) (usbtmc_epbuf.epout); + uint8_t invInvTag = (uint8_t) ~(msg->header.bTagInverse); TU_VERIFY(msg->header.bTag == invInvTag); TU_VERIFY(msg->header.bTag != 0x00); - switch(msg->header.MsgID) { - case USBTMC_MSGID_DEV_DEP_MSG_OUT: - if(!handle_devMsgOutStart(rhport, msg, xferred_bytes)) - { - usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); - return false; - } - break; + switch (msg->header.MsgID) { + case USBTMC_MSGID_DEV_DEP_MSG_OUT: + usbtmcVendorSpecificRequested = false; + if (!handle_devMsgOutStart(rhport, msg, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + break; - case USBTMC_MSGID_DEV_DEP_MSG_IN: - TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); - break; + case USBTMC_MSGID_DEV_DEP_MSG_IN: + usbtmcVendorSpecificRequested = false; + TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); + break; #if (CFG_TUD_USBTMC_ENABLE_488) - case USBTMC_MSGID_USB488_TRIGGER: - // Spec says we halt the EP if we didn't declare we support it. - TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger); - TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg)); + case USBTMC_MSGID_USB488_TRIGGER: + // Spec says we halt the EP if we didn't declare we support it. + TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger); + TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg)); - break; + break; #endif - case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT: - case USBTMC_MSGID_VENDOR_SPECIFIC_IN: - default: + case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT: + usbtmcVendorSpecificRequested = true; + if (!handle_devMsgOutStart(rhport, msg, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + break; + + case USBTMC_MSGID_VENDOR_SPECIFIC_IN: + usbtmcVendorSpecificRequested = true; + TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); + break; + + default: + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + return true; + } + case STATE_RCV: + if (!handle_devMsgOut(rhport, usbtmc_epbuf.epout, xferred_bytes, xferred_bytes)) { usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); return false; } return true; - } - case STATE_RCV: - if(!handle_devMsgOut(rhport, usbtmc_epbuf.epout, xferred_bytes, xferred_bytes)) - { - usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); - return false; - } - return true; - case STATE_ABORTING_BULK_OUT: - // Should be stalled by now, shouldn't have received a packet. - return false; + case STATE_ABORTING_BULK_OUT: + // Should be stalled by now, shouldn't have received a packet. + return false; - case STATE_TX_REQUESTED: - case STATE_TX_INITIATED: - case STATE_ABORTING_BULK_IN: - case STATE_ABORTING_BULK_IN_SHORTED: - case STATE_ABORTING_BULK_IN_ABORTED: - default: - return false; + case STATE_TX_REQUESTED: + case STATE_TX_INITIATED: + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_IN_SHORTED: + case STATE_ABORTING_BULK_IN_ABORTED: + default: + return false; } - } - else if(ep_addr == usbtmc_state.ep_bulk_in) - { - switch(usbtmc_state.state) { - case STATE_TX_SHORTED: - TU_VERIFY(atomicChangeState(STATE_TX_SHORTED, STATE_NAK)); - TU_VERIFY(tud_usbtmc_msgBulkIn_complete_cb()); - break; + } else if (ep_addr == usbtmc_state.ep_bulk_in) { + switch (usbtmc_state.state) { + case STATE_TX_SHORTED: + TU_VERIFY(atomicChangeState(STATE_TX_SHORTED, STATE_NAK)); + TU_VERIFY(tud_usbtmc_msgBulkIn_complete_cb()); + break; - case STATE_TX_INITIATED: - if(usbtmc_state.transfer_size_remaining >= USBTMCD_BUFFER_SIZE) - { - // Copy buffer to ensure alignment correctness - memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, USBTMCD_BUFFER_SIZE); - TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, USBTMCD_BUFFER_SIZE)); - usbtmc_state.devInBuffer += USBTMCD_BUFFER_SIZE; - usbtmc_state.transfer_size_remaining -= USBTMCD_BUFFER_SIZE; - usbtmc_state.transfer_size_sent += USBTMCD_BUFFER_SIZE; - } - else // last packet - { - size_t packetLen = usbtmc_state.transfer_size_remaining; - memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining); - usbtmc_state.transfer_size_sent += sizeof(usbtmc_state.transfer_size_remaining); - usbtmc_state.transfer_size_remaining = 0; - usbtmc_state.devInBuffer = NULL; - TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t)packetLen) ); - if(((packetLen % usbtmc_state.ep_bulk_in_wMaxPacketSize) != 0) || (packetLen == 0 )) + case STATE_TX_INITIATED: + if (usbtmc_state.transfer_size_remaining >= USBTMCD_BUFFER_SIZE) { + // Copy buffer to ensure alignment correctness + memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, USBTMCD_BUFFER_SIZE); + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, USBTMCD_BUFFER_SIZE)); + usbtmc_state.devInBuffer += USBTMCD_BUFFER_SIZE; + usbtmc_state.transfer_size_remaining -= USBTMCD_BUFFER_SIZE; + usbtmc_state.transfer_size_sent += USBTMCD_BUFFER_SIZE; + } else// last packet { - usbtmc_state.state = STATE_TX_SHORTED; + size_t packetLen = usbtmc_state.transfer_size_remaining; + memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining); + usbtmc_state.transfer_size_sent += packetLen; + usbtmc_state.transfer_size_remaining = 0; + usbtmc_state.devInBuffer = NULL; + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) packetLen)); + if (((packetLen % usbtmc_state.ep_bulk_in_wMaxPacketSize) != 0) || (packetLen == 0)) { + usbtmc_state.state = STATE_TX_SHORTED; + } } - } - return true; + return true; - case STATE_ABORTING_BULK_IN: - // need to send short packet (ZLP?) - TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin,(uint16_t)0u)); - usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; - return true; + case STATE_ABORTING_BULK_IN: + // need to send short packet (ZLP?) + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) 0u)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + return true; - case STATE_ABORTING_BULK_IN_SHORTED: - /* Done. :)*/ - usbtmc_state.state = STATE_ABORTING_BULK_IN_ABORTED; - return true; + case STATE_ABORTING_BULK_IN_SHORTED: + /* Done. :)*/ + usbtmc_state.state = STATE_ABORTING_BULK_IN_ABORTED; + return true; - default: - TU_ASSERT(false); + default: + TU_ASSERT(false); } - } - else if (ep_addr == usbtmc_state.ep_int_in) { + } else if (ep_addr == usbtmc_state.ep_int_in) { if (tud_usbtmc_notification_complete_cb) { TU_VERIFY(tud_usbtmc_notification_complete_cb()); } @@ -620,188 +598,157 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint // Invoked when a control transfer occurred on an interface of this class // Driver response accordingly to the request and the transfer stage (setup/data/ack) // return false to stall control endpoint (e.g unsupported request) -bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) -{ +bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { // nothing to do with DATA and ACK stage - if ( stage != CONTROL_STAGE_SETUP ) return true; + if (stage != CONTROL_STAGE_SETUP) return true; uint8_t tmcStatusCode = USBTMC_STATUS_FAILED; #if (CFG_TUD_USBTMC_ENABLE_488) uint8_t bTag; #endif - if((request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) && + if ((request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) && (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_ENDPOINT) && (request->bRequest == TUSB_REQ_CLEAR_FEATURE) && - (request->wValue == TUSB_REQ_FEATURE_EDPT_HALT)) - { + (request->wValue == TUSB_REQ_FEATURE_EDPT_HALT)) { uint32_t ep_addr = (request->wIndex); // At this point, a transfer MAY be in progress. Based on USB spec, when clearing bulk EP HALT, // the EP transfer buffer needs to be cleared and DTOG needs to be reset, even if // the EP is not halted. The only USBD API interface to do this is to stall and then un-stall the EP. - if(ep_addr == usbtmc_state.ep_bulk_out) - { + if (ep_addr == usbtmc_state.ep_bulk_out) { criticalEnter(); - usbd_edpt_stall(rhport, (uint8_t)ep_addr); - usbd_edpt_clear_stall(rhport, (uint8_t)ep_addr); - usbtmc_state.state = STATE_NAK; // USBD core has placed EP in NAK state for us + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); + usbtmc_state.state = STATE_NAK;// USBD core has placed EP in NAK state for us criticalLeave(); tud_usbtmc_bulkOut_clearFeature_cb(); - } - else if (ep_addr == usbtmc_state.ep_bulk_in) - { - usbd_edpt_stall(rhport, (uint8_t)ep_addr); - usbd_edpt_clear_stall(rhport, (uint8_t)ep_addr); + } else if (ep_addr == usbtmc_state.ep_bulk_in) { + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); tud_usbtmc_bulkIn_clearFeature_cb(); - } - else if ((usbtmc_state.ep_int_in != 0) && (ep_addr == usbtmc_state.ep_int_in)) - { + } else if ((usbtmc_state.ep_int_in != 0) && (ep_addr == usbtmc_state.ep_int_in)) { // Clearing interrupt in EP - usbd_edpt_stall(rhport, (uint8_t)ep_addr); - usbd_edpt_clear_stall(rhport, (uint8_t)ep_addr); - } - else - { + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); + } else { return false; } return true; } // Otherwise, we only handle class requests. - if(request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) - { + if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) { return false; } // Verification that we own the interface is unneeded since it's been routed to us specifically. - switch(request->bRequest) - { - // USBTMC required requests - case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT: - { - usbtmc_initiate_abort_rsp_t rsp = { - .bTag = usbtmc_state.lastBulkOutTag, - }; - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,interface - TU_VERIFY(request->wLength == sizeof(rsp)); - TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + switch (request->bRequest) { + // USBTMC required requests + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT: { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkOutTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); - // wValue is the requested bTag to abort - if(usbtmc_state.state != STATE_RCV) - { - rsp.USBTMC_status = USBTMC_STATUS_FAILED; + // wValue is the requested bTag to abort + if (usbtmc_state.state != STATE_RCV) { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } else if (usbtmc_state.lastBulkOutTag == (request->wValue & 0x7Fu)) { + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } else { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = STATE_ABORTING_BULK_OUT; + criticalLeave(); + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_out_cb(&(rsp.USBTMC_status))); + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; } - else if(usbtmc_state.lastBulkOutTag == (request->wValue & 0x7Fu)) - { - rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + + case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS: { + usbtmc_check_abort_bulk_rsp_t rsp = { + .USBTMC_status = USBTMC_STATUS_SUCCESS, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent}; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,EP + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + TU_VERIFY(tud_usbtmc_check_abort_bulk_out_cb(&rsp)); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; } - else - { - rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; - // Check if we've queued a short packet - criticalEnter(); - usbtmc_state.state = STATE_ABORTING_BULK_OUT; - criticalLeave(); - TU_VERIFY(tud_usbtmc_initiate_abort_bulk_out_cb(&(rsp.USBTMC_status))); - usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN: { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkInTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_in); + // wValue is the requested bTag to abort + if ((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED) && + usbtmc_state.lastBulkInTag == (request->wValue & 0x7Fu)) { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.transfer_size_remaining = 0u; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = ((usbtmc_state.transfer_size_sent % usbtmc_state.ep_bulk_in_wMaxPacketSize) == 0) ? STATE_ABORTING_BULK_IN : STATE_ABORTING_BULK_IN_SHORTED; + criticalLeave(); + if (usbtmc_state.transfer_size_sent == 0) { + // Send short packet, nothing is in the buffer yet + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) 0u)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + } + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_in_cb(&(rsp.USBTMC_status))); + } else if ((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED)) {// FIXME: Unsure how to check if the OUT endpoint fifo is non-empty.... + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } else { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - return true; - } - case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS: - { - usbtmc_check_abort_bulk_rsp_t rsp = { - .USBTMC_status = USBTMC_STATUS_SUCCESS, - .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent - }; - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP - TU_VERIFY(request->wLength == sizeof(rsp)); - TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); - TU_VERIFY(tud_usbtmc_check_abort_bulk_out_cb(&rsp)); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - return true; - } + case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS: { + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,EP + TU_VERIFY(request->wLength == 8u); - case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN: - { - usbtmc_initiate_abort_rsp_t rsp = { - .bTag = usbtmc_state.lastBulkInTag, - }; - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,interface - TU_VERIFY(request->wLength == sizeof(rsp)); - TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_in); - // wValue is the requested bTag to abort - if((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED) && - usbtmc_state.lastBulkInTag == (request->wValue & 0x7Fu)) - { - rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; - usbtmc_state.transfer_size_remaining = 0u; - // Check if we've queued a short packet + usbtmc_check_abort_bulk_rsp_t rsp = + { + .USBTMC_status = USBTMC_STATUS_FAILED, + .bmAbortBulkIn = + { + .BulkInFifoBytes = (usbtmc_state.state != STATE_ABORTING_BULK_IN_ABORTED)}, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent, + }; + TU_VERIFY(tud_usbtmc_check_abort_bulk_in_cb(&rsp)); criticalEnter(); - usbtmc_state.state = ((usbtmc_state.transfer_size_sent % usbtmc_state.ep_bulk_in_wMaxPacketSize) == 0) ? - STATE_ABORTING_BULK_IN : STATE_ABORTING_BULK_IN_SHORTED; - criticalLeave(); - if(usbtmc_state.transfer_size_sent == 0) - { - // Send short packet, nothing is in the buffer yet - TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin,(uint16_t)0u)); - usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + switch (usbtmc_state.state) { + case STATE_ABORTING_BULK_IN_ABORTED: + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.state = STATE_IDLE; + break; + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_OUT: + rsp.USBTMC_status = USBTMC_STATUS_PENDING; + break; + default: + break; } - TU_VERIFY(tud_usbtmc_initiate_abort_bulk_in_cb(&(rsp.USBTMC_status))); - } - else if((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED)) - { // FIXME: Unsure how to check if the OUT endpoint fifo is non-empty.... - rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; - } - else - { - rsp.USBTMC_status = USBTMC_STATUS_FAILED; - } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - return true; - } - - case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS: - { - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP - TU_VERIFY(request->wLength == 8u); + criticalLeave(); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); - usbtmc_check_abort_bulk_rsp_t rsp = - { - .USBTMC_status = USBTMC_STATUS_FAILED, - .bmAbortBulkIn = - { - .BulkInFifoBytes = (usbtmc_state.state != STATE_ABORTING_BULK_IN_ABORTED) - }, - .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent, - }; - TU_VERIFY(tud_usbtmc_check_abort_bulk_in_cb(&rsp)); - criticalEnter(); - switch(usbtmc_state.state) - { - case STATE_ABORTING_BULK_IN_ABORTED: - rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; - usbtmc_state.state = STATE_IDLE; - break; - case STATE_ABORTING_BULK_IN: - case STATE_ABORTING_BULK_OUT: - rsp.USBTMC_status = USBTMC_STATUS_PENDING; - break; - default: - break; + return true; } - criticalLeave(); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - return true; - } - - case USBTMC_bREQUEST_INITIATE_CLEAR: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + case USBTMC_bREQUEST_INITIATE_CLEAR: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); // After receiving an INITIATE_CLEAR request, the device must Halt the Bulk-OUT endpoint, queue the // control endpoint response shown in Table 31, and clear all input buffers and output buffers. @@ -811,112 +758,97 @@ bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request usbtmc_state.state = STATE_CLEARING; criticalLeave(); TU_VERIFY(tud_usbtmc_initiate_clear_cb(&tmcStatusCode)); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode,sizeof(tmcStatusCode))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &tmcStatusCode, sizeof(tmcStatusCode))); return true; } - case USBTMC_bREQUEST_CHECK_CLEAR_STATUS: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + case USBTMC_bREQUEST_CHECK_CLEAR_STATUS: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface usbtmc_get_clear_status_rsp_t clearStatusRsp = {0}; TU_VERIFY(request->wLength == sizeof(clearStatusRsp)); - if(usbd_edpt_busy(rhport, usbtmc_state.ep_bulk_in)) - { + if (usbd_edpt_busy(rhport, usbtmc_state.ep_bulk_in)) { // Stuff stuck in TX buffer? clearStatusRsp.bmClear.BulkInFifoBytes = 1; clearStatusRsp.USBTMC_status = USBTMC_STATUS_PENDING; - } - else - { + } else { // Let app check if it's clear TU_VERIFY(tud_usbtmc_check_clear_cb(&clearStatusRsp)); } - if(clearStatusRsp.USBTMC_status == USBTMC_STATUS_SUCCESS) - { + if (clearStatusRsp.USBTMC_status == USBTMC_STATUS_SUCCESS) { criticalEnter(); usbtmc_state.state = STATE_IDLE; criticalLeave(); } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&clearStatusRsp,sizeof(clearStatusRsp))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &clearStatusRsp, sizeof(clearStatusRsp))); return true; } - case USBTMC_bREQUEST_GET_CAPABILITIES: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + case USBTMC_bREQUEST_GET_CAPABILITIES: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface TU_VERIFY(request->wLength == sizeof(*(usbtmc_state.capabilities))); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)(uintptr_t) usbtmc_state.capabilities, sizeof(*usbtmc_state.capabilities))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) (uintptr_t) usbtmc_state.capabilities, sizeof(*usbtmc_state.capabilities))); return true; } - // USBTMC Optional Requests + // USBTMC Optional Requests - case USBTMC_bREQUEST_INDICATOR_PULSE: // Optional + case USBTMC_bREQUEST_INDICATOR_PULSE:// Optional { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities.supportsIndicatorPulse); TU_VERIFY(tud_usbtmc_indicator_pulse_cb(request, &tmcStatusCode)); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode, sizeof(tmcStatusCode))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &tmcStatusCode, sizeof(tmcStatusCode))); return true; } #if (CFG_TUD_USBTMC_ENABLE_488) - // USB488 required requests - case USB488_bREQUEST_READ_STATUS_BYTE: - { + // USB488 required requests + case USB488_bREQUEST_READ_STATUS_BYTE: { usbtmc_read_stb_rsp_488_t rsp; TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface - TU_VERIFY(request->wLength == sizeof(rsp)); // in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp));// in,class,interface bTag = request->wValue & 0x7F; TU_VERIFY(request->bmRequestType == 0xA1); - TU_VERIFY((request->wValue & (~0x7F)) == 0u); // Other bits are required to be zero (USB488v1.0 Table 11) + TU_VERIFY((request->wValue & (~0x7F)) == 0u);// Other bits are required to be zero (USB488v1.0 Table 11) TU_VERIFY(bTag >= 0x02 && bTag <= 127); TU_VERIFY(request->wIndex == usbtmc_state.itf_id); TU_VERIFY(request->wLength == 0x0003); - rsp.bTag = (uint8_t)bTag; - if(usbtmc_state.ep_int_in != 0) - { - rsp.statusByte = 0x00; // Use interrupt endpoint, instead. Must be 0x00 (USB488v1.0 4.3.1.2) - if(usbd_edpt_busy(rhport, usbtmc_state.ep_int_in)) - { + rsp.bTag = (uint8_t) bTag; + if (usbtmc_state.ep_int_in != 0) { + rsp.statusByte = 0x00;// Use interrupt endpoint, instead. Must be 0x00 (USB488v1.0 4.3.1.2) + if (usbd_edpt_busy(rhport, usbtmc_state.ep_int_in)) { rsp.USBTMC_status = USB488_STATUS_INTERRUPT_IN_BUSY; - } - else - { + } else { rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; usbtmc_read_stb_interrupt_488_t intMsg = - { - .bNotify1 = { - .one = 1, - .bTag = bTag & 0x7Fu, - }, - .StatusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status)) - }; + { + .bNotify1 = { + .one = 1, + .bTag = bTag & 0x7Fu, + }, + .StatusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status))}; // Must be queued before control request response sent (USB488v1.0 4.3.1.2) - usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void*)&intMsg, sizeof(intMsg)); + usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void *) &intMsg, sizeof(intMsg)); } - } - else - { + } else { rsp.statusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status)); } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp, sizeof(rsp))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); return true; } - // USB488 optional requests - case USB488_bREQUEST_REN_CONTROL: - case USB488_bREQUEST_GO_TO_LOCAL: - case USB488_bREQUEST_LOCAL_LOCKOUT: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + // USB488 optional requests + case USB488_bREQUEST_REN_CONTROL: + case USB488_bREQUEST_GO_TO_LOCAL: + case USB488_bREQUEST_LOCAL_LOCKOUT: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface return false; } #endif - default: - return false; + default: + return false; } } diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c index 2fc0ac944..b0332b0fe 100644 --- a/src/class/vendor/vendor_device.c +++ b/src/class/vendor/vendor_device.c @@ -196,40 +196,41 @@ void vendord_reset(uint8_t rhport) { uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) { TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == desc_itf->bInterfaceClass, 0); + const uint8_t* desc_end = (const uint8_t*)desc_itf + max_len; const uint8_t* p_desc = tu_desc_next(desc_itf); - const uint8_t* desc_end = (uint8_t const*)desc_itf + max_len; // Find available interface vendord_interface_t* p_vendor = NULL; - for(uint8_t i=0; i<CFG_TUD_VENDOR; i++) { - if (!tud_vendor_n_mounted(i)) { - p_vendor = &_vendord_itf[i]; + uint8_t itf; + for(itf=0; itf<CFG_TUD_VENDOR; itf++) { + if (!tud_vendor_n_mounted(itf)) { + p_vendor = &_vendord_itf[itf]; break; } } TU_VERIFY(p_vendor, 0); p_vendor->itf_num = desc_itf->bInterfaceNumber; - uint8_t found_ep = 0; - while (found_ep < desc_itf->bNumEndpoints) { - // skip non-endpoint descriptors - while ( (TUSB_DESC_ENDPOINT != tu_desc_type(p_desc)) && (p_desc < desc_end) ) { - p_desc = tu_desc_next(p_desc); - } - if (p_desc >= desc_end) { - break; - } - - const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; - TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); - found_ep++; + while (tu_desc_in_bounds(p_desc, desc_end)) { + const uint8_t desc_type = tu_desc_type(p_desc); + if (desc_type == TUSB_DESC_INTERFACE || desc_type == TUSB_DESC_INTERFACE_ASSOCIATION) { + break; // end of this interface + } else if (desc_type == TUSB_DESC_ENDPOINT) { + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); - if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { - tu_edpt_stream_open(&p_vendor->tx.stream, desc_ep); - tud_vendor_n_write_flush((uint8_t)(p_vendor - _vendord_itf)); - } else { - tu_edpt_stream_open(&p_vendor->rx.stream, desc_ep); - TU_ASSERT(tu_edpt_stream_read_xfer(rhport, &p_vendor->rx.stream) > 0, 0); // prepare for incoming data + // open endpoint stream, skip if already opened + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + if (p_vendor->tx.stream.ep_addr == 0) { + tu_edpt_stream_open(&p_vendor->tx.stream, desc_ep); + tud_vendor_n_write_flush(itf); + } + } else { + if (p_vendor->rx.stream.ep_addr == 0) { + tu_edpt_stream_open(&p_vendor->rx.stream, desc_ep); + TU_ASSERT(tu_edpt_stream_read_xfer(rhport, &p_vendor->rx.stream) > 0, 0); // prepare for incoming data + } + } } p_desc = tu_desc_next(p_desc); diff --git a/src/common/tusb_common.h b/src/common/tusb_common.h index 0351a3d8f..e35d3e6fe 100644 --- a/src/common/tusb_common.h +++ b/src/common/tusb_common.h @@ -78,10 +78,16 @@ #include "tusb_debug.h" //--------------------------------------------------------------------+ -// Optional API implemented by application if needed +// API implemented by application if needed // TODO move to a more obvious place/file //--------------------------------------------------------------------+ +// Get current milliseconds, required by some port/configuration without RTOS +extern uint32_t tusb_time_millis_api(void); + +// Delay in milliseconds, use tusb_time_millis_api() by default. required by some port/configuration with no RTOS +extern void tusb_time_delay_ms_api(uint32_t ms); + // flush data cache TU_ATTR_WEAK extern void tusb_app_dcache_flush(uintptr_t addr, uint32_t data_size); @@ -162,8 +168,8 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_high(uint16_t ui16) { return TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_low (uint16_t ui16) { return TU_U16_LOW(ui16); } //------------- Bits -------------// -TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_set (uint32_t value, uint8_t pos) { return value | TU_BIT(pos); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_clear(uint32_t value, uint8_t pos) { return value & (~TU_BIT(pos)); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_set (uint32_t value, uint8_t pos) { return value | TU_BIT(pos); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_clear(uint32_t value, uint8_t pos) { return value & (~TU_BIT(pos)); } TU_ATTR_ALWAYS_INLINE static inline bool tu_bit_test (uint32_t value, uint8_t pos) { return (value & TU_BIT(pos)) ? true : false; } //------------- Min -------------// @@ -324,6 +330,44 @@ TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void *mem, uint16_ + TU_BIN8(dlsb)) #endif +//--------------------------------------------------------------------+ +// Descriptor helper +//--------------------------------------------------------------------+ + +// return next descriptor +TU_ATTR_ALWAYS_INLINE static inline uint8_t const * tu_desc_next(void const* desc) { + uint8_t const* desc8 = (uint8_t const*) desc; + return desc8 + desc8[DESC_OFFSET_LEN]; +} + +// get descriptor length +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; +} + +// get descriptor type +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_type(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; +} + +// get descriptor subtype +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_subtype(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_SUBTYPE]; +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_in_bounds(uint8_t const* p_desc, uint8_t const* desc_end) { + return (p_desc < desc_end) && (tu_desc_next(p_desc) <= desc_end); +} + +// find descriptor that match byte1 (type) +uint8_t const * tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1); + +// find descriptor that match byte1 (type) and byte2 +uint8_t const * tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2); + +// find descriptor that match byte1 (type) and byte2 +uint8_t const * tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3); + #ifdef __cplusplus } #endif diff --git a/src/common/tusb_debug.h b/src/common/tusb_debug.h index 2e9f1d9cd..1d0c6f1ad 100644 --- a/src/common/tusb_debug.h +++ b/src/common/tusb_debug.h @@ -108,15 +108,13 @@ typedef struct { } tu_lookup_table_t; static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint32_t key) { - tu_static char not_found[11]; - for(uint16_t i=0; i<p_table->count; i++) { - if (p_table->items[i].key == key) return p_table->items[i].data; + if (p_table->items[i].key == key) { return p_table->items[i].data; } } // not found return the key value in hex + static char not_found[11]; snprintf(not_found, sizeof(not_found), "0x%08lX", (unsigned long) key); - return not_found; } diff --git a/src/common/tusb_fifo.c b/src/common/tusb_fifo.c index 5f2dcabad..f7679556f 100644 --- a/src/common/tusb_fifo.c +++ b/src/common/tusb_fifo.c @@ -428,7 +428,6 @@ static bool _tu_fifo_peek(tu_fifo_t* f, void * p_buffer, uint16_t wr_idx, uint16 if ( cnt > f->depth ) { rd_idx = _ff_correct_read_index(f, wr_idx); - cnt = f->depth; } uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); @@ -916,8 +915,11 @@ bool tu_fifo_clear(tu_fifo_t *f) Overwritable mode the fifo is set to */ /******************************************************************************/ -bool tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) -{ +bool tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) { + if (f->overwritable == overwritable) { + return true; + } + _ff_lock(f->mutex_wr); _ff_lock(f->mutex_rd); diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index a0175d664..7722ca1e6 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -117,9 +117,9 @@ #define TUP_RHPORT_HIGHSPEED 1 #if __CORTEX_M == 7 - #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT 1 - #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT 1 - #define CFG_TUSB_MEM_DCACHE_LINE_SIZE 32 + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT 1 + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT 1 + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 #endif #elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L, OPT_MCU_KINETIS_K) @@ -220,12 +220,25 @@ #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS #endif + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #elif TU_CHECK_MCU(OPT_MCU_STM32H7) + #include "stm32h7xx.h" #define TUP_USBIP_DWC2 #define TUP_USBIP_DWC2_STM32 #define TUP_DCD_ENDPOINT_MAX 9 + #if __CORTEX_M == 7 + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #endif + #elif TU_CHECK_MCU(OPT_MCU_STM32H5) #define TUP_USBIP_FSDEV #define TUP_USBIP_FSDEV_STM32 @@ -282,6 +295,12 @@ #define TUP_USBIP_FSDEV_STM32 #define TUP_DCD_ENDPOINT_MAX 8 +#elif TU_CHECK_MCU(OPT_MCU_STM32WBA) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + #define TUP_DCD_ENDPOINT_MAX 9 + #define TUP_RHPORT_HIGHSPEED 1 + #elif TU_CHECK_MCU(OPT_MCU_STM32U5) #if defined (STM32U535xx) || defined (STM32U545xx) #define TUP_USBIP_FSDEV @@ -312,7 +331,7 @@ #define TUP_USBIP_FSDEV_STM32 #define TUP_DCD_ENDPOINT_MAX 8 -#elif TU_CHECK_MCU(OPT_MCU_STM32H7RS) +#elif TU_CHECK_MCU(OPT_MCU_STM32H7RS, OPT_MCU_STM32N6) #define TUP_USBIP_DWC2 #define TUP_USBIP_DWC2_STM32 @@ -322,6 +341,11 @@ // MCU with on-chip HS Phy #define TUP_RHPORT_HIGHSPEED 1 + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + //--------------------------------------------------------------------+ // Sony //--------------------------------------------------------------------+ @@ -369,6 +393,10 @@ #define TUP_DCD_ENDPOINT_MAX 7 // only 5 TX FIFO for endpoint IN #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + #if CFG_TUSB_MCU == OPT_MCU_ESP32S3 + #define TUP_MCU_MULTIPLE_CORE 1 + #endif + // Disable slave if DMA is enabled #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUD_DWC2_DMA_ENABLE #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUH_DWC2_DMA_ENABLE @@ -381,16 +409,18 @@ #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + #define TUP_MCU_MULTIPLE_CORE 1 + // Disable slave if DMA is enabled #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUD_DWC2_DMA_ENABLE #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUH_DWC2_DMA_ENABLE // Enable dcache if DMA is enabled - #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE - #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 64 -#elif TU_CHECK_MCU(OPT_MCU_ESP32, OPT_MCU_ESP32C2, OPT_MCU_ESP32C3, OPT_MCU_ESP32C6, OPT_MCU_ESP32H2) +#elif TU_CHECK_MCU(OPT_MCU_ESP32, OPT_MCU_ESP32C2, OPT_MCU_ESP32C3, OPT_MCU_ESP32C5, OPT_MCU_ESP32C6, OPT_MCU_ESP32C61, OPT_MCU_ESP32H2) #if (CFG_TUD_ENABLED || !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)) #error "MCUs are only supported with CFG_TUH_MAX3421 enabled" #endif @@ -410,6 +440,7 @@ #elif TU_CHECK_MCU(OPT_MCU_RP2040) #define TUP_DCD_EDPT_ISO_ALLOC #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_MCU_MULTIPLE_CORE 1 #define TU_ATTR_FAST_FUNC __attribute__((section(".time_critical.tinyusb"))) @@ -496,11 +527,17 @@ #define TUP_DCD_ENDPOINT_MAX 8 #elif TU_CHECK_MCU(OPT_MCU_CH32V20X) - // v20x support both FSDEV (USBD) and USBFS, default to FSDEV + // v20x support both port0 FSDEV (USBD) and port1 USBFS #define TUP_USBIP_WCH_USBFS + + #ifndef CFG_TUH_WCH_USBIP_USBFS + #define CFG_TUH_WCH_USBIP_USBFS 1 + #endif + #define TUP_USBIP_FSDEV #define TUP_USBIP_FSDEV_CH32 + // default to FSDEV for device #if !defined(CFG_TUD_WCH_USBIP_USBFS) #define CFG_TUD_WCH_USBIP_USBFS 0 #endif @@ -537,6 +574,53 @@ #define TUP_RHPORT_HIGHSPEED 1 #define TUD_ENDPOINT_ONE_DIRECTION_ONLY +//--------------------------------------------------------------------+ +// ArteryTek +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_AT32F403A_407) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F413) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F415) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 4 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F435_437) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F423) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F402_405) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + + // AT32F405xx has on-chip HS PHY + #if defined(AT32F405CBT7) || defined(AT32F405CBU7) || \ + defined(AT32F405CCT7) || defined(AT32F405CCU7) || \ + defined(AT32F405KBU7_4) || defined(AT32F405KCU7_4) || \ + defined(AT32F405RBT7_7) || defined(AT32F405RBT7) || \ + defined(AT32F405RCT7_7) || defined(AT32F405RCT7) + #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS + #endif + +#elif TU_CHECK_MCU(OPT_MCU_AT32F425) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + #endif //--------------------------------------------------------------------+ diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index e000a4bd3..b3ef1e9c9 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -43,14 +43,14 @@ #define TUD_EPBUF_DEF(_name, _size) \ union { \ CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ - uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type #define TUD_EPBUF_TYPE_DEF(_type, _name) \ union { \ CFG_TUD_MEM_ALIGN _type _name; \ - uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } //------------- Host DCache declaration -------------// @@ -61,14 +61,14 @@ #define TUH_EPBUF_DEF(_name, _size) \ union { \ CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ - uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type #define TUH_EPBUF_TYPE_DEF(_type, _name) \ union { \ CFG_TUH_MEM_ALIGN _type _name; \ - uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } @@ -278,6 +278,8 @@ typedef enum { XFER_RESULT_INVALID } xfer_result_t; +#define tusb_xfer_result_t xfer_result_t + // TODO remove enum { DESC_OFFSET_LEN = 0, @@ -345,7 +347,6 @@ typedef struct TU_ATTR_PACKED { uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. uint8_t iProduct ; ///< Index of string descriptor describing product. uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. - uint8_t bNumConfigurations ; ///< Number of possible configurations. } tusb_desc_device_t; @@ -561,40 +562,6 @@ TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_ } #endif -//--------------------------------------------------------------------+ -// Descriptor helper -//--------------------------------------------------------------------+ - -// return next descriptor -TU_ATTR_ALWAYS_INLINE static inline uint8_t const * tu_desc_next(void const* desc) { - uint8_t const* desc8 = (uint8_t const*) desc; - return desc8 + desc8[DESC_OFFSET_LEN]; -} - -// get descriptor length -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) { - return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; -} - -// get descriptor type -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_type(void const* desc) { - return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; -} - -// get descriptor subtype -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_subtype(void const* desc) { - return ((uint8_t const*) desc)[DESC_OFFSET_SUBTYPE]; -} - -// find descriptor that match byte1 (type) -uint8_t const * tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1); - -// find descriptor that match byte1 (type) and byte2 -uint8_t const * tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2); - -// find descriptor that match byte1 (type) and byte2 -uint8_t const * tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3); - #ifdef __cplusplus } #endif diff --git a/src/device/usbd.c b/src/device/usbd.c index 8f50d815f..8620c3b6f 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -163,6 +163,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = cdcd_open, .control_xfer_cb = cdcd_control_xfer_cb, .xfer_cb = cdcd_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -176,6 +177,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = mscd_open, .control_xfer_cb = mscd_control_xfer_cb, .xfer_cb = mscd_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -189,6 +191,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = hidd_open, .control_xfer_cb = hidd_control_xfer_cb, .xfer_cb = hidd_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -202,6 +205,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = audiod_open, .control_xfer_cb = audiod_control_xfer_cb, .xfer_cb = audiod_xfer_cb, + .xfer_isr = audiod_xfer_isr, .sof = audiod_sof_isr }, #endif @@ -215,6 +219,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = videod_open, .control_xfer_cb = videod_control_xfer_cb, .xfer_cb = videod_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -228,6 +233,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .reset = midid_reset, .control_xfer_cb = midid_control_xfer_cb, .xfer_cb = midid_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -241,6 +247,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = vendord_open, .control_xfer_cb = tud_vendor_control_xfer_cb, .xfer_cb = vendord_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -254,6 +261,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = usbtmcd_open_cb, .control_xfer_cb = usbtmcd_control_xfer_cb, .xfer_cb = usbtmcd_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -267,6 +275,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = dfu_rtd_open, .control_xfer_cb = dfu_rtd_control_xfer_cb, .xfer_cb = NULL, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -280,6 +289,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = dfu_moded_open, .control_xfer_cb = dfu_moded_control_xfer_cb, .xfer_cb = NULL, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -293,7 +303,8 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = netd_open, .control_xfer_cb = netd_control_xfer_cb, .xfer_cb = netd_xfer_cb, - .sof = NULL, + .xfer_isr = NULL, + .sof = NULL, }, #endif @@ -306,6 +317,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = btd_open, .control_xfer_cb = btd_control_xfer_cb, .xfer_cb = btd_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif @@ -330,38 +342,37 @@ enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) }; tu_static usbd_class_driver_t const * _app_driver = NULL; tu_static uint8_t _app_driver_count = 0; -#define TOTAL_DRIVER_COUNT (_app_driver_count + BUILTIN_DRIVER_COUNT) +#define TOTAL_DRIVER_COUNT ((uint8_t) (_app_driver_count + BUILTIN_DRIVER_COUNT)) // virtually joins built-in and application drivers together. // Application is positioned first to allow overwriting built-in ones. TU_ATTR_ALWAYS_INLINE static inline usbd_class_driver_t const * get_driver(uint8_t drvid) { - usbd_class_driver_t const * driver = NULL; - if ( drvid < _app_driver_count ) { + usbd_class_driver_t const *driver = NULL; + if (drvid < _app_driver_count) { // Application drivers driver = &_app_driver[drvid]; - } else if ( drvid < TOTAL_DRIVER_COUNT && BUILTIN_DRIVER_COUNT > 0 ){ + } else if (drvid < TOTAL_DRIVER_COUNT && BUILTIN_DRIVER_COUNT > 0) { driver = &_usbd_driver[drvid - _app_driver_count]; } return driver; } - //--------------------------------------------------------------------+ // DCD Event //--------------------------------------------------------------------+ - enum { RHPORT_INVALID = 0xFFu }; tu_static uint8_t _usbd_rhport = RHPORT_INVALID; -// Event queue -// usbd_int_set() is used as mutex in OS NONE config +static OSAL_SPINLOCK_DEF(_usbd_spin, usbd_int_set); + +// Event queue: usbd_int_set() is used as mutex in OS NONE config OSAL_QUEUE_DEF(usbd_int_set, _usbd_qdef, CFG_TUD_TASK_QUEUE_SZ, dcd_event_t); -tu_static osal_queue_t _usbd_q; +static osal_queue_t _usbd_q; // Mutex for claiming endpoint #if OSAL_MUTEX_REQUIRED - tu_static osal_mutex_def_t _ubsd_mutexdef; - tu_static osal_mutex_t _usbd_mutex; + static osal_mutex_def_t _ubsd_mutexdef; + static osal_mutex_t _usbd_mutex; #else #define _usbd_mutex NULL #endif @@ -477,17 +488,36 @@ bool tud_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; // skip if already initialized } TU_ASSERT(rh_init); - - TU_LOG_USBD("USBD init on controller %u, speed = %s\r\n", rhport, - rh_init->speed == TUSB_SPEED_HIGH ? "High" : "Full"); +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + char const* speed_str = 0; + switch (rh_init->speed) { + case TUSB_SPEED_HIGH: + speed_str = "High"; + break; + case TUSB_SPEED_FULL: + speed_str = "Full"; + break; + case TUSB_SPEED_LOW: + speed_str = "Low"; + break; + case TUSB_SPEED_AUTO: + speed_str = "Auto"; + break; + default: + break; + } + TU_LOG_USBD("USBD init on controller %u, speed = %s\r\n", rhport, speed_str); TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(usbd_device_t)); TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(dcd_event_t)); TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_fifo_t)); TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_edpt_stream_t)); +#endif tu_varclr(&_usbd_dev); _usbd_queued_setup = 0; + osal_spin_init(&_usbd_spin); + #if OSAL_MUTEX_REQUIRED // Init device mutex _usbd_mutex = osal_mutex_create(&_ubsd_mutexdef); @@ -501,6 +531,7 @@ bool tud_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // Get application driver if available if (usbd_app_driver_get_cb) { _app_driver = usbd_app_driver_get_cb(&_app_driver_count); + TU_ASSERT(_app_driver_count + BUILTIN_DRIVER_COUNT <= UINT8_MAX); } // Init class drivers @@ -574,8 +605,7 @@ static void usbd_reset(uint8_t rhport) { } bool tud_task_event_ready(void) { - // Skip if stack is not initialized - if (!tud_inited()) return false; + TU_VERIFY(tud_inited()); // Skip if stack is not initialized return !osal_queue_empty(_usbd_q); } @@ -697,7 +727,9 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { case USBD_EVENT_FUNC_CALL: TU_LOG_USBD("\r\n"); - if (event.func_call.func) event.func_call.func(event.func_call.param); + if (event.func_call.func) { + event.func_call.func(event.func_call.param); + } break; case DCD_EVENT_SOF: @@ -714,7 +746,7 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { #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(_usbd_q)) return; + if (osal_queue_empty(_usbd_q)) { return; } #endif } } @@ -1240,6 +1272,32 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) send = true; break; + case DCD_EVENT_XFER_COMPLETE: { + // Invoke the class callback associated with the endpoint address + uint8_t const ep_addr = event->xfer_complete.ep_addr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = tu_edpt_dir(ep_addr); + + send = true; + if(epnum > 0) { + usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); + + if (driver && driver->xfer_isr) { + _usbd_dev.ep_status[epnum][ep_dir].busy = 0; + _usbd_dev.ep_status[epnum][ep_dir].claimed = 0; + + send = !driver->xfer_isr(event->rhport, ep_addr, (xfer_result_t) event->xfer_complete.result, event->xfer_complete.len); + + // xfer_isr() is deferred to xfer_cb(), revert busy/claimed status + if (send) { + _usbd_dev.ep_status[epnum][ep_dir].busy = 1; + _usbd_dev.ep_status[epnum][ep_dir].claimed = 1; + } + } + } + break; + } + default: send = true; break; @@ -1254,17 +1312,21 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) // USBD API For Class Driver //--------------------------------------------------------------------+ -void usbd_int_set(bool enabled) -{ - if (enabled) - { +void usbd_int_set(bool enabled) { + if (enabled) { dcd_int_enable(_usbd_rhport); - }else - { + } else { dcd_int_disable(_usbd_rhport); } } +void usbd_spin_lock(bool in_isr) { + osal_spin_lock(&_usbd_spin, in_isr); +} +void usbd_spin_unlock(bool in_isr) { + osal_spin_unlock(&_usbd_spin, in_isr); +} + // Parse consecutive endpoint descriptors (IN & OUT) bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in) { diff --git a/src/device/usbd.h b/src/device/usbd.h index 921d5c0d0..1dc3761d9 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -244,7 +244,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ /* CDC Union */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ /* Endpoint Notification */\ - 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 16,\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\ /* CDC Data Interface */\ 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ /* Endpoint Out */\ diff --git a/src/device/usbd_pvt.h b/src/device/usbd_pvt.h index 190d6fd7f..f1797bf0d 100644 --- a/src/device/usbd_pvt.h +++ b/src/device/usbd_pvt.h @@ -57,6 +57,7 @@ typedef struct { uint16_t (* open ) (uint8_t rhport, tusb_desc_interface_t const * desc_intf, uint16_t max_len); bool (* control_xfer_cb ) (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); bool (* xfer_cb ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + bool (* xfer_isr ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); // optional, return false to defer to xfer_cb() void (* sof ) (uint8_t rhport, uint32_t frame_count); // optional } usbd_class_driver_t; @@ -68,6 +69,8 @@ usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) TU_ATTR typedef bool (*usbd_control_xfer_cb_t)(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); void usbd_int_set(bool enabled); +void usbd_spin_lock(bool in_isr); +void usbd_spin_unlock(bool in_isr); //--------------------------------------------------------------------+ // USBD Endpoint API diff --git a/src/host/hcd.h b/src/host/hcd.h index 56b6fdb5d..d3551bf5b 100644 --- a/src/host/hcd.h +++ b/src/host/hcd.h @@ -90,13 +90,6 @@ typedef struct { }; } hcd_event_t; -typedef struct { - uint8_t rhport; - uint8_t hub_addr; - uint8_t hub_port; - uint8_t speed; -} hcd_devtree_info_t; - //--------------------------------------------------------------------+ // Memory API //--------------------------------------------------------------------+ @@ -163,8 +156,12 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr); //--------------------------------------------------------------------+ // Open an endpoint +// return true if successfully opened or endpoint is currently opened bool hcd_edpt_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const * ep_desc); +// Close an endpoint +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr); + // Submit a transfer, when complete hcd_event_xfer_complete() must be invoked bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); @@ -182,13 +179,6 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr); // USBH implemented API //--------------------------------------------------------------------+ -// Get device tree information of a device -// USB device tree can be complicated and manged by USBH, this help HCD to retrieve -// needed topology info to carry out its work -extern void hcd_devtree_get_info(uint8_t dev_addr, hcd_devtree_info_t* devtree_info); - -//------------- Event API -------------// - // Called by HCD to notify stack extern void hcd_event_handler(hcd_event_t const* event, bool in_isr); @@ -235,4 +225,4 @@ void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, uint32_t xferred } #endif -#endif /* _TUSB_HCD_H_ */ +#endif diff --git a/src/host/hub.c b/src/host/hub.c index 61efa8ba5..0b172a596 100644 --- a/src/host/hub.c +++ b/src/host/hub.c @@ -57,9 +57,11 @@ typedef struct { TUH_EPBUF_DEF(ctrl_buf, CFG_TUH_HUB_BUFSIZE); } hub_epbuf_t; +static tuh_xfer_cb_t user_complete_cb = NULL; static hub_interface_t hub_itfs[CFG_TUH_HUB]; CFG_TUH_MEM_SECTION static hub_epbuf_t hub_epbufs[CFG_TUH_HUB]; + TU_ATTR_ALWAYS_INLINE static inline hub_interface_t* get_hub_itf(uint8_t daddr) { return &hub_itfs[daddr-1-CFG_TUH_DEVICE_MAX]; } @@ -142,10 +144,23 @@ bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, }; TU_LOG_DRV("HUB Set Feature: %s, addr = %u port = %u\r\n", _hub_feature_str[feature], hub_addr, hub_port); - TU_ASSERT( tuh_control_xfer(&xfer) ); + TU_ASSERT(tuh_control_xfer(&xfer)); return true; } +static void port_get_status_complete (tuh_xfer_t* xfer) { + if (xfer->result == XFER_RESULT_SUCCESS) { + hub_interface_t* p_hub = get_hub_itf(xfer->daddr); + p_hub->port_status = *((const hub_port_status_response_t *) (uintptr_t) xfer->buffer); + } + + xfer->complete_cb = user_complete_cb; + user_complete_cb = NULL; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } +} + bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void* resp, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { tusb_control_request_t const request = { @@ -169,8 +184,25 @@ bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void* resp, .user_data = user_data }; + if (hub_port != 0) { + // intercept complete callback to save port status, ignore resp + hub_epbuf_t* p_epbuf = get_hub_epbuf(hub_addr); + xfer.complete_cb = port_get_status_complete; + xfer.buffer = p_epbuf->ctrl_buf; + user_complete_cb = complete_cb; + } else { + user_complete_cb = NULL; + } + TU_LOG_DRV("HUB Get Port Status: addr = %u port = %u\r\n", hub_addr, hub_port); - TU_VERIFY( tuh_control_xfer(&xfer) ); + TU_VERIFY(tuh_control_xfer(&xfer)); + return true; +} + +bool hub_port_get_status_local(uint8_t hub_addr, uint8_t hub_port, hub_port_status_response_t* resp) { + (void) hub_port; + hub_interface_t* p_hub = get_hub_itf(hub_addr); + *resp = p_hub->port_status; return true; } @@ -238,10 +270,10 @@ 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); -bool hub_set_config(uint8_t dev_addr, uint8_t itf_num) { - hub_interface_t* p_hub = get_hub_itf(dev_addr); +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(dev_addr); + hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); // Get Hub Descriptor tusb_control_request_t const request = { @@ -257,7 +289,7 @@ bool hub_set_config(uint8_t dev_addr, uint8_t itf_num) { }; tuh_xfer_t xfer = { - .daddr = dev_addr, + .daddr = daddr, .ep_addr = 0, .setup = &request, .buffer = p_epbuf->ctrl_buf, @@ -312,11 +344,15 @@ static void config_port_power_complete (tuh_xfer_t* xfer) { //--------------------------------------------------------------------+ // Connection Changes //--------------------------------------------------------------------+ -static void get_status_complete (tuh_xfer_t* xfer); -static void port_get_status_complete (tuh_xfer_t* xfer); -static void port_clear_feature_complete_stub(tuh_xfer_t* xfer); -static void connection_clear_conn_change_complete (tuh_xfer_t* xfer); -static void connection_port_reset_complete (tuh_xfer_t* xfer); +enum { + STATE_IDLE = 0, + STATE_HUB_STATUS, + STATE_CLEAR_CHANGE, + STATE_CHECK_CONN, + STATE_COMPLETE +}; + +static void process_new_status(tuh_xfer_t* xfer); // callback as response of interrupt endpoint polling bool hub_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { @@ -337,12 +373,12 @@ bool hub_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t processed = false; } else if (tu_bit_test(status_change, 0)) { // Hub bit 0 is for the hub device events - processed = hub_get_status(daddr, p_epbuf->ctrl_buf, get_status_complete, 0); + processed = hub_get_status(daddr, p_epbuf->ctrl_buf, process_new_status, STATE_HUB_STATUS); } else { // Hub bits 1 to n are hub port events for (uint8_t port=1; port <= p_hub->bNbrPorts; port++) { if (tu_bit_test(status_change, port)) { - processed = hub_port_get_status(daddr, port, p_epbuf->ctrl_buf, port_get_status_complete, 0); + processed = hub_port_get_status(daddr, port, NULL, process_new_status, STATE_CLEAR_CHANGE); break; // after completely processed one port, we will re-queue the status poll and handle next one } } @@ -358,117 +394,85 @@ bool hub_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t return true; } -static void port_clear_feature_complete_stub(tuh_xfer_t* xfer) { - hub_edpt_status_xfer(xfer->daddr); -} - -static void get_status_complete(tuh_xfer_t *xfer) { +static void process_new_status(tuh_xfer_t* xfer) { const uint8_t daddr = xfer->daddr; - bool processed = false; // true if new status is processed - if (xfer->result == XFER_RESULT_SUCCESS) { - hub_status_response_t hub_status = *((const hub_status_response_t *) (uintptr_t) xfer->buffer); - - TU_LOG_DRV("HUB Got hub status, addr = %u, status = %04x\r\n", daddr, hub_status.change.value); - - if (hub_status.change.local_power_source) { - TU_LOG_DRV(" Local Power Change\r\n"); - processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_LOCAL_POWER_CHANGE, port_clear_feature_complete_stub, 0); - } else if (hub_status.change.over_current) { - TU_LOG_DRV(" Over Current\r\n"); - processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_OVER_CURRENT_CHANGE, port_clear_feature_complete_stub, 0); - } - } - - if (!processed) { - TU_ASSERT(hub_edpt_status_xfer(daddr), ); + if (xfer->result != XFER_RESULT_SUCCESS) { + TU_ASSERT(hub_edpt_status_xfer(daddr),); + return; } -} -static void port_get_status_complete(tuh_xfer_t *xfer) { - const uint8_t daddr = xfer->daddr; + const uint8_t port_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + hub_interface_t *p_hub = get_hub_itf(daddr); + const uintptr_t state = xfer->user_data; bool processed = false; // true if new status is processed - if (xfer->result == XFER_RESULT_SUCCESS) { - const uint8_t port_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - hub_interface_t *p_hub = get_hub_itf(daddr); - p_hub->port_status = *((const hub_port_status_response_t *) (uintptr_t) xfer->buffer); - - // Clear port status change interrupts - if (p_hub->port_status.change.connection) { - // Connection change - // Port is powered and enabled - //TU_VERIFY(port_status.status_current.port_power && port_status.status_current.port_enable, ); - - // Acknowledge Port Connection Change - processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_CONNECTION_CHANGE, connection_clear_conn_change_complete, 0); - } else if (p_hub->port_status.change.port_enable) { - processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_ENABLE_CHANGE, port_clear_feature_complete_stub, 0); - } else if (p_hub->port_status.change.suspend) { - processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_SUSPEND_CHANGE, port_clear_feature_complete_stub, 0); - } else if (p_hub->port_status.change.over_current) { - processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_OVER_CURRENT_CHANGE, port_clear_feature_complete_stub, 0); - } else if (p_hub->port_status.change.reset) { - processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_RESET_CHANGE, port_clear_feature_complete_stub, 0); + switch (state) { + case STATE_HUB_STATUS: { + hub_status_response_t hub_status = *((const hub_status_response_t *) (uintptr_t) xfer->buffer); + TU_LOG_DRV("HUB Got hub status, addr = %u, status = %04x\r\n", daddr, hub_status.change.value); + if (hub_status.change.local_power_source) { + TU_LOG_DRV(" Local Power Change\r\n"); + processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_LOCAL_POWER_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (hub_status.change.over_current) { + TU_LOG_DRV(" Over Current\r\n"); + processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_OVER_CURRENT_CHANGE, + process_new_status, STATE_COMPLETE); + } + break; } - } - if (!processed) { - TU_ASSERT(hub_edpt_status_xfer(daddr), ); - } -} + case STATE_CLEAR_CHANGE: + // Get port status complete --> clear change + if (p_hub->port_status.change.connection) { + // Connection change + // Port is powered and enabled + //TU_VERIFY(port_status.status_current.port_power && port_status.status_current.port_enable, ); -static void connection_clear_conn_change_complete (tuh_xfer_t* xfer) { - const uint8_t daddr = xfer->daddr; + // Acknowledge Port Connection Change + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_CONNECTION_CHANGE, + process_new_status, STATE_CHECK_CONN); + } else if (p_hub->port_status.change.port_enable) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_ENABLE_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.suspend) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_SUSPEND_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.over_current) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_OVER_CURRENT_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.reset) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_RESET_CHANGE, + process_new_status, STATE_COMPLETE); + } + break; - if (xfer->result != XFER_RESULT_SUCCESS) { - TU_ASSERT(hub_edpt_status_xfer(daddr), ); - return; - } + case STATE_CHECK_CONN: { + const hcd_event_t event = { + .rhport = usbh_get_rhport(daddr), + .event_id = p_hub->port_status.status.connection ? HCD_EVENT_DEVICE_ATTACH : HCD_EVENT_DEVICE_REMOVE, + .connection = { + .hub_addr = daddr, + .hub_port = port_num + } + }; + hcd_event_handler(&event, false); + // skip status for attach event, usbh will do it after handled this enumeration + processed = (event.event_id == HCD_EVENT_DEVICE_ATTACH); + break; + } - hub_interface_t *p_hub = get_hub_itf(daddr); - const uint8_t port_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + case STATE_COMPLETE: + default: + processed = false; // complete this status, queue next status + break; - if (p_hub->port_status.status.connection) { - // Reset port if attach event - hub_port_reset(daddr, port_num, connection_port_reset_complete, 0); - } else { - // submit detach event - const hcd_event_t event = { - .rhport = usbh_get_rhport(daddr), - .event_id = HCD_EVENT_DEVICE_REMOVE, - .connection = { - .hub_addr = daddr, - .hub_port = port_num - } - }; - hcd_event_handler(&event, false); } -} - -static void connection_port_reset_complete (tuh_xfer_t* xfer) { - const uint8_t daddr = xfer->daddr; - if (xfer->result != XFER_RESULT_SUCCESS) { - // retry port reset if failed - if (!tuh_control_xfer(xfer)) { - TU_ASSERT(hub_edpt_status_xfer(daddr), ); // back to status poll if failed to queue request - } - return; + if (!processed) { + TU_ASSERT(hub_edpt_status_xfer(daddr),); } - - const uint8_t port_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - - // submit attach event - hcd_event_t event = { - .rhport = usbh_get_rhport(daddr), - .event_id = HCD_EVENT_DEVICE_ATTACH, - .connection = { - .hub_addr = daddr, - .hub_port = port_num - } - }; - hcd_event_handler(&event, false); } #endif diff --git a/src/host/hub.h b/src/host/hub.h index e4e576661..3587f0ee3 100644 --- a/src/host/hub.h +++ b/src/host/hub.h @@ -170,9 +170,13 @@ bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, tuh_xfer_cb_t complete_cb, uintptr_t user_data); // Get port status +// If hub_port != 0, resp is ignored. hub_port_get_status_local() can be used to retrieve the status bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void *resp, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +// Get port status from local cache. This does not send a request to the device +bool hub_port_get_status_local(uint8_t hub_addr, uint8_t hub_port, hub_port_status_response_t* resp); + // Get status from Interrupt endpoint bool hub_edpt_status_xfer(uint8_t daddr); @@ -188,7 +192,7 @@ bool hub_port_clear_reset_change(uint8_t hub_addr, uint8_t hub_port, tuh_xfer_cb return hub_port_clear_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET_CHANGE, complete_cb, user_data); } -// Get Hub status +// Get Hub status (port = 0) TU_ATTR_ALWAYS_INLINE static inline bool hub_get_status(uint8_t hub_addr, void* resp, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return hub_port_get_status(hub_addr, 0, resp, complete_cb, user_data); @@ -205,7 +209,7 @@ bool hub_clear_feature(uint8_t hub_addr, uint8_t feature, tuh_xfer_cb_t complete bool hub_init (void); bool hub_deinit (void); bool hub_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); -bool hub_set_config (uint8_t dev_addr, uint8_t itf_num); +bool hub_set_config (uint8_t daddr, uint8_t itf_num); bool hub_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); void hub_close (uint8_t dev_addr); diff --git a/src/host/usbh.c b/src/host/usbh.c index c87f058cd..d09874d6e 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -28,13 +28,13 @@ #if CFG_TUH_ENABLED -#include "host/hcd.h" +#include "hcd.h" #include "tusb.h" -#include "host/usbh_pvt.h" +#include "usbh_pvt.h" #include "hub.h" //--------------------------------------------------------------------+ -// USBH Configuration +// Configuration //--------------------------------------------------------------------+ #ifndef CFG_TUH_TASK_QUEUE_SZ #define CFG_TUH_TASK_QUEUE_SZ 16 @@ -52,21 +52,25 @@ enum { // Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ TU_ATTR_WEAK bool hcd_deinit(uint8_t rhport) { - (void) rhport; - return false; + (void) rhport; return false; } TU_ATTR_WEAK bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { - (void) rhport; - (void) cfg_id; - (void) cfg_param; + (void) rhport; (void) cfg_id; (void) cfg_param; return false; } +TU_ATTR_WEAK void tuh_enum_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device) { + (void) daddr; (void) desc_device; +} + +TU_ATTR_WEAK bool tuh_enum_descriptor_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config) { + (void) daddr; (void) cfg_index; (void) desc_config; + return true; +} + TU_ATTR_WEAK void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { - (void) rhport; - (void) eventid; - (void) in_isr; + (void) rhport; (void) eventid; (void) in_isr; } TU_ATTR_WEAK bool hcd_dcache_clean(const void* addr, uint32_t data_size) { @@ -85,27 +89,24 @@ TU_ATTR_WEAK bool hcd_dcache_clean_invalidate(const void* addr, uint32_t data_si } //--------------------------------------------------------------------+ -// USBH-HCD common data structure +// Data Structure //--------------------------------------------------------------------+ typedef struct { - // port - uint8_t rhport; - uint8_t hub_addr; - uint8_t hub_port; + tuh_bus_info_t bus_info; - struct TU_ATTR_PACKED { - uint8_t speed : 4; // packed speed to save footprint - volatile uint8_t enumerating : 1; // enumeration is in progress, false if not connected or all interfaces are configured - uint8_t TU_RESERVED : 3; - }; -} usbh_dev0_t; - -typedef struct { - // port, must be same layout as usbh_dev0_t - uint8_t rhport; - uint8_t hub_addr; - uint8_t hub_port; - uint8_t speed; + // Device Descriptor + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; // Device State struct TU_ATTR_PACKED { @@ -113,23 +114,9 @@ typedef struct { volatile uint8_t addressed : 1; // After SET_ADDR volatile uint8_t configured : 1; // After SET_CONFIG and all drivers are configured volatile uint8_t suspended : 1; // Bus suspended - // volatile uint8_t removing : 1; // Physically disconnected, waiting to be processed by usbh }; - // Device Descriptor - uint8_t ep0_size; - - uint16_t vid; - uint16_t pid; - - uint8_t i_manufacturer; - uint8_t i_product; - uint8_t i_serial; - - // Configuration Descriptor - // uint8_t interface_count; // bNumInterfaces alias - // Endpoint & Interface uint8_t itf2drv[CFG_TUH_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) uint8_t ep2drv[CFG_TUH_ENDPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each @@ -146,8 +133,63 @@ typedef struct { } usbh_device_t; +// sum of end device + hub +#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB) + +// all devices excluding zero-address +// hub address start from CFG_TUH_DEVICE_MAX+1 +// TODO: hub can has its own simpler struct to save memory +static usbh_device_t _usbh_devices[TOTAL_DEVICES]; + +// Mutex for claiming endpoint +#if OSAL_MUTEX_REQUIRED +static osal_mutex_def_t _usbh_mutexdef; +static osal_mutex_t _usbh_mutex; +#else +#define _usbh_mutex NULL +#endif + +// Spinlock for interrupt handler +static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set); + +// Event queue: usbh_int_set() is used as mutex in OS NONE config +OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); +static osal_queue_t _usbh_q; + +// 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. +typedef struct { + uint8_t* buffer; + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + + volatile uint8_t stage; + uint8_t daddr; + volatile uint16_t actual_len; + uint8_t failed_count; +} usbh_ctrl_xfer_info_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_data_t; + +static usbh_data_t _usbh_data = { + .controller_id = TUSB_INDEX_INVALID_8, +}; + +typedef struct { + TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); + TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE); +} usbh_epbuf_t; +CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf; + //--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF +// Class Driver //--------------------------------------------------------------------+ #if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL #define DRIVER_NAME(_name) _name @@ -230,11 +272,10 @@ static usbh_class_driver_t const usbh_class_drivers[] = { }; enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(usbh_class_drivers) }; -enum { CONFIG_NUM = 1 }; // default to use configuration 1 // Additional class drivers implemented by application -tu_static usbh_class_driver_t const * _app_driver = NULL; -tu_static uint8_t _app_driver_count = 0; +static usbh_class_driver_t const * _app_driver = NULL; +static uint8_t _app_driver_count = 0; #define TOTAL_DRIVER_COUNT (_app_driver_count + BUILTIN_DRIVER_COUNT) @@ -251,66 +292,21 @@ static inline usbh_class_driver_t const *get_driver(uint8_t drv_id) { } //--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION +// Function Inline and Prototypes //--------------------------------------------------------------------+ +static bool enum_new_device(hcd_event_t* event); +static void process_removed_device(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); +static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); -// sum of end device + hub -#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB) - -static uint8_t _usbh_controller = TUSB_INDEX_INVALID_8; - -// Device with address = 0 for enumeration -static usbh_dev0_t _dev0; - -// all devices excluding zero-address -// hub address start from CFG_TUH_DEVICE_MAX+1 -// TODO: hub can has its own simpler struct to save memory -static usbh_device_t _usbh_devices[TOTAL_DEVICES]; - -// Mutex for claiming endpoint -#if OSAL_MUTEX_REQUIRED - static osal_mutex_def_t _usbh_mutexdef; - static osal_mutex_t _usbh_mutex; -#else - #define _usbh_mutex NULL -#endif - -// Event queue -// usbh_int_set is used as mutex in OS NONE config -OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); -static osal_queue_t _usbh_q; - -// 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. -static struct { - uint8_t* buffer; - tuh_xfer_cb_t complete_cb; - uintptr_t user_data; - - volatile uint8_t stage; - uint8_t daddr; - volatile uint16_t actual_len; - uint8_t failed_count; -} _ctrl_xfer; - -typedef struct { - TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); - TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE); -} usbh_epbuf_t; - -CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf; - -//------------- Helper Function -------------// TU_ATTR_ALWAYS_INLINE static inline usbh_device_t* get_device(uint8_t dev_addr) { TU_VERIFY(dev_addr > 0 && dev_addr <= TOTAL_DEVICES, NULL); return &_usbh_devices[dev_addr-1]; } -static bool enum_new_device(hcd_event_t* event); -static void process_removing_device(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); -static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) { + return (CFG_TUH_HUB > 0) && (daddr > CFG_TUH_DEVICE_MAX); +} TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, bool in_isr) { TU_ASSERT(osal_queue_send(_usbh_q, event, in_isr)); @@ -318,10 +314,40 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, return true; } +TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) { + if (_usbh_data.ctrl_xfer_info.stage != stage) { + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + _usbh_data.ctrl_xfer_info.stage = stage; + (void) osal_mutex_unlock(_usbh_mutex); + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const uint8_t setup_packet[8]) { + const uint8_t rhport = usbh_get_rhport(daddr); + const bool ret = hcd_setup_send(rhport, daddr, setup_packet); + if (!ret) { + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + } + return ret; +} + +TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) { + hcd_device_close(rhport, daddr); + + // abort any ongoing control transfer + if (daddr == _usbh_data.ctrl_xfer_info.daddr) { + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + } + + // invalidate if enumerating + if (daddr == _usbh_data.enumerating_daddr) { + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + } +} + //--------------------------------------------------------------------+ // Device API //--------------------------------------------------------------------+ - bool tuh_mounted(uint8_t dev_addr) { usbh_device_t *dev = get_device(dev_addr); TU_VERIFY(dev); @@ -330,7 +356,7 @@ bool tuh_mounted(uint8_t dev_addr) { bool tuh_connected(uint8_t daddr) { if (daddr == 0) { - return _dev0.enumerating; // dev0 is connected if still enumerating + return _usbh_data.enumerating_daddr == 0; } else { const usbh_device_t* dev = get_device(daddr); return dev && dev->connected; @@ -341,26 +367,49 @@ 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->vid != 0); + TU_VERIFY(dev && dev->addressed && dev->idVendor != 0); - *vid = dev->vid; - *pid = dev->pid; + *vid = dev->idVendor; + *pid = dev->idProduct; return true; } -tusb_speed_t tuh_speed_get(uint8_t dev_addr) { - usbh_device_t *dev = get_device(dev_addr); - return (tusb_speed_t) (dev ? get_device(dev_addr)->speed : _dev0.speed); +bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_device) { + usbh_device_t *dev = get_device(daddr); + TU_VERIFY(dev && desc_device); + + 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; + + return true; +} + +tusb_speed_t tuh_speed_get(uint8_t daddr) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(daddr, &bus_info); + return (tusb_speed_t)bus_info.speed; } bool tuh_rhport_is_active(uint8_t rhport) { - return _usbh_controller == rhport; + return _usbh_data.controller_id == rhport; } bool tuh_rhport_reset_bus(uint8_t rhport, bool active) { TU_VERIFY(tuh_rhport_is_active(rhport)); - if ( active ) { + if (active) { hcd_port_reset(rhport); } else { hcd_port_reset_end(rhport); @@ -371,7 +420,6 @@ bool tuh_rhport_reset_bus(uint8_t rhport, bool active) { //--------------------------------------------------------------------+ // PUBLIC API (Parameter Verification is required) //--------------------------------------------------------------------+ - bool tuh_configure(uint8_t rhport, uint32_t cfg_id, const void *cfg_param) { return hcd_configure(rhport, cfg_id, cfg_param); } @@ -383,26 +431,45 @@ static void clear_device(usbh_device_t* dev) { } bool tuh_inited(void) { - return _usbh_controller != TUSB_INDEX_INVALID_8; + return _usbh_data.controller_id != TUSB_INDEX_INVALID_8; } bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { if (tuh_rhport_is_active(rhport)) { return true; // skip if already initialized } - - TU_LOG_USBH("USBH init on controller %u, speed = %s\r\n", rhport, - rh_init->speed == TUSB_SPEED_HIGH ? "High" : "Full"); +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL + char const* speed_str = 0; + switch (rh_init->speed) { + case TUSB_SPEED_HIGH: + speed_str = "High"; + break; + case TUSB_SPEED_FULL: + speed_str = "Full"; + break; + case TUSB_SPEED_LOW: + speed_str = "Low"; + break; + case TUSB_SPEED_AUTO: + speed_str = "Auto"; + break; + default: + break; + } + TU_LOG_USBH("USBH init on controller %u, speed = %s\r\n", rhport, speed_str); +#endif // Init host stack if not already if (!tuh_inited()) { + TU_LOG_INT_USBH(sizeof(usbh_data_t)); TU_LOG_INT_USBH(sizeof(usbh_device_t)); TU_LOG_INT_USBH(sizeof(hcd_event_t)); - TU_LOG_INT_USBH(sizeof(_ctrl_xfer)); TU_LOG_INT_USBH(sizeof(tuh_xfer_t)); TU_LOG_INT_USBH(sizeof(tu_fifo_t)); TU_LOG_INT_USBH(sizeof(tu_edpt_stream_t)); + osal_spin_init(&_usbh_spin); + // Event queue _usbh_q = osal_queue_create(&_usbh_qdef); TU_ASSERT(_usbh_q != NULL); @@ -419,9 +486,11 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } // Device - tu_memclr(&_dev0, sizeof(_dev0)); tu_memclr(_usbh_devices, sizeof(_usbh_devices)); - tu_memclr(&_ctrl_xfer, sizeof(_ctrl_xfer)); + tu_memclr(&_usbh_data, sizeof(_usbh_data)); + + _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; for (uint8_t i = 0; i < TOTAL_DEVICES; i++) { clear_device(&_usbh_devices[i]); @@ -438,7 +507,7 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } // Init host controller - _usbh_controller = rhport; + _usbh_data.controller_id = rhport; TU_ASSERT(hcd_init(rhport, rh_init)); hcd_int_enable(rhport); @@ -453,10 +522,10 @@ bool tuh_deinit(uint8_t rhport) { // deinit host controller hcd_int_disable(rhport); hcd_deinit(rhport); - _usbh_controller = TUSB_INDEX_INVALID_8; + _usbh_data.controller_id = TUSB_INDEX_INVALID_8; // "unplug" all devices on this rhport (hub_addr = 0, hub_port = 0) - process_removing_device(rhport, 0, 0); + process_removed_device(rhport, 0, 0); // deinit host stack if no controller is active if (!tuh_inited()) { @@ -516,50 +585,39 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { // Loop until there is no more events in the queue while (1) { hcd_event_t event; - if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) return; + if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { return; } switch (event.event_id) { case HCD_EVENT_DEVICE_ATTACH: - // due to the shared control buffer, we must complete enumerating one device before enumerating another one. + // 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 (_dev0.enumerating) { - // Some device can cause multiple duplicated attach events - // drop current enumerating and start over for a proper port reset - if (event.rhport == _dev0.rhport && event.connection.hub_addr == _dev0.hub_addr && - event.connection.hub_port == _dev0.hub_port) { - // abort/cancel current enumeration and start new one - TU_LOG1("[%u:] USBH Device Attach (duplicated)\r\n", event.rhport); - tuh_edpt_abort_xfer(0, 0); - enum_new_device(&event); - } else { - TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport); - - bool is_empty = osal_queue_empty(_usbh_q); - queue_event(&event, in_isr); - - if (is_empty) { - // Exit if this is the only event in the queue, otherwise we may loop forever - return; - } - } - } else { - TU_LOG1("[%u:] USBH Device Attach\r\n", event.rhport); - _dev0.enumerating = 1; + 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 + 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 + } } 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); - process_removing_device(event.rhport, event.connection.hub_addr, event.connection.hub_port); - - #if CFG_TUH_HUB - // TODO remove - if (event.connection.hub_addr != 0 && event.connection.hub_port != 0) { - // done with hub, waiting for next data on status pipe - (void) hub_edpt_status_xfer(event.connection.hub_addr); + if (_usbh_data.enumerating_daddr == 0 && + event.rhport == _usbh_data.dev0_bus.rhport && + event.connection.hub_addr == _usbh_data.dev0_bus.hub_addr && + event.connection.hub_port == _usbh_data.dev0_bus.hub_port) { + // dev0 is unplugged while enumerating (not yet assigned an address) + usbh_device_close(_usbh_data.dev0_bus.rhport, 0); + } else { + process_removed_device(event.rhport, event.connection.hub_addr, event.connection.hub_port); } - #endif break; case HCD_EVENT_XFER_COMPLETE: { @@ -567,7 +625,8 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr); - TU_LOG_USBH("on EP %02X with %u bytes: %s\r\n", ep_addr, (unsigned int) event.xfer_complete.len, tu_str_xfer_result[event.xfer_complete.result]); + TU_LOG_USBH("[:%u] on EP %02X with %u bytes: %s\r\n", + event.dev_addr, ep_addr, (unsigned int) event.xfer_complete.len, tu_str_xfer_result[event.xfer_complete.result]); if (event.dev_addr == 0) { // device 0 only has control endpoint @@ -592,7 +651,7 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { tuh_xfer_t xfer = { .daddr = event.dev_addr, .ep_addr = ep_addr, - .result = event.xfer_complete.result, + .result = (xfer_result_t)event.xfer_complete.result, .actual_len = event.xfer_complete.len, .buflen = 0, // not available .buffer = NULL, // not available @@ -606,7 +665,7 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { uint8_t drv_id = dev->ep2drv[epnum][ep_dir]; usbh_class_driver_t const* driver = get_driver(drv_id); if (driver) { - TU_LOG_USBH("%s xfer callback\r\n", driver->name); + TU_LOG_USBH(" %s xfer callback\r\n", driver->name); driver->xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); } else { @@ -647,47 +706,44 @@ static void _control_blocking_complete_cb(tuh_xfer_t* xfer) { bool tuh_control_xfer (tuh_xfer_t* xfer) { TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet const uint8_t daddr = xfer->daddr; - TU_VERIFY(tuh_connected(daddr)); // Check if device is still connected (enumerating for dev0) + TU_VERIFY(tuh_connected(daddr)); - // pre-check to help reducing mutex lock - TU_VERIFY(_ctrl_xfer.stage == CONTROL_STAGE_IDLE); - (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; - bool const is_idle = (_ctrl_xfer.stage == CONTROL_STAGE_IDLE); + TU_VERIFY(ctrl_info->stage == CONTROL_STAGE_IDLE); // pre-check to help reducing mutex lock + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + bool const is_idle = (ctrl_info->stage == CONTROL_STAGE_IDLE); if (is_idle) { - _ctrl_xfer.stage = CONTROL_STAGE_SETUP; - _ctrl_xfer.daddr = daddr; - _ctrl_xfer.actual_len = 0; - _ctrl_xfer.failed_count = 0; + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->daddr = daddr; + ctrl_info->actual_len = 0; + ctrl_info->failed_count = 0; - _ctrl_xfer.buffer = xfer->buffer; - _ctrl_xfer.complete_cb = xfer->complete_cb; - _ctrl_xfer.user_data = xfer->user_data; + ctrl_info->buffer = xfer->buffer; + ctrl_info->complete_cb = xfer->complete_cb; + ctrl_info->user_data = xfer->user_data; _usbh_epbuf.request = (*xfer->setup); } - (void) osal_mutex_unlock(_usbh_mutex); TU_VERIFY(is_idle); - const uint8_t rhport = usbh_get_rhport(daddr); - - TU_LOG_USBH("[%u:%u] %s: ", rhport, daddr, + TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(daddr), daddr, (xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ? tu_str_std_request[xfer->setup->bRequest] : "Class Request"); TU_LOG_BUF_USBH(xfer->setup, 8); if (xfer->complete_cb) { - TU_ASSERT(hcd_setup_send(rhport, daddr, (uint8_t const *) &_usbh_epbuf.request)); + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); }else { // blocking if complete callback is not provided // change callback to internal blocking, and result as user argument volatile xfer_result_t result = XFER_RESULT_INVALID; // use user_data to point to xfer_result_t - _ctrl_xfer.user_data = (uintptr_t) &result; - _ctrl_xfer.complete_cb = _control_blocking_complete_cb; + ctrl_info->user_data = (uintptr_t) &result; + ctrl_info->complete_cb = _control_blocking_complete_cb; - TU_ASSERT(hcd_setup_send(rhport, daddr, (uint8_t *) &_usbh_epbuf.request)); + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); while (result == XFER_RESULT_INVALID) { // Note: this can be called within an callback ie. part of tuh_task() @@ -703,20 +759,15 @@ bool tuh_control_xfer (tuh_xfer_t* xfer) { *((xfer_result_t*) xfer->user_data) = result; } xfer->result = result; - xfer->actual_len = _ctrl_xfer.actual_len; + xfer->actual_len = ctrl_info->actual_len; } return true; } -TU_ATTR_ALWAYS_INLINE static inline void _set_control_xfer_stage(uint8_t stage) { - (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); - _ctrl_xfer.stage = stage; - (void) osal_mutex_unlock(_usbh_mutex); -} - static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { TU_LOG_USBH("\r\n"); + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; // duplicate xfer since user can execute control transfer within callback tusb_control_request_t const request = _usbh_epbuf.request; @@ -725,13 +776,13 @@ static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { .ep_addr = 0, .result = result, .setup = &request, - .actual_len = (uint32_t) _ctrl_xfer.actual_len, - .buffer = _ctrl_xfer.buffer, - .complete_cb = _ctrl_xfer.complete_cb, - .user_data = _ctrl_xfer.user_data + .actual_len = (uint32_t) ctrl_info->actual_len, + .buffer = ctrl_info->buffer, + .complete_cb = ctrl_info->complete_cb, + .user_data = ctrl_info->user_data }; - _set_control_xfer_stage(CONTROL_STAGE_IDLE); + _control_set_xfer_stage(CONTROL_STAGE_IDLE); if (xfer_temp.complete_cb) { xfer_temp.complete_cb(&xfer_temp); @@ -743,6 +794,7 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t const uint8_t rhport = usbh_get_rhport(daddr); tusb_control_request_t const * request = &_usbh_epbuf.request; + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; switch (result) { case XFER_RESULT_STALLED: @@ -752,15 +804,15 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t break; case XFER_RESULT_FAILED: - if (tuh_connected(daddr) && _ctrl_xfer.failed_count < USBH_CONTROL_RETRY_MAX) { - TU_LOG_USBH("[%u:%u] Control FAILED %u/%u, retrying\r\n", rhport, daddr, _ctrl_xfer.failed_count+1, USBH_CONTROL_RETRY_MAX); + if (tuh_connected(daddr) && ctrl_info->failed_count < USBH_CONTROL_RETRY_MAX) { + TU_LOG_USBH("[%u:%u] Control FAILED %u/%u, retrying\r\n", rhport, daddr, ctrl_info->failed_count+1, USBH_CONTROL_RETRY_MAX); (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); - _ctrl_xfer.stage = CONTROL_STAGE_SETUP; - _ctrl_xfer.failed_count++; - _ctrl_xfer.actual_len = 0; // reset actual_len + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->failed_count++; + ctrl_info->actual_len = 0; // reset actual_len (void) osal_mutex_unlock(_usbh_mutex); - TU_ASSERT(hcd_setup_send(rhport, daddr, (uint8_t const *) request)); + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) request)); } else { TU_LOG_USBH("[%u:%u] Control FAILED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); TU_LOG_BUF_USBH(request, 8); @@ -769,28 +821,30 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t break; case XFER_RESULT_SUCCESS: - switch(_ctrl_xfer.stage) { + switch(ctrl_info->stage) { case CONTROL_STAGE_SETUP: if (request->wLength) { // DATA stage: initial data toggle is always 1 - _set_control_xfer_stage(CONTROL_STAGE_DATA); - TU_ASSERT( hcd_edpt_xfer(rhport, daddr, tu_edpt_addr(0, request->bmRequestType_bit.direction), _ctrl_xfer.buffer, request->wLength) ); + _control_set_xfer_stage(CONTROL_STAGE_DATA); + const uint8_t ep_data = tu_edpt_addr(0, request->bmRequestType_bit.direction); + TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_data, ctrl_info->buffer, request->wLength)); return true; } - TU_ATTR_FALLTHROUGH; - - case CONTROL_STAGE_DATA: - if (request->wLength) { - TU_LOG_USBH("[%u:%u] Control data:\r\n", rhport, daddr); - TU_LOG_MEM_USBH(_ctrl_xfer.buffer, xferred_bytes, 2); - } + TU_ATTR_FALLTHROUGH; - _ctrl_xfer.actual_len = (uint16_t) xferred_bytes; + case CONTROL_STAGE_DATA: { + if (request->wLength) { + TU_LOG_USBH("[%u:%u] Control data:\r\n", rhport, daddr); + TU_LOG_MEM_USBH(ctrl_info->buffer, xferred_bytes, 2); + } + ctrl_info->actual_len = (uint16_t) xferred_bytes; - // ACK stage: toggle is always 1 - _set_control_xfer_stage(CONTROL_STAGE_ACK); - TU_ASSERT( hcd_edpt_xfer(rhport, daddr, tu_edpt_addr(0, 1 - request->bmRequestType_bit.direction), NULL, 0) ); - break; + // ACK stage: toggle is always 1 + _control_set_xfer_stage(CONTROL_STAGE_ACK); + const uint8_t ep_status = tu_edpt_addr(0, 1 - request->bmRequestType_bit.direction); + TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_status, NULL, 0)); + break; + } case CONTROL_STAGE_ACK: { // Abort all pending transfers if SET_CONFIGURATION request @@ -839,7 +893,6 @@ bool tuh_edpt_xfer(tuh_xfer_t* xfer) { bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { TU_LOG_USBH("[%u] Aborted transfer on EP %02X\r\n", daddr, ep_addr); - const uint8_t epnum = tu_edpt_number(ep_addr); const uint8_t dir = tu_edpt_dir(ep_addr); @@ -848,17 +901,17 @@ bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { const uint8_t rhport = usbh_get_rhport(daddr); // control transfer: only 1 control at a time, check if we are aborting the current one - TU_VERIFY(daddr == _ctrl_xfer.daddr && _ctrl_xfer.stage != CONTROL_STAGE_IDLE); + const usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + TU_VERIFY(daddr == ctrl_info->daddr && ctrl_info->stage != CONTROL_STAGE_IDLE); hcd_edpt_abort_xfer(rhport, daddr, ep_addr); - _set_control_xfer_stage(CONTROL_STAGE_IDLE); // reset control transfer state to idle + _control_set_xfer_stage(CONTROL_STAGE_IDLE); // reset control transfer state to idle } else { usbh_device_t* dev = get_device(daddr); TU_VERIFY(dev); TU_VERIFY(dev->ep_status[epnum][dir].busy); // non-control skip if not busy - hcd_edpt_abort_xfer(dev->rhport, daddr, ep_addr); - - // mark as ready and release endpoint if transfer is aborted + // abort then mark as ready and release endpoint + hcd_edpt_abort_xfer(dev->bus_info.rhport, daddr, ep_addr); dev->ep_status[epnum][dir].busy = false; tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex); } @@ -870,9 +923,10 @@ bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { // USBH API For Class Driver //--------------------------------------------------------------------+ -uint8_t usbh_get_rhport(uint8_t dev_addr) { - usbh_device_t *dev = get_device(dev_addr); - return dev ? dev->rhport : _dev0.rhport; +uint8_t usbh_get_rhport(uint8_t daddr) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(daddr, &bus_info); + return bus_info.rhport; } uint8_t *usbh_get_enum_buf(void) { @@ -882,18 +936,25 @@ uint8_t *usbh_get_enum_buf(void) { void usbh_int_set(bool enabled) { // TODO all host controller if multiple are used since they shared the same event queue if (enabled) { - hcd_int_enable(_usbh_controller); + hcd_int_enable(_usbh_data.controller_id); } else { - hcd_int_disable(_usbh_controller); + hcd_int_disable(_usbh_data.controller_id); } } +void usbh_spin_lock(bool in_isr) { + osal_spin_lock(&_usbh_spin, in_isr); +} + +void usbh_spin_unlock(bool in_isr) { + osal_spin_unlock(&_usbh_spin, in_isr); +} + void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr) { hcd_event_t event = { 0 }; event.event_id = USBH_EVENT_FUNC_CALL; event.func_call.func = func; event.func_call.param = param; - queue_event(&event, in_isr); } @@ -958,7 +1019,7 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* bu dev->ep_callback[epnum][dir].user_data = user_data; #endif - if (hcd_edpt_xfer(dev->rhport, dev_addr, ep_addr, buffer, total_bytes)) { + if (hcd_edpt_xfer(dev->bus_info.rhport, dev_addr, ep_addr, buffer, total_bytes)) { TU_LOG_USBH("OK\r\n"); return true; } else { @@ -990,6 +1051,12 @@ bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const* desc_ep) { return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, desc_ep); } +bool tuh_edpt_close(uint8_t daddr, uint8_t ep_addr) { + TU_VERIFY(0 != tu_edpt_number(ep_addr)); // cannot close EP0 + tuh_edpt_abort_xfer(daddr, ep_addr); // abort any pending transfer + return hcd_edpt_close(usbh_get_rhport(daddr), daddr, ep_addr); +} + bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) { usbh_device_t* dev = get_device(dev_addr); TU_VERIFY(dev); @@ -1004,31 +1071,30 @@ bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) { // HCD Event Handler //--------------------------------------------------------------------+ -void hcd_devtree_get_info(uint8_t dev_addr, hcd_devtree_info_t* devtree_info) { - usbh_device_t const* dev = get_device(dev_addr); +bool tuh_bus_info_get(uint8_t daddr, tuh_bus_info_t* bus_info) { + usbh_device_t const* dev = get_device(daddr); if (dev) { - devtree_info->rhport = dev->rhport; - devtree_info->hub_addr = dev->hub_addr; - devtree_info->hub_port = dev->hub_port; - devtree_info->speed = dev->speed; + *bus_info = dev->bus_info; } else { - devtree_info->rhport = _dev0.rhport; - devtree_info->hub_addr = _dev0.hub_addr; - devtree_info->hub_port = _dev0.hub_port; - devtree_info->speed = _dev0.speed; + *bus_info = _usbh_data.dev0_bus; } + return true; } TU_ATTR_FAST_FUNC void hcd_event_handler(hcd_event_t const* event, bool in_isr) { switch (event->event_id) { + case HCD_EVENT_DEVICE_ATTACH: case HCD_EVENT_DEVICE_REMOVE: - // FIXME device remove from a hub need an HCD API for hcd to free up endpoint - // mark device as removing to prevent further xfer before the event is processed in usbh task + // Attach debouncing on roothub: skip attach/remove while debouncing delay + if (event->connection.hub_addr == 0) { + if (tu_bit_test(_usbh_data.attach_debouncing_bm, event->rhport)) { + return; + } - // Check if dev0 is removed - if ((event->rhport == _dev0.rhport) && (event->connection.hub_addr == _dev0.hub_addr) && - (event->connection.hub_port == _dev0.hub_port)) { - _dev0.enumerating = 0; + if (event->event_id == HCD_EVENT_DEVICE_ATTACH) { + // No debouncing, set flag if attach event + _usbh_data.attach_debouncing_bm |= TU_BIT(event->rhport); + } } break; @@ -1044,8 +1110,9 @@ TU_ATTR_FAST_FUNC void hcd_event_handler(hcd_event_t const* event, bool in_isr) // generic helper to get a descriptor // if blocking, user_data is pointed to xfer_result -static bool _get_descriptor(uint8_t daddr, uint8_t type, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +TU_ATTR_ALWAYS_INLINE static inline +bool _get_descriptor(uint8_t daddr, uint8_t type, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { tusb_control_request_t const request = { .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, @@ -1086,7 +1153,6 @@ bool tuh_descriptor_get_configuration(uint8_t daddr, uint8_t index, void* buffer } //------------- String Descriptor -------------// - bool tuh_descriptor_get_string(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return _get_descriptor(daddr, TUSB_DESC_STRING, index, language_id, buffer, len, complete_cb, user_data); @@ -1097,24 +1163,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->i_manufacturer); - return tuh_descriptor_get_string(daddr, dev->i_manufacturer, language_id, buffer, len, complete_cb, user_data); + TU_VERIFY(dev && dev->iManufacturer); + return tuh_descriptor_get_string(daddr, dev->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->i_product); - return tuh_descriptor_get_string(daddr, dev->i_product, language_id, buffer, len, complete_cb, user_data); + TU_VERIFY(dev && dev->iProduct); + return tuh_descriptor_get_string(daddr, dev->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->i_serial); - return tuh_descriptor_get_string(daddr, dev->i_serial, language_id, buffer, len, complete_cb, user_data); + TU_VERIFY(dev && dev->iSerialNumber); + return tuh_descriptor_get_string(daddr, dev->iSerialNumber, language_id, buffer, len, complete_cb, user_data); } // Get HID report descriptor @@ -1145,6 +1211,33 @@ bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_ return tuh_control_xfer(&xfer); } +bool tuh_address_set(uint8_t daddr, uint8_t new_addr, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("Set Address = %d\r\n", new_addr); + const tusb_control_request_t request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_ADDRESS, + .wValue = tu_htole16(new_addr), + .wIndex = 0, + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + bool tuh_configuration_set(uint8_t daddr, uint8_t config_num, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { TU_LOG_USBH("Set Configuration = %d\r\n", config_num); @@ -1198,101 +1291,33 @@ bool tuh_interface_set(uint8_t daddr, uint8_t itf_num, uint8_t itf_alt, } //--------------------------------------------------------------------+ -// Descriptor Sync -//--------------------------------------------------------------------+ - -#define _CONTROL_SYNC_API(_async_func, ...) \ - xfer_result_t result = XFER_RESULT_INVALID;\ - TU_VERIFY(_async_func(__VA_ARGS__, NULL, (uintptr_t) &result), XFER_RESULT_TIMEOUT); \ - return (uint8_t) result - -uint8_t tuh_descriptor_get_sync(uint8_t daddr, uint8_t type, uint8_t index, - void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get, daddr, type, index, buffer, len); -} - -uint8_t tuh_descriptor_get_device_sync(uint8_t daddr, void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get_device, daddr, buffer, len); -} - -uint8_t tuh_descriptor_get_configuration_sync(uint8_t daddr, uint8_t index, - void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get_configuration, daddr, index, buffer, len); -} - -uint8_t tuh_descriptor_get_hid_report_sync(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, - void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get_hid_report, daddr, itf_num, desc_type, index, buffer, len); -} - -uint8_t tuh_descriptor_get_string_sync(uint8_t daddr, uint8_t index, uint16_t language_id, - void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get_string, daddr, index, language_id, buffer, len); -} - -uint8_t tuh_descriptor_get_manufacturer_string_sync(uint8_t daddr, uint16_t language_id, - void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get_manufacturer_string, daddr, language_id, buffer, len); -} - -uint8_t tuh_descriptor_get_product_string_sync(uint8_t daddr, uint16_t language_id, - void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get_product_string, daddr, language_id, buffer, len); -} - -uint8_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_id, - void* buffer, uint16_t len) { - _CONTROL_SYNC_API(tuh_descriptor_get_serial_string, daddr, language_id, buffer, len); -} - -//--------------------------------------------------------------------+ // Detaching //--------------------------------------------------------------------+ - -TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) { - return (CFG_TUH_HUB > 0) && (daddr > CFG_TUH_DEVICE_MAX); -} - -//static void mark_removing_device_isr(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) { -// for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) { -// usbh_device_t *dev = &_usbh_devices[dev_id]; -// uint8_t const daddr = dev_id + 1; -// -// // hub_addr = 0 means roothub, hub_port = 0 means all devices of downstream hub -// if (dev->rhport == rhport && dev->connected && -// (hub_addr == 0 || dev->hub_addr == hub_addr) && -// (hub_port == 0 || dev->hub_port == hub_port)) { -// if (is_hub_addr(daddr)) { -// // If the device itself is a usb hub, mark all downstream devices. -// // FIXME recursive calls -// mark_removing_device_isr(rhport, daddr, 0); -// } -// -// dev->removing = 1; -// } -// } -//} - // a device unplugged from rhport:hub_addr:hub_port -static void process_removing_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) { - //------------- find the all devices (star-network) under port that is unplugged -------------// - // TODO mark as disconnected in ISR, also handle dev0 - uint32_t removing_hubs = 0; +static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) { + // Find the all devices (star-network) under port that is unplugged + #if CFG_TUH_HUB + uint8_t removing_hubs[CFG_TUH_HUB] = { 0 }; + #endif + do { for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) { usbh_device_t* dev = &_usbh_devices[dev_id]; uint8_t const daddr = dev_id + 1; // hub_addr = 0 means roothub, hub_port = 0 means all devices of downstream hub - if (dev->rhport == rhport && dev->connected && - (hub_addr == 0 || dev->hub_addr == hub_addr) && - (hub_port == 0 || dev->hub_port == hub_port)) { + if (dev->bus_info.rhport == rhport && dev->connected && + (hub_addr == 0 || dev->bus_info.hub_addr == hub_addr) && + (hub_port == 0 || dev->bus_info.hub_port == hub_port)) { TU_LOG_USBH("[%u:%u:%u] unplugged address = %u\r\n", rhport, hub_addr, hub_port, daddr); + #if CFG_TUH_HUB if (is_hub_addr(daddr)) { TU_LOG_USBH(" is a HUB device %u\r\n", daddr); - removing_hubs |= TU_BIT(dev_id - CFG_TUH_DEVICE_MAX); - } else { + removing_hubs[dev_id - CFG_TUH_DEVICE_MAX] = 1; + } else + #endif + { // Invoke callback before closing driver (maybe call it later ?) if (tuh_umount_cb) { tuh_umount_cb(daddr); @@ -1307,22 +1332,21 @@ static void process_removing_device(uint8_t rhport, uint8_t hub_addr, uint8_t hu } } - hcd_device_close(rhport, daddr); + usbh_device_close(rhport, daddr); clear_device(dev); - - // abort on-going control xfer on this device if any - if (_ctrl_xfer.daddr == daddr) _set_control_xfer_stage(CONTROL_STAGE_IDLE); } } - // if removing a hub, we need to remove its downstream devices - #if CFG_TUH_HUB - if (removing_hubs == 0) break; +#if CFG_TUH_HUB + // if a hub is removed, we need to remove all of its downstream devices + if (tu_mem_is_zero(removing_hubs, CFG_TUH_HUB)) { + break; + } // find a marked hub to process for (uint8_t h_id = 0; h_id < CFG_TUH_HUB; h_id++) { - if (tu_bit_test(removing_hubs, h_id)) { - removing_hubs &= ~TU_BIT(h_id); + if (removing_hubs[h_id]) { + removing_hubs[h_id] = 0; // update hub_addr and hub_port for next loop hub_addr = h_id + 1 + CFG_TUH_DEVICE_MAX; @@ -1330,41 +1354,44 @@ static void process_removing_device(uint8_t rhport, uint8_t hub_addr, uint8_t hu break; } } - #else - (void) removing_hubs; +#else break; - #endif +#endif + } while(1); } //--------------------------------------------------------------------+ // Enumeration Process -// is a lengthy process with a series of control transfer to configure -// newly attached device. +// is a lengthy process with a series of control transfer to configure newly attached device. // NOTE: due to the shared control buffer, we must complete enumerating // one device before enumerating another one. //--------------------------------------------------------------------+ - -enum { - ENUM_RESET_DELAY_MS = 50, // USB specs: 10 to 50ms - ENUM_DEBOUNCING_DELAY_MS = 450, // when plug/unplug a device, physical connection can be bouncing and may - // generate a series of attach/detach event. This delay wait for stable connection +enum { // USB 2.0 specs 7.1.7 for timing + ENUM_DEBOUNCING_DELAY_MS = 150, // T(ATTDB) minimum 100 ms for stable connection + ENUM_RESET_ROOT_DELAY_MS = 50, // T(DRSTr) minimum 50 ms for reset from root port + ENUM_RESET_HUB_DELAY_MS = 20, // T(DRST) 10-20 ms for hub reset + ENUM_RESET_RECOVERY_DELAY_MS = 10, // T(RSTRCY) minimum 10 ms for reset recovery + ENUM_SET_ADDRESS_RECOVERY_DELAY_MS = 2, // USB 2.0 Spec 9.2.6.3 min is 2 ms }; enum { ENUM_IDLE, - ENUM_RESET_1, // 1st reset when attached - //ENUM_HUB_GET_STATUS_1, - ENUM_HUB_CLEAR_RESET_1, + ENUM_HUB_RERSET, + ENUM_HUB_GET_STATUS_AFTER_RESET, + ENUM_HUB_CLEAR_RESET, + ENUM_HUB_CLEAR_RESET_COMPLETE, + ENUM_ADDR0_DEVICE_DESC, - ENUM_RESET_2, // 2nd reset before set address (not used) - ENUM_HUB_GET_STATUS_2, - ENUM_HUB_CLEAR_RESET_2, ENUM_SET_ADDR, ENUM_GET_DEVICE_DESC, + ENUM_GET_STRING_LANGUAGE_ID_LEN, ENUM_GET_STRING_LANGUAGE_ID, + ENUM_GET_STRING_MANUFACTURER_LEN, ENUM_GET_STRING_MANUFACTURER, + ENUM_GET_STRING_PRODUCT_LEN, ENUM_GET_STRING_PRODUCT, + ENUM_GET_STRING_SERIAL_LEN, ENUM_GET_STRING_SERIAL, ENUM_GET_9BYTE_CONFIG_DESC, ENUM_GET_FULL_CONFIG_DESC, @@ -1372,9 +1399,69 @@ enum { ENUM_CONFIG_DRIVER }; -static bool enum_request_set_addr(void); -static bool _parse_configuration_descriptor (uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg); +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(void); +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; + + // wait until device connection is stable TODO non blocking + tusb_time_delay_ms_api(ENUM_DEBOUNCING_DELAY_MS); + + // clear roothub debouncing delay + if (dev0_bus->hub_addr == 0) { + _usbh_data.attach_debouncing_bm &= (uint8_t) ~TU_BIT(dev0_bus->rhport); + } + + 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 + + if (!hcd_port_connect_status(dev0_bus->rhport)) { + TU_LOG_USBH("Device unplugged while debouncing\r\n"); + enum_full_complete(); + return true; + } + + // 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); + + if (!hcd_port_connect_status(dev0_bus->rhport)) { + // device unplugged while delaying + enum_full_complete(); + 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]); + + // 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)); + } + #endif // hub + + return true; +} // process device enumeration static void process_enumeration(tuh_xfer_t* xfer) { @@ -1388,17 +1475,16 @@ static void process_enumeration(tuh_xfer_t* xfer) { // retry if not reaching max attempt failed_count++; - bool retry = _dev0.enumerating && (failed_count < ATTEMPT_COUNT_MAX); + 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_LOG1("Enumeration attempt %u/%u\r\n", failed_count+1, ATTEMPT_COUNT_MAX); + 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(); // complete as failed } - return; } failed_count = 0; @@ -1406,166 +1492,218 @@ static void process_enumeration(tuh_xfer_t* xfer) { 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; + if (daddr > 0) { + TU_ASSERT(dev,); + } uint16_t langid = 0x0409; // default is English switch (state) { #if CFG_TUH_HUB - //case ENUM_HUB_GET_STATUS_1: break; - - case ENUM_HUB_CLEAR_RESET_1: { + case ENUM_HUB_RERSET: { hub_port_status_response_t port_status; - memcpy(&port_status, _usbh_epbuf.ctrl, sizeof(hub_port_status_response_t)); + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); if (!port_status.status.connection) { - // device unplugged while delaying, nothing else to do + TU_LOG_USBH("Device unplugged from hub while debouncing\r\n"); enum_full_complete(); return; } - _dev0.speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH : - (port_status.status.low_speed) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; - - // Acknowledge Port Reset Change - if (port_status.change.reset) { - hub_port_clear_reset_change(_dev0.hub_addr, _dev0.hub_port, - process_enumeration, ENUM_ADDR0_DEVICE_DESC); - } + 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_2: - tusb_time_delay_ms_api(ENUM_RESET_DELAY_MS); - TU_ASSERT(hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_epbuf.ctrl, - process_enumeration, ENUM_HUB_CLEAR_RESET_2),); + 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),); break; + } - case ENUM_HUB_CLEAR_RESET_2: { + case ENUM_HUB_CLEAR_RESET: { hub_port_status_response_t port_status; - memcpy(&port_status, _usbh_epbuf.ctrl, sizeof(hub_port_status_response_t)); + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); - // Acknowledge Port Reset Change if Reset Successful if (port_status.change.reset) { - TU_ASSERT(hub_port_clear_reset_change(_dev0.hub_addr, _dev0.hub_port, - process_enumeration, ENUM_SET_ADDR),); + // 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),); + } 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),); } + break; } + + case ENUM_HUB_CLEAR_RESET_COMPLETE: { + hub_port_status_response_t port_status; + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); + + if (!port_status.status.connection) { + TU_LOG_USBH("Device unplugged from hub (not addressed yet)\r\n"); + enum_full_complete(); + return; + } + + 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; TU_ASSERT(usbh_edpt_control_open(addr0, 8),); - // Get first 8 bytes of device descriptor for Control Endpoint size + // 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),); break; } -#if 0 - case ENUM_RESET_2: - // TODO not used by now, but may be needed for some devices !? - // Reset device again before Set Address - TU_LOG_USBH("Port reset2 \r\n"); - if (_dev0.hub_addr == 0) { - // connected directly to roothub - hcd_port_reset( _dev0.rhport ); - tusb_time_delay_ms_api(RESET_DELAY); // TODO may not work for no-OS on MCU that require reset_end() since - // sof of controller may not running while resetting - hcd_port_reset_end(_dev0.rhport); - // TODO: fall through to SET ADDRESS, refactor later - } -#if CFG_TUH_HUB - else { - // after RESET_DELAY the hub_port_reset() already complete - TU_ASSERT( hub_port_reset(_dev0.hub_addr, _dev0.hub_port, - process_enumeration, ENUM_HUB_GET_STATUS_2), ); - break; - } -#endif - TU_ATTR_FALLTHROUGH; -#endif + case ENUM_SET_ADDR: { + // Due to physical debouncing, some devices can cause multiple attaches (actually reset) without detach event + // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists + process_removed_device(dev0_bus->rhport, dev0_bus->hub_addr, dev0_bus->hub_port); + + const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl; + const uint8_t new_addr = enum_get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB); + TU_ASSERT(new_addr != 0,); - case ENUM_SET_ADDR: - enum_request_set_addr(); + 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; + + TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC),); break; + } case ENUM_GET_DEVICE_DESC: { - // Allow 2ms for address recovery time, Ref USB Spec 9.2.6.3 - tusb_time_delay_ms_api(2); + 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; + _usbh_data.enumerating_daddr = new_addr; - // Close device 0 - hcd_device_close(_dev0.rhport, 0); + usbh_device_close(dev0_bus->rhport, 0); // close dev0 - // open control pipe for new address - TU_ASSERT(usbh_edpt_control_open(new_addr, new_dev->ep0_size),); + TU_ASSERT(usbh_edpt_control_open(new_addr, new_dev->bMaxPacketSize0),); // open new control endpoint - // Get full device descriptor 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),); + process_enumeration, ENUM_GET_STRING_LANGUAGE_ID_LEN),); break; } - case ENUM_GET_STRING_LANGUAGE_ID: { + // For string descriptor (langid, manufacturer, product, serila): always get the first 2 bytes + // to determine the length first. otherwise, some device may have buffer overflow. + case ENUM_GET_STRING_LANGUAGE_ID_LEN: { // save the received device descriptor - TU_ASSERT(dev,); - tusb_desc_device_t const* desc_device = (tusb_desc_device_t const*) _usbh_epbuf.ctrl; - dev->vid = desc_device->idVendor; - dev->pid = desc_device->idProduct; - dev->i_manufacturer = desc_device->iManufacturer; - dev->i_product = desc_device->iProduct; - dev->i_serial = desc_device->iSerialNumber; + 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; - tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, CFG_TUH_ENUMERATION_BUFSIZE, - process_enumeration, ENUM_GET_STRING_MANUFACTURER); + tuh_enum_descriptor_device_cb(daddr, desc_device); // callback + tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_LANGUAGE_ID); break; } - case ENUM_GET_STRING_MANUFACTURER: { - TU_ASSERT(dev,); - const tusb_desc_string_t* desc_langid = (tusb_desc_string_t const*) _usbh_epbuf.ctrl; + case ENUM_GET_STRING_LANGUAGE_ID: { + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_STRING_MANUFACTURER_LEN); + break; + } + + case ENUM_GET_STRING_MANUFACTURER_LEN: { + const tusb_desc_string_t* desc_langid = (const tusb_desc_string_t *) _usbh_epbuf.ctrl; if (desc_langid->bLength >= 4) { - langid = tu_le16toh(desc_langid->utf16le[0]); + langid = tu_le16toh(desc_langid->utf16le[0]); // previous request is langid } - if (dev->i_manufacturer != 0) { - tuh_descriptor_get_string(daddr, dev->i_manufacturer, langid, _usbh_epbuf.ctrl, CFG_TUH_ENUMERATION_BUFSIZE, - process_enumeration, ENUM_GET_STRING_PRODUCT); + if (dev->iManufacturer != 0) { + tuh_descriptor_get_string(daddr, dev->iManufacturer, langid, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_MANUFACTURER); + break; + }else { + TU_ATTR_FALLTHROUGH; + } + } + + case ENUM_GET_STRING_MANUFACTURER: { + if (dev->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, + process_enumeration, ENUM_GET_STRING_PRODUCT_LEN); break; } else { TU_ATTR_FALLTHROUGH; } } - case ENUM_GET_STRING_PRODUCT: { - TU_ASSERT(dev,); - if (state == ENUM_GET_STRING_PRODUCT) { - langid = tu_le16toh(xfer->setup->wIndex); // if not fall through, get langid from previous setup packet + case ENUM_GET_STRING_PRODUCT_LEN: + if (dev->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); + break; + } else { + TU_ATTR_FALLTHROUGH; } - if (dev->i_product != 0) { - tuh_descriptor_get_string(daddr, dev->i_product, 0x0409, _usbh_epbuf.ctrl, CFG_TUH_ENUMERATION_BUFSIZE, - process_enumeration, ENUM_GET_STRING_SERIAL); + + case ENUM_GET_STRING_PRODUCT: { + if (dev->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, + process_enumeration, ENUM_GET_STRING_SERIAL_LEN); break; } else { TU_ATTR_FALLTHROUGH; } } - case ENUM_GET_STRING_SERIAL: { - TU_ASSERT(dev,); - if (state == ENUM_GET_STRING_SERIAL) { - langid = tu_le16toh(xfer->setup->wIndex); // if not fall through, get langid from previous setup packet + case ENUM_GET_STRING_SERIAL_LEN: + if (dev->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); + break; + } else { + TU_ATTR_FALLTHROUGH; } - if (dev->i_serial != 0) { - tuh_descriptor_get_string(daddr, dev->i_serial, langid, _usbh_epbuf.ctrl, CFG_TUH_ENUMERATION_BUFSIZE, - process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC); + + case ENUM_GET_STRING_SERIAL: { + if (dev->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, + process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC); break; } else { TU_ATTR_FALLTHROUGH; @@ -1574,8 +1712,8 @@ static void process_enumeration(tuh_xfer_t* xfer) { case ENUM_GET_9BYTE_CONFIG_DESC: { // Get 9-byte for total length - uint8_t const config_idx = CONFIG_NUM - 1; - TU_LOG_USBH("Get Configuration[0] Descriptor (9 bytes)\r\n"); + uint8_t const config_idx = 0; + 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),); break; @@ -1585,35 +1723,40 @@ static void process_enumeration(tuh_xfer_t* xfer) { uint8_t const* desc_config = _usbh_epbuf.ctrl; // Use offsetof to avoid pointer to the odd/misaligned address - uint16_t const total_len = tu_le16toh( - tu_unaligned_read16(desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))); + uint16_t const total_len = tu_le16toh(tu_unaligned_read16(desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))); // TODO not enough buffer to hold configuration descriptor TU_ASSERT(total_len <= CFG_TUH_ENUMERATION_BUFSIZE,); // Get full configuration descriptor - uint8_t const config_idx = CONFIG_NUM - 1; - TU_LOG_USBH("Get Configuration[0] Descriptor\r\n"); + uint8_t const config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex); + TU_LOG_USBH("Get Configuration[%u] Descriptor\r\n", config_idx); TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, total_len, process_enumeration, ENUM_SET_CONFIG),); break; } - case ENUM_SET_CONFIG: - // tuh_desc_configuration_cb(daddr, CONFIG_NUM-1, (const tusb_desc_configuration_t*) _usbh_epbuf.ctrl); - - TU_ASSERT(tuh_configuration_set(daddr, CONFIG_NUM, process_enumeration, ENUM_CONFIG_DRIVER),); + case ENUM_SET_CONFIG: { + uint8_t config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (tuh_enum_descriptor_configuration_cb(daddr, config_idx, (const tusb_desc_configuration_t*) _usbh_epbuf.ctrl)) { + TU_ASSERT(tuh_configuration_set(daddr, config_idx+1, process_enumeration, ENUM_CONFIG_DRIVER),); + } else { + config_idx++; + TU_ASSERT(config_idx < dev->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),); + } break; + } case ENUM_CONFIG_DRIVER: { TU_LOG_USBH("Device configured\r\n"); - TU_ASSERT(dev,); - dev->configured = 1; // Parse configuration & set up drivers // driver_open() must not make any usb transfer - TU_ASSERT(_parse_configuration_descriptor(daddr, (tusb_desc_configuration_t*) _usbh_epbuf.ctrl),); + TU_ASSERT(enum_parse_configuration_desc(daddr, (tusb_desc_configuration_t*) _usbh_epbuf.ctrl),); // Start the Set Configuration process for interfaces (itf = TUSB_INDEX_INVALID_8) // Since driver can perform control transfer within its set_config, this is done asynchronously. @@ -1624,65 +1767,12 @@ static void process_enumeration(tuh_xfer_t* xfer) { } default: - // stop enumeration if unknown state - enum_full_complete(); + enum_full_complete(); // stop enumeration if unknown state break; } } - - -static bool enum_new_device(hcd_event_t* event) { - _dev0.rhport = event->rhport; - _dev0.hub_addr = event->connection.hub_addr; - _dev0.hub_port = event->connection.hub_port; - - if (_dev0.hub_addr == 0) { - // connected directly to roothub - hcd_port_reset(_dev0.rhport); - - // Since we are in middle of rhport reset, frame number is not available yet. - // need to depend on tusb_time_millis_api() - tusb_time_delay_ms_api(ENUM_RESET_DELAY_MS); - - hcd_port_reset_end(_dev0.rhport); - - // wait until device connection is stable TODO non blocking - tusb_time_delay_ms_api(ENUM_DEBOUNCING_DELAY_MS); - - // device unplugged while delaying - if (!hcd_port_connect_status(_dev0.rhport)) { - enum_full_complete(); - return true; - } - - _dev0.speed = hcd_port_speed_get(_dev0.rhport); - TU_LOG_USBH("%s Speed\r\n", tu_str_speed[_dev0.speed]); - - // 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 external hub - // wait until device connection is stable TODO non blocking - tusb_time_delay_ms_api(ENUM_DEBOUNCING_DELAY_MS); - - // ENUM_HUB_GET_STATUS - TU_ASSERT(hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_epbuf.ctrl, - process_enumeration, ENUM_HUB_CLEAR_RESET_1)); - } -#endif // hub - - return true; -} - -static uint8_t get_new_address(bool is_hub) { +static uint8_t enum_get_new_address(bool is_hub) { uint8_t start; uint8_t end; @@ -1695,53 +1785,15 @@ static uint8_t get_new_address(bool is_hub) { } for (uint8_t idx = start; idx < end; idx++) { - if (!_usbh_devices[idx].connected) return (idx+1); + if (!_usbh_devices[idx].connected) { + return (idx + 1); + } } return 0; // invalid address } -static bool enum_request_set_addr(void) { - tusb_desc_device_t const* desc_device = (tusb_desc_device_t const*) _usbh_epbuf.ctrl; - - // Get new address - uint8_t const new_addr = get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB); - TU_ASSERT(new_addr != 0); - TU_LOG_USBH("Set Address = %d\r\n", new_addr); - - usbh_device_t* new_dev = get_device(new_addr); - new_dev->rhport = _dev0.rhport; - new_dev->hub_addr = _dev0.hub_addr; - new_dev->hub_port = _dev0.hub_port; - new_dev->speed = _dev0.speed; - new_dev->connected = 1; - new_dev->ep0_size = desc_device->bMaxPacketSize0; - - tusb_control_request_t const request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_DEVICE, - .type = TUSB_REQ_TYPE_STANDARD, - .direction = TUSB_DIR_OUT - }, - .bRequest = TUSB_REQ_SET_ADDRESS, - .wValue = tu_htole16(new_addr), - .wIndex = 0, - .wLength = 0 - }; - tuh_xfer_t xfer = { - .daddr = 0, // dev0 - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = process_enumeration, - .user_data = ENUM_GET_DEVICE_DESC - }; - - TU_ASSERT(tuh_control_xfer(&xfer)); - return true; -} - -static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg) { +static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg) { usbh_device_t* dev = get_device(dev_addr); uint16_t const total_len = tu_le16toh(desc_cfg->wTotalLength); uint8_t const* desc_end = ((uint8_t const*) desc_cfg) + total_len; @@ -1802,7 +1854,7 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur // Find driver for this interface for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { usbh_class_driver_t const * driver = get_driver(drv_id); - if (driver && driver->open(dev->rhport, dev_addr, desc_itf, drv_len) ) { + if (driver && driver->open(dev->bus_info.rhport, dev_addr, desc_itf, drv_len) ) { // open successfully TU_LOG_USBH(" %s opened\r\n", driver->name); @@ -1821,9 +1873,9 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur break; // exit driver find loop } - if ( drv_id == TOTAL_DRIVER_COUNT - 1 ) { + if (drv_id == TOTAL_DRIVER_COUNT - 1) { TU_LOG_USBH("[%u:%u] Interface %u: class = %u subclass = %u protocol = %u is not supported\r\n", - dev->rhport, dev_addr, desc_itf->bInterfaceNumber, desc_itf->bInterfaceClass, desc_itf->bInterfaceSubClass, desc_itf->bInterfaceProtocol); + dev->bus_info.rhport, dev_addr, desc_itf->bInterfaceNumber, desc_itf->bInterfaceClass, desc_itf->bInterfaceSubClass, desc_itf->bInterfaceProtocol); } } @@ -1858,18 +1910,20 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) { TU_LOG_USBH("HUB address = %u is mounted\r\n", dev_addr); }else { // Invoke callback if available - if (tuh_mount_cb) tuh_mount_cb(dev_addr); + if (tuh_mount_cb) { + tuh_mount_cb(dev_addr); + } } } } static void enum_full_complete(void) { // mark enumeration as complete - _dev0.enumerating = 0; + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; #if CFG_TUH_HUB - if (_dev0.hub_addr) { - hub_edpt_status_xfer(_dev0.hub_addr); // get next hub status + if (_usbh_data.dev0_bus.hub_addr != 0) { + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status } #endif diff --git a/src/host/usbh.h b/src/host/usbh.h index 52b4f478c..1e9bb26bc 100644 --- a/src/host/usbh.h +++ b/src/host/usbh.h @@ -33,17 +33,20 @@ #include "common/tusb_common.h" +#if CFG_TUH_MAX3421 +#include "portable/analog/max3421/hcd_max3421.h" +#endif + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ // Endpoint Bulk size depending on host mx speed -#define TUH_EPSIZE_BULK_MPS (TUD_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS) +#define TUH_EPSIZE_BULK_MPS (TUH_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS) // forward declaration struct tuh_xfer_s; typedef struct tuh_xfer_s tuh_xfer_t; - typedef void (*tuh_xfer_cb_t)(tuh_xfer_t* xfer); // Note1: layout and order of this will be changed in near future @@ -76,6 +79,17 @@ typedef struct { tusb_desc_interface_t desc; } tuh_itf_info_t; +typedef struct { + uint8_t rhport; + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; +} tuh_bus_info_t; + +// backward compatibility for hcd_devtree_info_t, maybe removed in the future +#define hcd_devtree_info_t tuh_bus_info_t +#define hcd_devtree_get_info(_daddr, _bus_info) tuh_bus_info_get(_daddr, _bus_info) + // ConfigID for tuh_configure() enum { TUH_CFGID_INVALID = 0, @@ -100,10 +114,13 @@ typedef union { //--------------------------------------------------------------------+ // Invoked when enumeration get device descriptor -// TU_ATTR_WEAK void tuh_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device); +// Device is not ready to communicate yet, application can copy the descriptor if needed +void tuh_enum_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device); // Invoked when enumeration get configuration descriptor -// TU_ATTR_WEAK void tuh_desc_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config); +// For multi-configuration device return false to skip, true to proceed with this configuration (may not be implemented yet) +// Device is not ready to communicate yet, application can copy the descriptor if needed +bool tuh_enum_descriptor_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config); // Invoked when a device is mounted (configured) TU_ATTR_WEAK void tuh_mount_cb (uint8_t daddr); @@ -155,8 +172,7 @@ bool tuh_inited(void); void tuh_task_ext(uint32_t timeout_ms, bool in_isr); // Task function should be called in main/rtos loop -TU_ATTR_ALWAYS_INLINE static inline -void tuh_task(void) { +TU_ATTR_ALWAYS_INLINE static inline void tuh_task(void) { tuh_task_ext(UINT32_MAX, false); } @@ -171,6 +187,8 @@ extern void hcd_int_handler(uint8_t rhport, bool in_isr); #define _tuh_int_handler_arg0() TU_VERIFY_STATIC(false, "tuh_int_handler() must have 1 or 2 arguments") #define _tuh_int_handler_arg1(_rhport) hcd_int_handler(_rhport, true) #define _tuh_int_handler_arg2(_rhport, _in_isr) hcd_int_handler(_rhport, _in_isr) + +// 1st argument is rhport (mandatory), 2nd argument in_isr (optional) #define tuh_int_handler(...) TU_FUNC_OPTIONAL_ARG(_tuh_int_handler, __VA_ARGS__) // Check if roothub port is initialized and active as a host @@ -186,6 +204,9 @@ bool tuh_rhport_reset_bus(uint8_t rhport, bool active); // Get VID/PID of device bool tuh_vid_pid_get(uint8_t daddr, uint16_t* vid, uint16_t* pid); +// Get local (cached) device descriptor once device is enumerated +bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_device); + // Get speed of device tusb_speed_t tuh_speed_get(uint8_t daddr); @@ -197,23 +218,34 @@ bool tuh_mounted(uint8_t daddr); bool tuh_connected(uint8_t daddr); // Check if device is suspended -TU_ATTR_ALWAYS_INLINE static inline -bool tuh_suspended(uint8_t daddr) { +TU_ATTR_ALWAYS_INLINE static inline bool tuh_suspended(uint8_t daddr) { // TODO implement suspend & resume on host (void) daddr; return false; } // Check if device is ready to communicate with -TU_ATTR_ALWAYS_INLINE static inline -bool tuh_ready(uint8_t daddr) { +TU_ATTR_ALWAYS_INLINE static inline bool tuh_ready(uint8_t daddr) { return tuh_mounted(daddr) && !tuh_suspended(daddr); } +// Get bus information of device +bool tuh_bus_info_get(uint8_t daddr, tuh_bus_info_t* bus_info); + //--------------------------------------------------------------------+ // Transfer API +// Each Function will make a USB transfer request to device. If +// - complete_cb != NULL, the function will return immediately and invoke the callback when request is complete. +// - complete_cb == NULL, the function will block until request is complete. +// In this case, user_data should be tusb_xfer_result_t* to hold the transfer result. //--------------------------------------------------------------------+ +// Helper to make Sync API from async one +#define TU_API_SYNC(_async_api, ...) \ + xfer_result_t result = XFER_RESULT_INVALID;\ + TU_VERIFY(_async_api(__VA_ARGS__, NULL, (uintptr_t) &result), XFER_RESULT_TIMEOUT); \ + return result + // Submit a control transfer // - async: complete callback invoked when finished. // - sync : blocking if complete callback is NULL. @@ -227,10 +259,17 @@ bool tuh_edpt_xfer(tuh_xfer_t* xfer); // Open a non-control endpoint bool tuh_edpt_open(uint8_t daddr, tusb_desc_endpoint_t const * desc_ep); +// Close a non-control endpoint, it will abort any pending transfer +bool tuh_edpt_close(uint8_t daddr, uint8_t ep_addr); + // 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 tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr); +// Set Address (control transfer) +bool tuh_address_set(uint8_t daddr, uint8_t new_addr, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + // Set Configuration (control transfer) // config_num = 0 will un-configure device. Note: config_num = config_descriptor_index + 1 // true on success, false if there is on-going control transfer or incorrect parameters @@ -298,45 +337,54 @@ bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* //--------------------------------------------------------------------+ // Descriptors Synchronous (blocking) +// Sync API which is blocking until transfer is complete. +// return transfer result //--------------------------------------------------------------------+ -// Sync (blocking) version of tuh_descriptor_get() -// return transfer result -uint8_t tuh_descriptor_get_sync(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_sync(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get, daddr, type, index, buffer, len); +} -// Sync (blocking) version of tuh_descriptor_get_device() -// return transfer result -uint8_t tuh_descriptor_get_device_sync(uint8_t daddr, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get_device() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_device_sync(uint8_t daddr, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_device, daddr, buffer, len); +} -// Sync (blocking) version of tuh_descriptor_get_configuration() -// return transfer result -uint8_t tuh_descriptor_get_configuration_sync(uint8_t daddr, uint8_t index, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get_configuration() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_configuration_sync(uint8_t daddr, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_configuration, daddr, index, buffer, len); +} -// Sync (blocking) version of tuh_descriptor_get_hid_report() -// return transfer result -uint8_t tuh_descriptor_get_hid_report_sync(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get_hid_report() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_hid_report_sync(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_hid_report, daddr, itf_num, desc_type, index, buffer, len); +} -// Sync (blocking) version of tuh_descriptor_get_string() -// return transfer result -uint8_t tuh_descriptor_get_string_sync(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_string_sync(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_string, daddr, index, language_id, buffer, len); +} -// Sync (blocking) version of tuh_descriptor_get_string_langid() -TU_ATTR_ALWAYS_INLINE static inline -uint8_t tuh_descriptor_get_string_langid_sync(uint8_t daddr, void* buffer, uint16_t len) { +// Sync version of tuh_descriptor_get_string_langid() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_string_langid_sync(uint8_t daddr, void* buffer, uint16_t len) { return tuh_descriptor_get_string_sync(daddr, 0, 0, buffer, len); } -// Sync (blocking) version of tuh_descriptor_get_manufacturer_string() -// return transfer result -uint8_t tuh_descriptor_get_manufacturer_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get_manufacturer_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_manufacturer_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_manufacturer_string, daddr, language_id, buffer, len); +} -// Sync (blocking) version of tuh_descriptor_get_product_string() -// return transfer result -uint8_t tuh_descriptor_get_product_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get_product_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_product_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_product_string, daddr, language_id, buffer, len); +} -// Sync (blocking) version of tuh_descriptor_get_serial_string() -// return transfer result -uint8_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len); +// Sync version of tuh_descriptor_get_serial_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_serial_string, daddr, language_id, buffer, len); +} #ifdef __cplusplus } diff --git a/src/host/usbh_pvt.h b/src/host/usbh_pvt.h index bfa1fb2ba..cb092e5f3 100644 --- a/src/host/usbh_pvt.h +++ b/src/host/usbh_pvt.h @@ -63,7 +63,7 @@ usbh_class_driver_t const* usbh_app_driver_get_cb(uint8_t* driver_count) TU_ATTR // Call by class driver to tell USBH that it has complete the enumeration void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num); -uint8_t usbh_get_rhport(uint8_t dev_addr); +uint8_t usbh_get_rhport(uint8_t daddr); uint8_t* usbh_get_enum_buf(void); @@ -71,6 +71,9 @@ void usbh_int_set(bool enabled); void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr); +void usbh_spin_lock(bool in_isr); +void usbh_spin_unlock(bool in_isr); + //--------------------------------------------------------------------+ // USBH Endpoint API //--------------------------------------------------------------------+ diff --git a/src/osal/osal.h b/src/osal/osal.h index 38d45da44..a33280425 100644 --- a/src/osal/osal.h +++ b/src/osal/osal.h @@ -75,6 +75,10 @@ typedef void (*osal_task_func_t)( void * ); // OSAL Porting API // Should be implemented as static inline function in osal_port.h header /* + 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); + osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); bool osal_semaphore_delete(osal_semaphore_t semd_hdl); bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); diff --git a/src/osal/osal_freertos.h b/src/osal/osal_freertos.h index a3a0f3a3f..bde5ec010 100644 --- a/src/osal/osal_freertos.h +++ b/src/osal/osal_freertos.h @@ -42,20 +42,20 @@ extern "C" { //--------------------------------------------------------------------+ #if configSUPPORT_STATIC_ALLOCATION - typedef StaticSemaphore_t osal_semaphore_def_t; - typedef StaticSemaphore_t osal_mutex_def_t; +typedef StaticSemaphore_t osal_semaphore_def_t; +typedef StaticSemaphore_t osal_mutex_def_t; #else - // not used therefore defined to smallest possible type to save space - typedef uint8_t osal_semaphore_def_t; - typedef uint8_t osal_mutex_def_t; + +// not used therefore defined to the smallest possible type to save space +typedef uint8_t osal_semaphore_def_t; +typedef uint8_t osal_mutex_def_t; #endif typedef SemaphoreHandle_t osal_semaphore_t; typedef SemaphoreHandle_t osal_mutex_t; typedef QueueHandle_t osal_queue_t; -typedef struct -{ +typedef struct { uint16_t depth; uint16_t item_sz; void* buf; @@ -83,16 +83,14 @@ typedef struct //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ - TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { - if ( msec == OSAL_TIMEOUT_WAIT_FOREVER ) return portMAX_DELAY; - if ( msec == 0 ) return 0; + if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { return portMAX_DELAY; } + if (msec == 0) { return 0; } uint32_t ticks = pdMS_TO_TICKS(msec); - // configTICK_RATE_HZ is less than 1000 and 1 tick > 1 ms - // we still need to delay at least 1 tick - if ( ticks == 0 ) ticks = 1; + // If configTICK_RATE_HZ 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; } @@ -102,9 +100,70 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { } //--------------------------------------------------------------------+ -// Semaphore API +// Spinlock API //--------------------------------------------------------------------+ +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +#if TUSB_MCU_VENDOR_ESPRESSIF +// Espressif critical take spinlock as argument and does not use in_isr +typedef portMUX_TYPE osal_spinlock_t; + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + spinlock_initialize(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + portENTER_CRITICAL(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + portEXIT_CRITICAL(ctx); +} + +#else + +typedef UBaseType_t osal_spinlock_t; + +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) { + if (!TUP_MCU_MULTIPLE_CORE) { + (void) ctx; + return; // single core MCU does not need to lock in ISR + } + *ctx = taskENTER_CRITICAL_FROM_ISR(); + } else { + taskENTER_CRITICAL(); + } +} + +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) { + (void) ctx; + return; // single core MCU does not need to lock in ISR + } + taskEXIT_CRITICAL_FROM_ISR(*ctx); + } else { + taskEXIT_CRITICAL(); + } +} +#endif + +//--------------------------------------------------------------------+ +// Semaphore API +//--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { #if configSUPPORT_STATIC_ALLOCATION return xSemaphoreCreateBinaryStatic(semdef); @@ -120,19 +179,12 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t } TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { - if ( !in_isr ) { + if (!in_isr) { return xSemaphoreGive(sem_hdl) != 0; } else { BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t res = xSemaphoreGiveFromISR(sem_hdl, &xHigherPriorityTaskWoken); - -#if CFG_TUSB_MCU == OPT_MCU_ESP32S2 || CFG_TUSB_MCU == OPT_MCU_ESP32S3 - // not needed after https://github.com/espressif/esp-idf/commit/c5fd79547ac9b7bae06fa660e9f814d18d3390b7 - if ( xHigherPriorityTaskWoken ) portYIELD_FROM_ISR(); -#else portYIELD_FROM_ISR(xHigherPriorityTaskWoken); -#endif - return res != 0; } } @@ -148,7 +200,6 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t c //--------------------------------------------------------------------+ // MUTEX API (priority inheritance) //--------------------------------------------------------------------+ - TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { #if configSUPPORT_STATIC_ALLOCATION return xSemaphoreCreateMutexStatic(mdef); @@ -174,7 +225,6 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hd //--------------------------------------------------------------------+ // QUEUE API //--------------------------------------------------------------------+ - TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { osal_queue_t q; @@ -201,19 +251,12 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, v } TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { - if ( !in_isr ) { + if (!in_isr) { return xQueueSendToBack(qhdl, data, OSAL_TIMEOUT_WAIT_FOREVER) != 0; } else { BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t res = xQueueSendToBackFromISR(qhdl, data, &xHigherPriorityTaskWoken); - -#if CFG_TUSB_MCU == OPT_MCU_ESP32S2 || CFG_TUSB_MCU == OPT_MCU_ESP32S3 - // not needed after https://github.com/espressif/esp-idf/commit/c5fd79547ac9b7bae06fa660e9f814d18d3390b7 (IDF v5) - if ( xHigherPriorityTaskWoken ) portYIELD_FROM_ISR(); -#else portYIELD_FROM_ISR(xHigherPriorityTaskWoken); -#endif - return res != 0; } } diff --git a/src/osal/osal_mynewt.h b/src/osal/osal_mynewt.h index 16def0d2a..6d51f8ec3 100644 --- a/src/osal/osal_mynewt.h +++ b/src/osal/osal_mynewt.h @@ -41,6 +41,32 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { } //--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef os_sr_t osal_spinlock_t; + +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +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 (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + OS_ENTER_CRITICAL(*ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + OS_EXIT_CRITICAL(*ctx); +} + +//--------------------------------------------------------------------+ // Semaphore API //--------------------------------------------------------------------+ typedef struct os_sem osal_semaphore_def_t; diff --git a/src/osal/osal_none.h b/src/osal/osal_none.h index 40e9bb83a..a8eb1042b 100644 --- a/src/osal/osal_none.h +++ b/src/osal/osal_none.h @@ -41,6 +41,33 @@ TU_ATTR_WEAK void osal_task_delay(uint32_t msec); #endif //--------------------------------------------------------------------+ +// 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 //--------------------------------------------------------------------+ typedef struct { diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index 315de0950..ace5907d7 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -44,6 +44,27 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { } //--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef critical_section_t osal_spinlock_t; // pico implement critical section with spinlock +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + critical_section_init(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + (void) in_isr; + critical_section_enter_blocking(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + (void) in_isr; + critical_section_exit(ctx); +} + +//--------------------------------------------------------------------+ // Binary Semaphore API //--------------------------------------------------------------------+ typedef struct semaphore osal_semaphore_def_t, * osal_semaphore_t; diff --git a/src/osal/osal_rtthread.h b/src/osal/osal_rtthread.h index c27814835..a778f5425 100644 --- a/src/osal/osal_rtthread.h +++ b/src/osal/osal_rtthread.h @@ -43,6 +43,32 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { } //--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef struct rt_spinlock osal_spinlock_t; + +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + rt_spin_lock_init(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + rt_spin_lock(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + rt_spin_unlock(ctx); +} + +//--------------------------------------------------------------------+ // Semaphore API //--------------------------------------------------------------------+ typedef struct rt_semaphore osal_semaphore_def_t; diff --git a/src/osal/osal_rtx4.h b/src/osal/osal_rtx4.h index 35909e4d6..35860ddd5 100644 --- a/src/osal/osal_rtx4.h +++ b/src/osal/osal_rtx4.h @@ -57,6 +57,25 @@ TU_ATTR_ALWAYS_INLINE static inline uint16_t msec2wait(uint32_t msec) { } //--------------------------------------------------------------------+ +// Spinlock API, stub not implemented +//--------------------------------------------------------------------+ +typedef uint8_t osal_spinlock_t; +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +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) { + (void) ctx; (void) in_isr; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + (void) ctx; (void) in_isr; +} + +//--------------------------------------------------------------------+ // Semaphore API //--------------------------------------------------------------------+ typedef OS_SEM osal_semaphore_def_t; diff --git a/src/osal/osal_zephyr.h b/src/osal/osal_zephyr.h index 8ecb13c6d..91f225f79 100644 --- a/src/osal/osal_zephyr.h +++ b/src/osal/osal_zephyr.h @@ -36,6 +36,35 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { } //--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef struct { + struct k_spinlock lock; + k_spinlock_key_t key; +} osal_spinlock_t; + +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +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 (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + ctx->key = k_spin_lock(&ctx->lock); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + k_spin_unlock(&ctx->lock, ctx->key); +} + +//--------------------------------------------------------------------+ // Binary Semaphore API //--------------------------------------------------------------------+ typedef struct k_sem osal_semaphore_def_t, * osal_semaphore_t; diff --git a/src/portable/analog/max3421/hcd_max3421.c b/src/portable/analog/max3421/hcd_max3421.c index c5e924266..971dbd62e 100644 --- a/src/portable/analog/max3421/hcd_max3421.c +++ b/src/portable/analog/max3421/hcd_max3421.c @@ -28,9 +28,9 @@ #if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 -#include <stdatomic.h> #include "host/hcd.h" #include "host/usbh.h" +#include "host/usbh_pvt.h" //--------------------------------------------------------------------+ // @@ -233,7 +233,7 @@ typedef struct { uint8_t hxfr; }sndfifo_owner; - atomic_flag busy; // busy transferring + bool busy_lock; // busy transferring #if OSAL_MUTEX_REQUIRED OSAL_MUTEX_DEF(spi_mutexdef); @@ -253,28 +253,6 @@ static tuh_configure_max3421_t _tuh_cfg = { }; //--------------------------------------------------------------------+ -// API: SPI transfer with MAX3421E -// - spi_cs_api(), spi_xfer_api(), int_api(): must be implemented by application -// - reg_read(), reg_write(): is implemented by this driver, can be used by application -//--------------------------------------------------------------------+ - -// API to control MAX3421 SPI CS -extern void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); - -// API to transfer data with MAX3421 SPI -// Either tx_buf or rx_buf can be NULL, which means transfer is write or read only -extern bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const* tx_buf, uint8_t* rx_buf, size_t xfer_bytes); - -// API to enable/disable MAX3421 INTR pin interrupt -extern void tuh_max3421_int_api(uint8_t rhport, bool enabled); - -// API to read MAX3421's register. Implemented by TinyUSB -uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr); - -// API to write MAX3421's register. Implemented by TinyUSB -bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr); - -//--------------------------------------------------------------------+ // SPI Commands and Helper //--------------------------------------------------------------------+ @@ -349,7 +327,9 @@ TU_ATTR_ALWAYS_INLINE static inline void mode_write(uint8_t rhport, uint8_t data } TU_ATTR_ALWAYS_INLINE static inline void peraddr_write(uint8_t rhport, uint8_t data, bool in_isr) { - if ( _hcd_data.peraddr == data ) return; // no need to change address + if (_hcd_data.peraddr == data) { + return; // no need to change address + } _hcd_data.peraddr = data; reg_write(rhport, PERADDR_ADDR, data, in_isr); @@ -395,7 +375,7 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_setup(uint8_t rhport, const uint static void hwfifo_receive(uint8_t rhport, uint8_t * buffer, uint16_t len, bool in_isr) { uint8_t hirq; - uint8_t const reg = RCVVFIFO_ADDR; + const uint8_t reg = RCVVFIFO_ADDR; max3421_spi_lock(rhport, in_isr); @@ -411,7 +391,7 @@ static void hwfifo_receive(uint8_t rhport, uint8_t * buffer, uint16_t len, bool //--------------------------------------------------------------------+ static max3421_ep_t* find_ep_not_addr0(uint8_t daddr, uint8_t ep_num, uint8_t ep_dir) { - uint8_t const is_out = 1-ep_dir; + const uint8_t is_out = 1-ep_dir; for(size_t i=1; i<CFG_TUH_MAX3421_ENDPOINT_TOTAL; i++) { max3421_ep_t* ep = &_hcd_data.ep[i]; // control endpoint is bi-direction (skip check) @@ -632,6 +612,9 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const * e if (daddr == 0 && ep_num == 0) { ep = &_hcd_data.ep[0]; }else { + if (NULL != find_ep_not_addr0(daddr, ep_num, ep_dir)) { + return true; // already opened + } ep = allocate_ep(); TU_ASSERT(ep); ep->daddr = daddr; @@ -645,6 +628,21 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const * e return true; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); + max3421_ep_t * ep = find_ep_not_addr0(daddr, ep_num, ep_dir); + + if (!ep) { + return false; // not opened + } + + tu_memclr(ep, sizeof(max3421_ep_t)); + + return true; +} + /* The microcontroller repeatedly writes the SNDFIFO register R2 to load the FIFO with up to 64 data bytes. * Then the microcontroller writes the SNDBC register, which this does three things: * 1. Tells the MAX3421E SIE (Serial Interface Engine) how many bytes in the FIFO to send. @@ -731,8 +729,8 @@ static void xact_generic(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool // Submit a transfer, when complete hcd_event_xfer_complete() must be invoked bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { - uint8_t const ep_num = tu_edpt_number(ep_addr); - uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = (uint8_t) tu_edpt_dir(ep_addr); max3421_ep_t* ep = find_opened_ep(daddr, ep_num, ep_dir); TU_VERIFY(ep); @@ -748,8 +746,17 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buf ep->xferred_len = 0; ep->state = EP_STATE_ATTEMPT_1; + bool has_xfer = false; + + usbh_spin_lock(false); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(false); + // carry out transfer if not busy - if (!atomic_flag_test_and_set(&_hcd_data.busy)) { + if (has_xfer) { xact_generic(rhport, ep, true, false); } @@ -785,8 +792,17 @@ bool hcd_setup_send(uint8_t rhport, uint8_t daddr, uint8_t const setup_packet[8] ep->xferred_len = 0; ep->state = EP_STATE_ATTEMPT_1; + bool has_xfer = false; + + usbh_spin_lock(false); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(false); + // carry out transfer if not busy - if (!atomic_flag_test_and_set(&_hcd_data.busy)) { + if (has_xfer) { xact_setup(rhport, ep, false); } @@ -852,8 +868,8 @@ static void handle_connect_irq(uint8_t rhport, bool in_isr) { } static void xfer_complete_isr(uint8_t rhport, max3421_ep_t *ep, xfer_result_t result, uint8_t hrsl, bool in_isr) { - uint8_t const ep_dir = 1-ep->hxfr_bm.is_out; - uint8_t const ep_addr = tu_edpt_addr(ep->hxfr_bm.ep_num, ep_dir); + const uint8_t ep_dir = 1 - ep->hxfr_bm.is_out; + const uint8_t ep_addr = tu_edpt_addr(ep->hxfr_bm.ep_num, ep_dir); // save data toggle if (ep_dir) { @@ -871,7 +887,9 @@ static void xfer_complete_isr(uint8_t rhport, max3421_ep_t *ep, xfer_result_t re xact_generic(rhport, next_ep, true, in_isr); }else { // no more pending - atomic_flag_clear(&_hcd_data.busy); + usbh_spin_lock(in_isr); + _hcd_data.busy_lock = false; + usbh_spin_unlock(in_isr); } } @@ -910,7 +928,9 @@ static void handle_xfer_done(uint8_t rhport, bool in_isr) { xact_generic(rhport, next_ep, true, in_isr); } else { // no more pending in this frame -> clear busy - atomic_flag_clear(&_hcd_data.busy); + usbh_spin_lock(in_isr); + _hcd_data.busy_lock = false; + usbh_spin_unlock(in_isr); } return; @@ -1001,8 +1021,8 @@ void print_hirq(uint8_t hirq) { // Interrupt handler void hcd_int_handler(uint8_t rhport, bool in_isr) { uint8_t hirq = reg_read(rhport, HIRQ_ADDR, in_isr) & _hcd_data.hien; - if (!hirq) return; -// print_hirq(hirq); + if (!hirq) { return; } + // print_hirq(hirq); if (hirq & HIRQ_FRAME_IRQ) { _hcd_data.frame_count++; @@ -1021,8 +1041,19 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) { } // start usb transfer if not busy - if (ep_retry != NULL && !atomic_flag_test_and_set(&_hcd_data.busy)) { - xact_generic(rhport, ep_retry, true, in_isr); + if (ep_retry != NULL) { + bool has_xfer = false; + + usbh_spin_lock(in_isr); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(in_isr); + + if (has_xfer) { + xact_generic(rhport, ep_retry, true, in_isr); + } } } diff --git a/src/portable/analog/max3421/hcd_max3421.h b/src/portable/analog/max3421/hcd_max3421.h new file mode 100644 index 000000000..4631fa21a --- /dev/null +++ b/src/portable/analog/max3421/hcd_max3421.h @@ -0,0 +1,63 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 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_HCD_MAX3421_H +#define TUSB_HCD_MAX3421_H + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// SPI transfer API with MAX3421E are implemented by application +// - spi_cs_api(), spi_xfer_api(), int_api() +//--------------------------------------------------------------------+ + +// API to control MAX3421 SPI CS +extern void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); + +// API to transfer data with MAX3421 SPI +// Either tx_buf or rx_buf can be NULL, which means transfer is write or read only +extern bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const* tx_buf, uint8_t* rx_buf, size_t xfer_bytes); + +// API to enable/disable MAX3421 INTR pin interrupt +extern void tuh_max3421_int_api(uint8_t rhport, bool enabled); + +//--------------------------------------------------------------------+ +// API for read/write MAX3421 registers +// are implemented by this driver, can be used by application +//--------------------------------------------------------------------+ + +// API to read MAX3421's register. Implemented by TinyUSB +uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr); + +// API to write MAX3421's register. Implemented by TinyUSB +bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index a716dc24c..244f5a2d4 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -239,7 +239,11 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { usbmode |= USBMODE_CM_DEVICE; dcd_reg->USBMODE = usbmode; +#ifdef CFG_TUD_CI_HS_VBUS_CHARGE + dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION; +#else dcd_reg->OTGSC = OTGSC_VBUS_DISCHARGE | OTGSC_OTG_TERMINATION; +#endif #if !TUD_OPT_HIGH_SPEED dcd_reg->PORTSC1 = PORTSC1_FORCE_FULL_SPEED; diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c index c4c342a70..b5324a754 100644 --- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c @@ -35,6 +35,7 @@ //--------------------------------------------------------------------+ #include "common/tusb_common.h" #include "host/hcd.h" +#include "host/usbh.h" #include "portable/ehci/ehci_api.h" #include "ci_hs_type.h" @@ -92,12 +93,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { hcd_reg->USBMODE = USBMODE_CM_HOST; #endif - // FIXME force full speed, still have issue with Highspeed enumeration - // probably due to physical connection bouncing when plug/unplug - // 1. Have issue when plug/unplug devices, maybe the port is not reset properly - // 2. Also does not seems to detect disconnection - hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; - return ehci_init(rhport, (uint32_t) &hcd_reg->CAPLENGTH, (uint32_t) &hcd_reg->USBCMD); } diff --git a/src/portable/ehci/ehci.c b/src/portable/ehci/ehci.c index 01bbf62bf..953483583 100644 --- a/src/portable/ehci/ehci.c +++ b/src/portable/ehci/ehci.c @@ -34,9 +34,16 @@ #include "osal/osal.h" #include "host/hcd.h" +#include "host/usbh.h" +#include "host/usbh_pvt.h" #include "ehci_api.h" #include "ehci.h" +// NXP specific fixes +#if TU_CHECK_MCU(OPT_MCU_MIMXRT1XXX, OPT_MCU_LPC55, OPT_MCU_MCXN9) +#include "fsl_device_registers.h" +#endif + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ @@ -62,8 +69,7 @@ #define QHD_MAX (CFG_TUH_DEVICE_MAX*CFG_TUH_ENDPOINT_MAX + CFG_TUH_HUB) #define QTD_MAX QHD_MAX -typedef struct -{ +typedef struct { ehci_link_t period_framelist[FRAMELIST_SIZE]; // TODO only implement 1 ms & 2 ms & 4 ms, 8 ms (framelist) @@ -139,6 +145,12 @@ static ehci_qhd_t* qhd_get_from_addr (uint8_t dev_addr, uint8_t ep_addr); static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc); static void qhd_attach_qtd(ehci_qhd_t *qhd, ehci_qtd_t *qtd); static void qhd_remove_qtd(ehci_qhd_t *qhd); +TU_ATTR_ALWAYS_INLINE static inline bool qhd_is_periodic(ehci_qhd_t const *qhd) { + return qhd->int_smask != 0; +} +TU_ATTR_ALWAYS_INLINE static inline uint8_t qhd_ep_addr(ehci_qhd_t const *qhd) { + return tu_edpt_addr(qhd->ep_number, qhd->pid); +} TU_ATTR_ALWAYS_INLINE static inline ehci_qtd_t* qtd_control(uint8_t dev_addr); TU_ATTR_ALWAYS_INLINE static inline ehci_qtd_t* qtd_find_free (void); @@ -146,9 +158,10 @@ static void qtd_init (ehci_qtd_t* qtd, void const* buffer, uint16_t total_bytes) TU_ATTR_ALWAYS_INLINE static inline ehci_link_t* list_get_period_head(uint8_t rhport, uint32_t interval_ms); TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* list_get_async_head(uint8_t rhport); -TU_ATTR_ALWAYS_INLINE static inline void list_insert (ehci_link_t *current, ehci_link_t *new, uint8_t new_type); TU_ATTR_ALWAYS_INLINE static inline ehci_link_t* list_next (ehci_link_t const *p_link); -static void list_remove_qhd_by_daddr(ehci_link_t* list_head, uint8_t dev_addr); +TU_ATTR_ALWAYS_INLINE static inline void list_insert (ehci_link_t *current, ehci_link_t *entry, uint8_t type); +TU_ATTR_ALWAYS_INLINE static inline void list_remove(ehci_link_t* head, ehci_link_t* prev, ehci_qhd_t* qhd); +static void list_remove_qhd_by_addr(ehci_link_t *list_head, uint8_t dev_addr, uint8_t ep_addr); static void ehci_disable_schedule(ehci_registers_t* regs, bool is_period) { // maybe have a timeout for status @@ -172,18 +185,46 @@ static void ehci_enable_schedule(ehci_registers_t* regs, bool is_period) { } } +#if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U)) +static void nxp_usbphy_disconn_detector_set(uint8_t port, bool enable) { + // unify naming convention +#if !defined(USBPHY1) && defined(USBPHY) + #define USBPHY1 USBPHY +#endif + + if (port == 0) { + if (enable) { + USBPHY1->CTRL_SET = USBPHY_CTRL_ENHOSTDISCONDETECT_MASK; + } else { + USBPHY1->CTRL_CLR = USBPHY_CTRL_ENHOSTDISCONDETECT_MASK; + } + } +#if FSL_FEATURE_SOC_USBPHY_COUNT > 1U + else if (port == 1) { + if (enable) { + USBPHY2->CTRL_SET = USBPHY_CTRL_ENHOSTDISCONDETECT_MASK; + } else { + USBPHY2->CTRL_CLR = USBPHY_CTRL_ENHOSTDISCONDETECT_MASK; + } + } +#endif + +#if !defined(USBPHY1) && defined(USBPHY) + #undef USBPHY1 +#endif +} +#endif + //--------------------------------------------------------------------+ // HCD API //--------------------------------------------------------------------+ - -uint32_t hcd_frame_number(uint8_t rhport) -{ +uint32_t hcd_frame_number(uint8_t rhport) { (void) rhport; - return (ehci_data.uframe_number + ehci_data.regs->frame_index) >> 3; + uint32_t uframe = ehci_data.regs->frame_index; + return (ehci_data.uframe_number + uframe) >> 3; } -void hcd_port_reset(uint8_t rhport) -{ +void hcd_port_reset(uint8_t rhport) { (void) rhport; ehci_registers_t* regs = ehci_data.regs; @@ -204,49 +245,50 @@ void hcd_port_reset(uint8_t rhport) regs->portsc = portsc; } -void hcd_port_reset_end(uint8_t rhport) -{ +void hcd_port_reset_end(uint8_t rhport) { (void) rhport; ehci_registers_t* regs = ehci_data.regs; - // skip if reset is already complete - if (!regs->portsc_bm.port_reset) { - return; - } + // stop reset only if is not complete yet + if (regs->portsc_bm.port_reset) { + // mask out all change bits since they are Write 1 to clear + uint32_t portsc = regs->portsc & ~EHCI_PORTSC_MASK_W1C; + portsc &= ~EHCI_PORTSC_MASK_PORT_RESET; - // mask out all change bits since they are Write 1 to clear - uint32_t portsc = regs->portsc & ~EHCI_PORTSC_MASK_W1C; - portsc &= ~EHCI_PORTSC_MASK_PORT_RESET; + regs->portsc = portsc; + } - regs->portsc = portsc; +#if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U)) + // Enable disconnect detector for highspeed device only + if (hcd_port_speed_get(rhport) == TUSB_SPEED_HIGH) { + nxp_usbphy_disconn_detector_set(rhport, true); + } +#endif } -bool hcd_port_connect_status(uint8_t rhport) -{ +bool hcd_port_connect_status(uint8_t rhport) { (void) rhport; return ehci_data.regs->portsc_bm.current_connect_status; } -tusb_speed_t hcd_port_speed_get(uint8_t rhport) -{ +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { (void) rhport; return (tusb_speed_t) ehci_data.regs->portsc_bm.nxp_port_speed; // NXP specific port speed } // Close all opened endpoint belong to this device -void hcd_device_close(uint8_t rhport, uint8_t daddr) -{ +void hcd_device_close(uint8_t rhport, uint8_t daddr) { // skip dev0 if (daddr == 0) { return; } - // Remove from async list - list_remove_qhd_by_daddr((ehci_link_t *) list_get_async_head(rhport), daddr); + // Remove from async list all endpoints of this device + list_remove_qhd_by_addr((ehci_link_t *) list_get_async_head(rhport), daddr, TUSB_INDEX_INVALID_8); - // Remove from all interval period list - for(uint8_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++) { - list_remove_qhd_by_daddr((ehci_link_t *) &ehci_data.period_head_arr[i], daddr); + // Remove from all interval period list of this device + for (uint8_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++) { + list_remove_qhd_by_addr((ehci_link_t *) &ehci_data.period_head_arr[i], daddr, TUSB_INDEX_INVALID_8); } // Async doorbell (EHCI 4.8.2 for operational details) @@ -358,12 +400,10 @@ bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg) } #if 0 -static void ehci_stop(uint8_t rhport) -{ +static void ehci_stop(uint8_t rhport) { (void) rhport; ehci_registers_t* regs = ehci_data.regs; - regs->command_bm.run_stop = 0; // USB Spec: controller has to stop within 16 uframe = 2 frames @@ -375,41 +415,46 @@ static void ehci_stop(uint8_t rhport) // Endpoint API //--------------------------------------------------------------------+ -bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) -{ - (void) rhport; - +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) { // TODO not support ISO yet TU_ASSERT (ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS); //------------- Prepare Queue Head -------------// - ehci_qhd_t *p_qhd = (ep_desc->bEndpointAddress == 0) ? qhd_control(dev_addr) : qhd_find_free(); + ehci_qhd_t *p_qhd; + if (ep_desc->bEndpointAddress == 0) { + p_qhd = qhd_control(dev_addr); + } else { + if (NULL != qhd_get_from_addr(dev_addr, ep_desc->bEndpointAddress)) { + return true; // already opened + } + p_qhd = qhd_find_free(); + } TU_ASSERT(p_qhd); - qhd_init(p_qhd, dev_addr, ep_desc); - // control of dev0 is always present as async head - if ( dev_addr == 0 ) return true; + // control of dev0 always exists as async head + if (dev_addr == 0) { + return true; + } // Insert to list ehci_link_t * list_head = NULL; - - switch (ep_desc->bmAttributes.xfer) - { + switch (ep_desc->bmAttributes.xfer) { case TUSB_XFER_CONTROL: case TUSB_XFER_BULK: - list_head = (ehci_link_t*) list_get_async_head(rhport); - break; + list_head = (ehci_link_t *) list_get_async_head(rhport); + break; case TUSB_XFER_INTERRUPT: list_head = list_get_period_head(rhport, p_qhd->interval_ms); - break; + break; case TUSB_XFER_ISOCHRONOUS: // TODO iso is not supported - break; + break; - default: break; + default: + break; } TU_ASSERT(list_head); @@ -421,8 +466,23 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const return true; } -bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) -{ +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + ehci_qhd_t* qhd = qhd_get_from_addr(daddr, ep_addr); + TU_VERIFY(qhd != NULL); + + ehci_link_t * list_head; + if (qhd_is_periodic(qhd)) { + // interrupt endpoint + list_head = list_get_period_head(rhport, qhd->interval_ms);; + } else { + list_head = (ehci_link_t *) list_get_async_head(rhport); + } + + list_remove_qhd_by_addr(list_head, daddr, ep_addr); + return true; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { (void) rhport; ehci_qhd_t* qhd = &ehci_data.control[dev_addr].qhd; @@ -444,14 +504,14 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet return true; } -bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) -{ +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { (void) rhport; uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); ehci_qhd_t* qhd = qhd_get_from_addr(dev_addr, ep_addr); + TU_VERIFY(qhd != NULL); ehci_qtd_t* qtd; if (epnum == 0) { @@ -540,8 +600,7 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { // This isr mean it is safe to modify previously removed queue head from async list. // In tinyusb, queue head is only removed when device is unplugged. TU_ATTR_ALWAYS_INLINE static inline -void async_advance_isr(uint8_t rhport) -{ +void async_advance_isr(uint8_t rhport) { (void) rhport; ehci_qhd_t *qhd_pool = ehci_data.qhd_pool; @@ -561,6 +620,10 @@ void port_connect_status_change_isr(uint8_t rhport) { hcd_event_device_attach(rhport, true); } else // device unplugged { +#if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U)) + // Disable disconnect detector + nxp_usbphy_disconn_detector_set(rhport, false); +#endif hcd_event_device_remove(rhport, true); } } @@ -612,8 +675,7 @@ void qhd_xfer_complete_isr(ehci_qhd_t * qhd) { } TU_ATTR_ALWAYS_INLINE static inline -void proccess_async_xfer_isr(ehci_qhd_t * const list_head) -{ +void proccess_async_xfer_isr(ehci_qhd_t * const list_head) { ehci_qhd_t *qhd = list_head; do { @@ -623,8 +685,7 @@ void proccess_async_xfer_isr(ehci_qhd_t * const list_head) } TU_ATTR_ALWAYS_INLINE static inline -void process_period_xfer_isr(uint8_t rhport, uint32_t interval_ms) -{ +void process_period_xfer_isr(uint8_t rhport, uint32_t interval_ms) { uint32_t const period_1ms_addr = (uint32_t) list_get_period_head(rhport, 1u); ehci_link_t next_link = *list_get_period_head(rhport, interval_ms); @@ -726,51 +787,55 @@ TU_ATTR_ALWAYS_INLINE static inline ehci_link_t* list_next(ehci_link_t const *p_ return (ehci_link_t*) tu_align32(p_link->address); } -TU_ATTR_ALWAYS_INLINE static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type) -{ - new->address = current->address; - current->address = ((uint32_t) new) | (new_type << 1); +TU_ATTR_ALWAYS_INLINE static inline void list_insert(ehci_link_t *current, ehci_link_t *entry, uint8_t type) { + entry->address = current->address; + current->address = ((uint32_t) entry) | (type << 1); } -// Remove all queue head belong to this device address -static void list_remove_qhd_by_daddr(ehci_link_t* list_head, uint8_t dev_addr) { - ehci_link_t* prev = list_head; +// Remove a queue head from the list. +// Per EHCI 4.8.2 the removed qhd's next is linked to list head (which always reachable by Host Controller) +// TODO support iTD/siTD +TU_ATTR_ALWAYS_INLINE static inline void list_remove(ehci_link_t* head, ehci_link_t* prev, ehci_qhd_t* qhd) { + // TODO deactivate all TD, wait for QHD to inactive before removal + prev->address = qhd->next.address; + + // link the removed qhd's next to list head + qhd->next.address = ((uint32_t) head) | (EHCI_QTYPE_QHD << 1); + + if (qhd_is_periodic(qhd)) { + // period list queue element is guarantee to be free in the next frame (1 ms) + qhd->used = 0; + } else { + // async list use async advance handshake. Mark as removing, will completely re-usable when async advance isr occurs + qhd->removing = 1; + } + + hcd_dcache_clean(qhd, sizeof(ehci_qhd_t)); + hcd_dcache_clean(prev, sizeof(ehci_qhd_t)); +} + +// Remove queue head belong to this device address +static void list_remove_qhd_by_addr(ehci_link_t *list_head, uint8_t dev_addr, uint8_t ep_addr) { + ehci_link_t *prev = list_head; while (prev && !prev->terminate) { - ehci_qhd_t* qhd = (ehci_qhd_t*) (uintptr_t) list_next(prev); + ehci_qhd_t *qhd = (ehci_qhd_t *) (uintptr_t) list_next(prev); // done if loop back to head - if ( (uintptr_t) qhd == (uintptr_t) list_head) { + if ((uintptr_t) qhd == (uintptr_t) list_head) { break; } - if ( qhd->dev_addr == dev_addr ) { - // TODO deactivate all TD, wait for QHD to inactive before removal - prev->address = qhd->next.address; - - // EHCI 4.8.2 link the removed qhd's next to async head (which always reachable by Host Controller) - qhd->next.address = ((uint32_t) list_head) | (EHCI_QTYPE_QHD << 1); - - if ( qhd->int_smask ) - { - // period list queue element is guarantee to be free in the next frame (1 ms) - qhd->used = 0; - }else - { - // async list use async advance handshake - // mark as removing, will completely re-usable when async advance isr occurs - qhd->removing = 1; - } - - hcd_dcache_clean(qhd, sizeof(ehci_qhd_t)); - hcd_dcache_clean(prev, sizeof(ehci_qhd_t)); - }else { + // ep_addr is 0xff means all endpoints of this device address + if (qhd->dev_addr == dev_addr && + (ep_addr == TUSB_INDEX_INVALID_8 || qhd_ep_addr(qhd) == ep_addr)) { + list_remove(list_head, prev, qhd); + } else { prev = list_next(prev); } } } - //--------------------------------------------------------------------+ // Queue Header helper //--------------------------------------------------------------------+ @@ -782,8 +847,10 @@ TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* qhd_control(uint8_t dev_addr) { // Find a free queue head TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t *qhd_find_free(void) { - for ( uint32_t i = 0; i < QHD_MAX; i++ ) { - if ( !ehci_data.qhd_pool[i].used ) return &ehci_data.qhd_pool[i]; + for (uint32_t i = 0; i < QHD_MAX; i++) { + if (!ehci_data.qhd_pool[i].used) { + return &ehci_data.qhd_pool[i]; + } } return NULL; } @@ -801,26 +868,31 @@ static ehci_qhd_t *qhd_get_from_addr(uint8_t dev_addr, uint8_t ep_addr) { ehci_qhd_t *qhd_pool = ehci_data.qhd_pool; - for ( uint32_t i = 0; i < QHD_MAX; i++ ) { - if ( (qhd_pool[i].dev_addr == dev_addr) && - ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) ) { - return &qhd_pool[i]; + // protect qhd_pool since 'used' and 'removing' can be changed in isr + ehci_qhd_t *result = NULL; + usbh_spin_lock(false); + for (uint32_t i = 0; i < QHD_MAX; i++) { + if ((qhd_pool[i].dev_addr == dev_addr) && + ep_addr == qhd_ep_addr(&qhd_pool[i]) && + qhd_pool[i].used && !qhd_pool[i].removing) { + result = &qhd_pool[i]; + break; } } + usbh_spin_unlock(false); - return NULL; + return result; } // Init queue head with endpoint descriptor -static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) -{ +static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) { // address 0 is used as async head, which always on the list --> cannot be cleared (ehci halted otherwise) if (dev_addr != 0) { tu_memclr(p_qhd, sizeof(ehci_qhd_t)); } - hcd_devtree_info_t devtree_info; - hcd_devtree_get_info(dev_addr, &devtree_info); + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); uint8_t const xfer_type = ep_desc->bmAttributes.xfer; uint8_t const interval = ep_desc->bInterval; @@ -828,60 +900,63 @@ static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t c p_qhd->dev_addr = dev_addr; p_qhd->fl_inactive_next_xact = 0; p_qhd->ep_number = tu_edpt_number(ep_desc->bEndpointAddress); - p_qhd->ep_speed = devtree_info.speed; + p_qhd->ep_speed = bus_info.speed; p_qhd->data_toggle_control= (xfer_type == TUSB_XFER_CONTROL) ? 1 : 0; - p_qhd->head_list_flag = (dev_addr == 0) ? 1 : 0; // addr0's endpoint is the static asyn list head + p_qhd->head_list_flag = (dev_addr == 0) ? 1 : 0; // addr0's endpoint is the static async list head p_qhd->max_packet_size = tu_edpt_packet_size(ep_desc); p_qhd->fl_ctrl_ep_flag = ((xfer_type == TUSB_XFER_CONTROL) && (p_qhd->ep_speed != TUSB_SPEED_HIGH)) ? 1 : 0; p_qhd->nak_reload = 0; - // Bulk/Control -> smask = cmask = 0 - // TODO Isochronous - if (TUSB_XFER_INTERRUPT == xfer_type) - { - if (TUSB_SPEED_HIGH == p_qhd->ep_speed) - { - TU_ASSERT( interval <= 16, ); - if ( interval < 4) // sub millisecond interval - { - p_qhd->interval_ms = 0; - p_qhd->int_smask = (interval == 1) ? TU_BIN8(11111111) : - (interval == 2) ? TU_BIN8(10101010) : TU_BIN8(01000100); - }else - { - p_qhd->interval_ms = (uint8_t) tu_min16( 1 << (interval-4), 255 ); - p_qhd->int_smask = TU_BIT(interval % 8); + switch (xfer_type) { + case TUSB_XFER_CONTROL: + case TUSB_XFER_BULK: + p_qhd->int_smask = p_qhd->fl_int_cmask = 0; + break; + + case TUSB_XFER_INTERRUPT: + if (TUSB_SPEED_HIGH == p_qhd->ep_speed) { + TU_ASSERT(interval <= 16, ); + if (interval < 4) { + // sub millisecond interval + p_qhd->interval_ms = 0; + p_qhd->int_smask = (interval == 1) ? TU_BIN8(11111111) : + (interval == 2) ? TU_BIN8(10101010): TU_BIN8(01000100); + } else { + p_qhd->interval_ms = (uint8_t) tu_min16(1 << (interval - 4), 255); + p_qhd->int_smask = TU_BIT(interval % 8); + } + } else { + TU_ASSERT(0 != interval, ); + // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes + p_qhd->int_smask = 0x01; + p_qhd->fl_int_cmask = TU_BIN8(11100); + p_qhd->interval_ms = interval; } - }else - { - TU_ASSERT( 0 != interval, ); - // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes - p_qhd->int_smask = 0x01; - p_qhd->fl_int_cmask = TU_BIN8(11100); - p_qhd->interval_ms = interval; - } - }else - { - p_qhd->int_smask = p_qhd->fl_int_cmask = 0; + break; + + case TUSB_XFER_ISOCHRONOUS: + // TODO not support ISO yet + break; + + default: break; } - p_qhd->fl_hub_addr = devtree_info.hub_addr; - p_qhd->fl_hub_port = devtree_info.hub_port; + p_qhd->fl_hub_addr = bus_info.hub_addr; + p_qhd->fl_hub_port = bus_info.hub_port; p_qhd->mult = 1; // TODO not use high bandwidth/park mode yet //------------- HCD Management Data -------------// p_qhd->used = 1; p_qhd->removing = 0; p_qhd->attached_qtd = NULL; - p_qhd->pid = tu_edpt_dir(ep_desc->bEndpointAddress) ? EHCI_PID_IN : EHCI_PID_OUT; // PID for TD under this endpoint + p_qhd->pid = tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN ? EHCI_PID_IN : EHCI_PID_OUT; // PID for TD under this endpoint //------------- active, but no TD list -------------// p_qhd->qtd_overlay.halted = 0; p_qhd->qtd_overlay.next.terminate = 1; p_qhd->qtd_overlay.alternate.terminate = 1; - if (TUSB_XFER_BULK == xfer_type && p_qhd->ep_speed == TUSB_SPEED_HIGH && p_qhd->pid == EHCI_PID_OUT) - { + if (TUSB_XFER_BULK == xfer_type && p_qhd->ep_speed == TUSB_SPEED_HIGH && p_qhd->pid == EHCI_PID_OUT) { p_qhd->qtd_overlay.ping_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11 } } diff --git a/src/portable/ehci/ehci.h b/src/portable/ehci/ehci.h index 457adc1d3..87659701b 100644 --- a/src/portable/ehci/ehci.h +++ b/src/portable/ehci/ehci.h @@ -49,15 +49,14 @@ // TODO merge OHCI with EHCI enum { - EHCI_MAX_ITD = 4, + EHCI_MAX_ITD = 4, EHCI_MAX_SITD = 16 }; //--------------------------------------------------------------------+ // EHCI Data Structure //--------------------------------------------------------------------+ -enum -{ +enum { EHCI_QTYPE_ITD = 0 , EHCI_QTYPE_QHD , EHCI_QTYPE_SITD , @@ -65,8 +64,7 @@ enum }; /// EHCI PID -enum -{ +enum { EHCI_PID_OUT = 0 , EHCI_PID_IN , EHCI_PID_SETUP @@ -74,187 +72,182 @@ enum /// Link pointer typedef union { - uint32_t address; - struct { - uint32_t terminate : 1; - uint32_t type : 2; - }; -}ehci_link_t; + uint32_t address; + struct { + uint32_t terminate : 1; + uint32_t type : 2; + }; +} ehci_link_t; TU_VERIFY_STATIC( sizeof(ehci_link_t) == 4, "size is not correct" ); /// Queue Element Transfer Descriptor /// Qtd is used to declare overlay in ehci_qhd_t -> cannot be declared with TU_ATTR_ALIGNED(32) -typedef struct -{ - // Word 0: Next QTD Pointer - ehci_link_t next; - - // Word 1: Alternate Next QTD Pointer (not used) - union{ - ehci_link_t alternate; - struct { - uint32_t : 5; - uint32_t used : 1; - uint32_t : 10; - uint32_t expected_bytes : 16; - }; - }; +typedef struct { + // Word 0 Next QTD Pointer + ehci_link_t next; - // Word 2: qTQ Token - volatile uint32_t ping_err : 1 ; ///< For Highspeed: 0 Out, 1 Ping. Full/Slow used as error indicator - volatile uint32_t non_hs_split_state : 1 ; ///< Used by HC to track the state of split transaction - volatile uint32_t non_hs_missed_uframe : 1 ; ///< HC misses a complete split transaction - volatile uint32_t xact_err : 1 ; ///< Error (Timeout, CRC, Bad PID ... ) - volatile uint32_t babble_err : 1 ; ///< Babble detected, also set Halted bit to 1 - volatile uint32_t buffer_err : 1 ; ///< Data overrun/underrun error - volatile uint32_t halted : 1 ; ///< Serious error or STALL received - volatile uint32_t active : 1 ; ///< Start transfer, clear by HC when complete + // Word 1 Alternate Next QTD Pointer (not used) + union { + ehci_link_t alternate; + struct { + uint32_t : 5; + uint32_t used : 1; + uint32_t : 10; + uint32_t expected_bytes : 16; + }; + }; - uint32_t pid : 2 ; ///< 0: OUT, 1: IN, 2 Setup - volatile uint32_t err_count : 2 ; ///< Error Counter of consecutive errors - volatile uint32_t current_page : 3 ; ///< Index into the qTD buffer pointer list - uint32_t int_on_complete : 1 ; ///< Interrupt on complete - volatile uint32_t total_bytes : 15 ; ///< Transfer bytes, decreased during transaction - volatile uint32_t data_toggle : 1 ; ///< Data Toggle bit + // Word 2 qTQ Token + volatile uint32_t ping_err : 1; // For Highspeed: 0 Out, 1 Ping. Full/Slow used as error indicator + volatile uint32_t non_hs_split_state : 1; // Used by HC to track the state of split transaction + volatile uint32_t non_hs_missed_uframe : 1; // HC misses a complete split transaction + volatile uint32_t xact_err : 1; // Error (Timeout, CRC, Bad PID ... ) + volatile uint32_t babble_err : 1; // Babble detected, also set Halted bit to 1 + volatile uint32_t buffer_err : 1; // Data overrun/underrun error + volatile uint32_t halted : 1; // Serious error or STALL received + volatile uint32_t active : 1; // Start transfer, clear by HC when complete + uint32_t pid : 2; // 0: OUT, 1: IN, 2 Setup + volatile uint32_t err_count : 2; // Error Counter of consecutive errors + volatile uint32_t current_page : 3; // Index into the qTD buffer pointer list + uint32_t int_on_complete : 1; // Interrupt on complete + volatile uint32_t total_bytes : 15; // Transfer bytes, decreased during transaction + volatile uint32_t data_toggle : 1; // Data Toggle bit - /// Buffer Page Pointer List, Each element in the list is a 4K page aligned, physical memory address. The lower 12 bits in each pointer are reserved (except for the first one) as each memory pointer must reference the start of a 4K page - uint32_t buffer[5]; + // Buffer Page Pointer List, Each element in the list is a 4K page aligned, physical memory address. + // The lower 12 bits in each pointer are reserved (except for the first one) as each memory pointer must reference the start of a 4K page + uint32_t buffer[5]; } ehci_qtd_t; TU_VERIFY_STATIC( sizeof(ehci_qtd_t) == 32, "size is not correct" ); /// Queue Head -typedef struct TU_ATTR_ALIGNED(32) -{ - // Word 0: Next QHD - ehci_link_t next; +typedef struct TU_ATTR_ALIGNED(32) { + // Word 0 Next QHD + ehci_link_t next; - // Word 1: Endpoint Characteristics - uint32_t dev_addr : 7 ; ///< device address - uint32_t fl_inactive_next_xact : 1 ; ///< Only valid for Periodic with Full/Slow speed - uint32_t ep_number : 4 ; ///< EP number - uint32_t ep_speed : 2 ; ///< 0: Full, 1: Low, 2: High - uint32_t data_toggle_control : 1 ; ///< 0: use DT in qHD, 1: use DT in qTD - uint32_t head_list_flag : 1 ; ///< Head of the queue - uint32_t max_packet_size : 11 ; ///< Max packet size - uint32_t fl_ctrl_ep_flag : 1 ; ///< 1 if is Full/Low speed control endpoint - uint32_t nak_reload : 4 ; ///< Used by HC + // Word 1 Endpoint Characteristics + uint32_t dev_addr : 7; // device address + uint32_t fl_inactive_next_xact : 1; // Only valid for Periodic with Full/Slow speed + uint32_t ep_number : 4; // EP number + uint32_t ep_speed : 2; // Full (0), Low (1), High (2) + uint32_t data_toggle_control : 1; // 0 use DT in qHD, 1 use DT in qTD + uint32_t head_list_flag : 1; // Head of the queue + uint32_t max_packet_size : 11; // Max packet size + uint32_t fl_ctrl_ep_flag : 1; // 1 if is Full/Low speed control endpoint + uint32_t nak_reload : 4; // Used by HC - // Word 2: Endpoint Capabilities - uint32_t int_smask : 8 ; ///< Interrupt Schedule Mask - uint32_t fl_int_cmask : 8 ; ///< Split Completion Mask for Full/Slow speed - uint32_t fl_hub_addr : 7 ; ///< Hub Address for Full/Slow speed - uint32_t fl_hub_port : 7 ; ///< Hub Port for Full/Slow speed - uint32_t mult : 2 ; ///< Transaction per micro frame + // Word 2 Endpoint Capabilities + uint32_t int_smask : 8; // Interrupt Schedule Mask + uint32_t fl_int_cmask : 8; // Split Completion Mask for Full/Slow speed + uint32_t fl_hub_addr : 7; // Hub Address for Full/Slow speed + uint32_t fl_hub_port : 7; // Hub Port for Full/Slow speed + uint32_t mult : 2; // Transaction per micro frame - // Word 3: Current qTD Pointer - volatile uint32_t qtd_addr; + // Word 3 Current qTD Pointer + volatile uint32_t qtd_addr; - // Word 4-11: Transfer Overlay - volatile ehci_qtd_t qtd_overlay; + // Word 4-11 Transfer Overlay + volatile ehci_qtd_t qtd_overlay; - //--------------------------------------------------------------------+ + //--------------------------------------------------------------------+ /// Due to the fact QHD is 32 bytes aligned but occupies only 48 bytes - /// thus there are 16 bytes padding free that we can make use of. + /// thus there are 16 bytes padding free that we can make use of. //--------------------------------------------------------------------+ - uint8_t used; - uint8_t removing; // removed from asyn list, waiting for async advance - uint8_t pid; - uint8_t interval_ms; // polling interval in frames (or millisecond) + uint8_t used; + uint8_t removing;// removed from asyn list, waiting for async advance + uint8_t pid; + uint8_t interval_ms;// polling interval in frames (or millisecond) - uint8_t TU_RESERVED[4]; + uint8_t TU_RESERVED[4]; // Attached TD management, note usbh will only queue 1 TD per QHD. // buffer for dcache invalidate since td's buffer is modified by HC and finding initial buffer address is not trivial uint32_t attached_buffer; - ehci_qtd_t * volatile attached_qtd; + ehci_qtd_t *volatile attached_qtd; } ehci_qhd_t; - TU_VERIFY_STATIC( sizeof(ehci_qhd_t) == 64, "size is not correct" ); /// Highspeed Isochronous Transfer Descriptor (section 3.3) typedef struct TU_ATTR_ALIGNED(32) { - // Word 0: Next Link Pointer - ehci_link_t next; + // Word 0: Next Link Pointer + ehci_link_t next; - // Word 1-8: iTD Transaction Status and Control List - struct { - // iTD Control - volatile uint32_t offset : 12 ; ///< This field is a value that is an offset, expressed in bytes, from the beginning of a buffer. - volatile uint32_t page_select : 3 ; ///< These bits are set by software to indicate which of the buffer page pointers the offset field in this slot should be concatenated to produce the starting memory address for this transaction. The valid range of values for this field is 0 to 6 - uint32_t int_on_complete : 1 ; ///< If this bit is set to a one, it specifies that when this transaction completes, the Host Controller should issue an interrupt at the next interrupt threshold - volatile uint32_t length : 12 ; ///< For an OUT, this field is the number of data bytes the host controller will send during the transaction. The host controller is not required to update this field to reflect the actual number of bytes transferred during the transfer - ///< For an IN, the initial value of the field is the number of bytes the host expects the endpoint to deliver. During the status update, the host controller writes back the number of bytes successfully received. The value in this register is the actual byte count - // iTD Status - volatile uint32_t error : 1 ; ///< Set to a one by the Host Controller during status update in the case where the host did not receive a valid response from the device (Timeout, CRC, Bad PID, etc.). This bit may only be set for isochronous IN transactions. - volatile uint32_t babble_err : 1 ; ///< Set to a 1 by the Host Controller during status update when a babble is detected during the transaction - volatile uint32_t buffer_err : 1 ; ///< Set to a 1 by the Host Controller during status update to indicate that the Host Controller is unable to keep up with the reception of incoming data (overrun) or is unable to supply data fast enough during transmission (underrun). - volatile uint32_t active : 1 ; ///< Set to 1 by software to enable the execution of an isochronous transaction by the Host Controller - } xact[8]; + // Word 1-8: iTD Transaction Status and Control List + struct { + // iTD Control + volatile uint32_t offset : 12; // offset in bytes, from the beginning of a buffer. + volatile uint32_t page_select : 3; // buffer page pointers the offset field in this slot should be concatenated to produce the starting memory address for this transaction. The valid range of values for this field is 0 to 6 + uint32_t int_on_complete : 1; // If this bit is set to a one, it specifies that when this transaction completes, the Host Controller should issue an interrupt at the next interrupt threshold + volatile uint32_t length : 12; // For an OUT, this field is the number of data bytes the host controller will send during the transaction. The host controller is not required to update this field to reflect the actual number of bytes transferred during the transfer + // For an IN, the initial value of the field is the number of bytes the host expects the endpoint to deliver. During the status update, the host controller writes back the number of bytes successfully received. The value in this register is the actual byte count + // iTD Status + volatile uint32_t error : 1; // Set to a one by the Host Controller during status update in the case where the host did not receive a valid response from the device (Timeout, CRC, Bad PID, etc.). This bit may only be set for isochronous IN transactions. + volatile uint32_t babble_err : 1; // Set to a 1 by the Host Controller during status update when a babble is detected during the transaction + volatile uint32_t buffer_err : 1; // Set to a 1 by the Host Controller during status update to indicate that the Host Controller is unable to keep up with the reception of incoming data (overrun) or is unable to supply data fast enough during transmission (underrun). + volatile uint32_t active : 1; // Set to 1 by software to enable the execution of an isochronous transaction by the Host Controller + } xact[8]; - // Word 9-15 Buffer Page Pointer List (Plus) - uint32_t BufferPointer[7]; + // Word 9-15 Buffer Page Pointer List (Plus) + uint32_t BufferPointer[7]; -// // FIXME: Store meta data into buffer pointer reserved for saving memory -// /*---------- HCD Area ----------*/ -// uint32_t used; -// uint32_t IhdIdx; -// uint32_t reserved[6]; + // FIXME: Store meta data into buffer pointer reserved for saving memory + //---------- HCD Area ---------- + // uint32_t used; + // uint32_t IhdIdx; + // uint32_t reserved[6]; } ehci_itd_t; - TU_VERIFY_STATIC( sizeof(ehci_itd_t) == 64, "size is not correct" ); /// Split (Full-Speed) Isochronous Transfer Descriptor -typedef struct TU_ATTR_ALIGNED(32) -{ +typedef struct TU_ATTR_ALIGNED(32) { // Word 0: Next Link Pointer - ehci_link_t next; + ehci_link_t next; - // Word 1: siTD Endpoint Characteristics - uint32_t dev_addr : 7; ///< This field selects the specific device serving as the data source or sink. - uint32_t : 1; ///< reserved - uint32_t ep_number : 4; ///< This 4-bit field selects the particular endpoint number on the device serving as the data source or sink. - uint32_t : 4; ///< This field is reserved and should be set to zero. - uint32_t hub_addr : 7; ///< This field holds the device address of the transaction translators’ hub. - uint32_t : 1; ///< reserved - uint32_t port_number : 7; ///< This field is the port number of the recipient transaction translator. - uint32_t direction : 1; ///< 0 = OUT; 1 = IN. This field encodes whether the full-speed transaction should be an IN or OUT. + // Word 1: siTD Endpoint Characteristics + uint32_t dev_addr : 7; ///< This field selects the specific device serving as the data source or sink. + uint32_t : 1; ///< reserved + uint32_t ep_number : 4; ///< This 4-bit field selects the particular endpoint number on the device serving as the data source or sink. + uint32_t : 4; ///< This field is reserved and should be set to zero. + uint32_t hub_addr : 7; ///< This field holds the device address of the transaction translators’ hub. + uint32_t : 1; ///< reserved + uint32_t port_number : 7; ///< This field is the port number of the recipient transaction translator. + uint32_t direction : 1; ///< 0 = OUT; 1 = IN. This field encodes whether the full-speed transaction should be an IN or OUT. - // Word 2: Micro-frame Schedule Control - uint8_t int_smask ; ///< This field (along with the Activeand SplitX-statefields in the Statusbyte) are used to determine during which micro-frames the host controller should execute complete-split transactions - uint8_t fl_int_cmask; ///< This field (along with the Activeand SplitX-statefields in the Statusbyte) are used to determine during which micro-frames the host controller should execute start-split transactions. - uint16_t reserved ; ///< reserved + // Word 2: Micro-frame Schedule Control + uint8_t int_smask ; ///< This field (along with the Activeand SplitX-statefields in the Statusbyte) are used to determine during which micro-frames the host controller should execute complete-split transactions + uint8_t fl_int_cmask; ///< This field (along with the Activeand SplitX-statefields in the Statusbyte) are used to determine during which micro-frames the host controller should execute start-split transactions. + uint16_t reserved ; ///< reserved - // Word 3: siTD Transfer Status and Control - // Status [7:0] TODO identical to qTD Token'status --> refactor later - volatile uint32_t : 1 ; // reserved - volatile uint32_t split_state : 1 ; - volatile uint32_t missed_uframe : 1 ; - volatile uint32_t xact_err : 1 ; - volatile uint32_t babble_err : 1 ; - volatile uint32_t buffer_err : 1 ; - volatile uint32_t error : 1 ; - volatile uint32_t active : 1 ; - // Micro-frame Schedule Control - volatile uint32_t cmask_progress : 8 ; ///< This field is used by the host controller to record which split-completes have been executed. See Section 4.12.3.3.2 for behavioral requirements. - volatile uint32_t total_bytes : 10 ; ///< This field is initialized by software to the total number of bytes expected in this transfer. Maximum value is 1023 - volatile uint32_t : 4 ; ///< reserved - volatile uint32_t page_select : 1 ; ///< Used to indicate which data page pointer should be concatenated with the CurrentOffsetfield to construct a data buffer pointer - uint32_t int_on_complete : 1 ; ///< Do not interrupt when transaction is complete. 1 = Do interrupt when transaction is complete - uint32_t : 0 ; // padding to the end of current storage unit + // Word 3: siTD Transfer Status and Control + // Status [7:0] TODO identical to qTD Token'status --> refactor later + volatile uint32_t : 1 ; // reserved + volatile uint32_t split_state : 1 ; + volatile uint32_t missed_uframe : 1 ; + volatile uint32_t xact_err : 1 ; + volatile uint32_t babble_err : 1 ; + volatile uint32_t buffer_err : 1 ; + volatile uint32_t error : 1 ; + volatile uint32_t active : 1 ; + // Micro-frame Schedule Control + volatile uint32_t cmask_progress : 8 ; ///< This field is used by the host controller to record which split-completes have been executed. See Section 4.12.3.3.2 for behavioral requirements. + volatile uint32_t total_bytes : 10 ; ///< This field is initialized by software to the total number of bytes expected in this transfer. Maximum value is 1023 + volatile uint32_t : 4 ; ///< reserved + volatile uint32_t page_select : 1 ; ///< Used to indicate which data page pointer should be concatenated with the CurrentOffsetfield to construct a data buffer pointer + uint32_t int_on_complete : 1 ; ///< Do not interrupt when transaction is complete. 1 = Do interrupt when transaction is complete + uint32_t : 0 ; // padding to the end of current storage unit - /// Word 4-5: Buffer Pointer List - uint32_t buffer[2]; // buffer[1] TP: Transaction Position - T-Count: Transaction Count + /// Word 4-5: Buffer Pointer List + uint32_t buffer[2]; // buffer[1] TP: Transaction Position - T-Count: Transaction Count - /*---------- Word 6 ----------*/ - ehci_link_t back; + /*---------- Word 6 ----------*/ + ehci_link_t back; - /// SITD is 32-byte aligned but occupies only 28 --> 4 bytes for storing extra data - uint8_t used; - uint8_t ihd_idx; - uint8_t reserved2[2]; + /// SITD is 32-byte aligned but occupies only 28 --> 4 bytes for storing extra data + uint8_t used; + uint8_t ihd_idx; + uint8_t reserved2[2]; } ehci_sitd_t; TU_VERIFY_STATIC( sizeof(ehci_sitd_t) == 32, "size is not correct" ); @@ -315,8 +308,7 @@ enum { EHCI_PORTSC_MASK_OVER_CURRENT_CHANGE }; -typedef volatile struct -{ +typedef volatile struct { union { uint32_t command; // 0x00 diff --git a/src/portable/mentor/musb/hcd_musb.c b/src/portable/mentor/musb/hcd_musb.c index 1c0740193..dcc023e0b 100644 --- a/src/portable/mentor/musb/hcd_musb.c +++ b/src/portable/mentor/musb/hcd_musb.c @@ -36,6 +36,7 @@ _Pragma("GCC diagnostic ignored \"-Waddress-of-packed-member\""); #endif #include "host/hcd.h" +#include "host/usbh.h" #include "musb_type.h" @@ -695,16 +696,16 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet _hcd.pipe0.length = 8; _hcd.pipe0.remaining = 0; - hcd_devtree_info_t devtree; - hcd_devtree_get_info(dev_addr, &devtree); - switch (devtree.speed) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); + switch (bus_info.speed) { default: return false; case TUSB_SPEED_LOW: USB0->TYPE0 = USB_TYPE0_SPEED_LOW; break; case TUSB_SPEED_FULL: USB0->TYPE0 = USB_TYPE0_SPEED_FULL; break; case TUSB_SPEED_HIGH: USB0->TYPE0 = USB_TYPE0_SPEED_HIGH; break; } - USB0->TXHUBADDR0 = devtree.hub_addr; - USB0->TXHUBPORT0 = devtree.hub_port; + USB0->TXHUBADDR0 = bus_info.hub_addr; + USB0->TXHUBPORT0 = bus_info.hub_port; USB0->TXFUNCADDR0 = dev_addr; USB0->CSRL0 = USB_CSRL0_TXRDY | USB_CSRL0_SETUP; return true; @@ -744,9 +745,9 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const pipe->remaining = 0; uint8_t pipe_type = 0; - hcd_devtree_info_t devtree; - hcd_devtree_get_info(dev_addr, &devtree); - switch (devtree.speed) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); + switch (bus_info.speed) { default: return false; case TUSB_SPEED_LOW: pipe_type |= USB_TXTYPE1_SPEED_LOW; break; case TUSB_SPEED_FULL: pipe_type |= USB_TXTYPE1_SPEED_FULL; break; @@ -763,8 +764,8 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const hw_endpoint_t volatile *regs = edpt_regs(pipenum - 1); if (dir_tx) { fadr->TXFUNCADDR = dev_addr; - fadr->TXHUBADDR = devtree.hub_addr; - fadr->TXHUBPORT = devtree.hub_port; + fadr->TXHUBADDR = bus_info.hub_addr; + fadr->TXHUBPORT = bus_info.hub_port; regs->TXMAXP = mps; regs->TXTYPE = pipe_type | epn; regs->TXINTERVAL = ep_desc->bInterval; @@ -775,8 +776,8 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const USB0->TXIE |= TU_BIT(pipenum); } else { fadr->RXFUNCADDR = dev_addr; - fadr->RXHUBADDR = devtree.hub_addr; - fadr->RXHUBPORT = devtree.hub_port; + fadr->RXHUBADDR = bus_info.hub_addr; + fadr->RXHUBPORT = bus_info.hub_port; regs->RXMAXP = mps; regs->RXTYPE = pipe_type | epn; regs->RXINTERVAL = ep_desc->bInterval; @@ -804,6 +805,11 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const return true; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void)rhport; diff --git a/src/portable/microchip/samd/hcd_samd.c b/src/portable/microchip/samd/hcd_samd.c new file mode 100644 index 000000000..1f4b2b233 --- /dev/null +++ b/src/portable/microchip/samd/hcd_samd.c @@ -0,0 +1,765 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 ChrisDeadman + * + * 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(CFG_TUH_MAX3421) && CFG_TUH_MAX3421) && \ + (CFG_TUSB_MCU == OPT_MCU_SAMD11 || CFG_TUSB_MCU == OPT_MCU_SAMD21 || \ + CFG_TUSB_MCU == OPT_MCU_SAMD51 || CFG_TUSB_MCU == OPT_MCU_SAME5X || \ + CFG_TUSB_MCU == OPT_MCU_SAML22 || CFG_TUSB_MCU == OPT_MCU_SAML21) + +#include "host/hcd.h" +#include "sam.h" + +/*------------------------------------------------------------------*/ +/* MACRO TYPEDEF CONSTANT ENUM + *------------------------------------------------------------------*/ +#define USB_HOST_PTYPE_DIS 0x0 +#define USB_HOST_PTYPE_CTRL 0x1 +#define USB_HOST_PTYPE_ISO 0x2 +#define USB_HOST_PTYPE_BULK 0x3 +#define USB_HOST_PTYPE_INT 0x4 +#define USB_HOST_PTYPE_EXT 0x5 + +#define USB_HOST_PCFG_PTOKEN_SETUP 0x0 +#define USB_HOST_PCFG_PTOKEN_IN 0x1 +#define USB_HOST_PCFG_PTOKEN_OUT 0x2 + +#define USB_PCKSIZE_ENUM(size) \ + ((size) >= 1024 ? 7 \ + : (size) >= 1023 ? 7 \ + : (size) > 256 ? 6 \ + : (size) > 128 ? 5 \ + : (size) > 64 ? 4 \ + : (size) > 32 ? 3 \ + : (size) > 16 ? 2 \ + : (size) > 8 ? 1 \ + : 0) + +// Uncomment to use fake frame number. +// Low-Speed devices stall FNUM during enumeration :/ +// #define HCD_SAMD_FAKE_FNUM + +typedef struct { + uint8_t dev_addr; + uint8_t ep_addr; + uint16_t max_packet_size; + uint16_t xfer_length; + uint16_t xfer_remaining; +} usb_pipe_status_t; + +CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN static volatile UsbHostDescriptor usb_pipe_table[USB_PIPE_NUM]; + +CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN static volatile usb_pipe_status_t usb_pipe_status_table[USB_PIPE_NUM]; + +CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN static volatile uint32_t fake_fnum; + +static uint8_t samd_configure_pipe(uint8_t dev_addr, uint8_t ep_addr) +{ + uint8_t pipe; + uint8_t token; + volatile usb_pipe_status_t* pipe_status; + bool same_addr = false; + bool same_ep_addr = false; + + // evaluate pipe token + token = (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) ? USB_HOST_PCFG_PTOKEN_IN + : tu_edpt_number(ep_addr) == 0 ? USB_HOST_PCFG_PTOKEN_SETUP + : USB_HOST_PCFG_PTOKEN_OUT; + + TU_LOG3("samd_configure_pipe(token=%02X, dev_addr=%02X, ep_addr=%02X)=", token, dev_addr, ep_addr); + + // find already allocated pipe + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + same_addr = (pipe_status->dev_addr == dev_addr); + same_ep_addr = (tu_edpt_number(pipe_status->ep_addr) == tu_edpt_number(ep_addr)); + if (same_ep_addr && (same_addr || (tu_edpt_number(ep_addr) == 0))) { + break; + } + } + + // allocate from pool of free pipes + if (pipe >= USB_PIPE_NUM) { + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + // found a free pipe + if (pipe_status->dev_addr >= UINT8_MAX) { + break; + } + } + } + + // no pipe available :( + if (pipe >= USB_PIPE_NUM) { + TU_LOG3("ERR_NO_PIPE\r\n"); + return pipe; + } + TU_LOG3("%d\r\n", pipe); + + // no transfer should be in progress + TU_ASSERT(((USB->HOST.HostPipe[pipe].PCFG.bit.PTYPE == USB_HOST_PTYPE_DIS) || + USB->HOST.HostPipe[pipe].PSTATUS.bit.PFREEZE == 1), + USB_PIPE_NUM); + + // update addr and ep_addr + pipe_status->dev_addr = dev_addr; + pipe_status->ep_addr = ep_addr; + usb_pipe_table[pipe].HostDescBank[0].CTRL_PIPE.bit.PDADDR = dev_addr; + usb_pipe_table[pipe].HostDescBank[0].CTRL_PIPE.bit.PEPNUM = tu_edpt_number(ep_addr); + + // token specific configuration + USB->HOST.HostPipe[pipe].PCFG.bit.PTOKEN = token; + USB->HOST.HostPipe[pipe].PINTENCLR.reg = USB_HOST_PINTENCLR_MASK; + if (token == USB_HOST_PCFG_PTOKEN_SETUP) { + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_DTGL; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TXSTP; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_STALL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRFAIL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_PERR; + } else if (token == USB_HOST_PCFG_PTOKEN_IN) { + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRCPT_Msk; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_STALL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRFAIL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_PERR; + } else { + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRCPT_Msk; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_STALL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRFAIL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_PERR; + } + + return pipe; +} + +static void samd_free_pipe(uint8_t pipe) +{ + volatile usb_pipe_status_t* pipe_status = &usb_pipe_status_table[pipe]; + pipe_status->dev_addr = UINT8_MAX; + pipe_status->ep_addr = UINT8_MAX; + pipe_status->max_packet_size = 0; + pipe_status->xfer_length = 0; + pipe_status->xfer_remaining = 0; + + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + USB->HOST.HostPipe[pipe].PCFG.reg &= ~USB_HOST_PCFG_PTYPE_Msk; + USB->HOST.HostPipe[pipe].PINTENCLR.reg = USB_HOST_PINTENCLR_MASK; + memset((uint8_t*)(uintptr_t) &usb_pipe_table[pipe], 0, sizeof(usb_pipe_table[pipe])); +} + +static void samd_free_all_pipes(void) +{ + for (uint8_t pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + samd_free_pipe(pipe); + } +} + +static bool samd_on_xfer(uint8_t pipe, xfer_result_t xfer_result) +{ + uint16_t xfer_delta; + bool xfer_complete; + volatile usb_pipe_status_t* pipe_status = &usb_pipe_status_table[pipe]; + + // freeze the pipe + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + + // get number of transferred bytes + if (xfer_result == XFER_RESULT_SUCCESS) { + xfer_delta = usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT; + } else { + xfer_delta = 0; + } + + TU_LOG3("samd_on_xfer(%d, result=%d, xdelta=%d, rem=%d)\r\n", xfer_result, pipe, xfer_delta, pipe_status->xfer_remaining); + + // update pipe status + if (xfer_delta > pipe_status->xfer_remaining) { + xfer_delta = pipe_status->xfer_remaining; + } + pipe_status->xfer_remaining -= xfer_delta; + pipe_status->xfer_length += xfer_delta; + + // last packet handling + if (xfer_delta < pipe_status->max_packet_size) { + pipe_status->xfer_remaining = 0; + } + + // transfer complete + xfer_complete = (xfer_result != XFER_RESULT_SUCCESS) || (pipe_status->xfer_remaining == 0); + if (xfer_complete) { + return true; + } + + // continue receiving + if (tu_edpt_dir(pipe_status->ep_addr) == TUSB_DIR_IN) { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = 0; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + } + // continue sending + else { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = + (pipe_status->xfer_remaining < pipe_status->max_packet_size) ? pipe_status->xfer_remaining + : pipe_status->max_packet_size; + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + } + + // advance packet buffer + usb_pipe_table[pipe].HostDescBank[0].ADDR.reg += xfer_delta; + + // start next transfer + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_PFREEZE; + + return false; +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// Interrupt Handler +void hcd_int_handler(uint8_t rhport, bool in_isr) +{ + (void) rhport; + + uint16_t int_flags; + uint8_t pint_flags; + xfer_result_t xfer_result; + volatile usb_pipe_status_t* pipe_status; + + // + // Check INTFLAG + // + int_flags = USB->HOST.INTFLAG.reg; + if (int_flags & USB_HOST_INTFLAG_HSOF) { + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_HSOF; + } + if (int_flags & USB_HOST_INTFLAG_RST) { + TU_LOG2("USB_HOST_INTFLAG_RST\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RST; + } + if (int_flags & USB_HOST_INTFLAG_WAKEUP) { + TU_LOG3("USB_HOST_INTFLAG_WAKEUP\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_WAKEUP; + } + if (int_flags & USB_HOST_INTFLAG_DNRSM) { + TU_LOG3("USB_HOST_INTFLAG_DNRSM\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DNRSM; + } + if (int_flags & USB_HOST_INTFLAG_UPRSM) { + TU_LOG3("USB_HOST_INTFLAG_UPRSM\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_UPRSM; + } + if (int_flags & USB_HOST_INTFLAG_RAMACER) { + TU_LOG1("USB_HOST_INTFLAG_RAMACER\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RAMACER; + } + if (int_flags & USB_HOST_INTFLAG_DCONN) { + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DCONN; + hcd_event_device_attach(rhport, in_isr); + } + if (int_flags & USB_HOST_INTFLAG_DDISC) { + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DDISC; + hcd_event_device_remove(rhport, in_isr); + } + + // handle pipe interrupts + for (uint8_t pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + // get pipe handle + pipe_status = &usb_pipe_status_table[pipe]; + if (pipe_status->dev_addr >= UINT8_MAX) { + continue; + } + + // + // Check PINTFLAG + // + pint_flags = USB->HOST.HostPipe[pipe].PINTFLAG.reg; + xfer_result = XFER_RESULT_INVALID; + if (pint_flags & USB_HOST_PINTFLAG_TRCPT0) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TRCPT0; + xfer_result = XFER_RESULT_SUCCESS; + } + if (pint_flags & USB_HOST_PINTFLAG_TRCPT1) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TRCPT1; + xfer_result = XFER_RESULT_SUCCESS; + } + if (pint_flags & USB_HOST_PINTFLAG_TXSTP) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TXSTP; + xfer_result = XFER_RESULT_SUCCESS; + } + if (pint_flags & USB_HOST_PINTFLAG_STALL) { + TU_LOG2("USB_HOST_PINTFLAG_STALL\r\n"); + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_STALL; + xfer_result = XFER_RESULT_STALLED; + } + if (pint_flags & USB_HOST_PINTFLAG_TRFAIL) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TRFAIL; + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_BK.reg & USB_HOST_STATUS_BK_ERRORFLOW) { + TU_LOG1("USB_HOST_STATUS_BK_ERRORFLOW\r\n"); + xfer_result = XFER_RESULT_FAILED; + } else if (usb_pipe_table[pipe].HostDescBank[0].STATUS_BK.reg & USB_HOST_STATUS_BK_CRCERR) { + TU_LOG1("USB_HOST_STATUS_BK_CRCERR\r\n"); + xfer_result = XFER_RESULT_FAILED; + } else { + // SAMD Quirk #1: + // Likes to report TRFAIL for no apparent reason -> ignore + } + } + if (pint_flags & USB_HOST_PINTFLAG_PERR) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_PERR; + // Handled by STATUS_PIPE checks below + } + + // + // Check STATUS_PIPE + // + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_DTGLER) { + TU_LOG1("USB_HOST_STATUS_PIPE_DTGLER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_DTGLER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_DAPIDER) { + TU_LOG1("USB_HOST_STATUS_PIPE_DAPIDER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_DAPIDER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_PIDER) { + TU_LOG1("USB_HOST_STATUS_PIPE_PIDER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_PIDER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_CRC16ER) { + TU_LOG1("USB_HOST_STATUS_PIPE_CRC16ER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_CRC16ER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_TOUTER) { + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_TOUTER; + + if ((USB->HOST.HostPipe[pipe].PCFG.bit.PTYPE == USB_HOST_PTYPE_INT) && + (tu_edpt_dir(pipe_status->ep_addr) == TUSB_DIR_IN)) { + // ignore timeouts from INT pipes + } else { + if (xfer_result == XFER_RESULT_INVALID) { + xfer_result = XFER_RESULT_TIMEOUT; + } + } + } + + // prevent PERR from too high error counts, that is handled by TinyUSB anyways + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.bit.ERCNT = 0; + + // no updates + if (xfer_result == XFER_RESULT_INVALID) { + continue; + } + + // continue / complete transfer + if (samd_on_xfer(pipe, xfer_result)) { + hcd_event_xfer_complete(pipe_status->dev_addr, pipe_status->ep_addr, pipe_status->xfer_length, xfer_result, true); + } + } +} + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + TU_ASSERT(rhport == 0); + (void) rh_init; + fake_fnum = 0; + + // reset to get in a clean state. + USB->HOST.CTRLA.bit.SWRST = 1; + while (USB->HOST.SYNCBUSY.bit.SWRST == 0) + ; + while (USB->HOST.SYNCBUSY.bit.SWRST == 1) + ; + + // load pad calibration + USB->HOST.PADCAL.bit.TRANSP = (*((uint32_t*) USB_FUSES_TRANSP_ADDR) & USB_FUSES_TRANSP_Msk) >> USB_FUSES_TRANSP_Pos; + USB->HOST.PADCAL.bit.TRANSN = (*((uint32_t*) USB_FUSES_TRANSN_ADDR) & USB_FUSES_TRANSN_Msk) >> USB_FUSES_TRANSN_Pos; + USB->HOST.PADCAL.bit.TRIM = (*((uint32_t*) USB_FUSES_TRIM_ADDR) & USB_FUSES_TRIM_Msk) >> USB_FUSES_TRIM_Pos; + + USB->HOST.QOSCTRL.bit.CQOS = 3; // High Quality + USB->HOST.QOSCTRL.bit.DQOS = 3; // High Quality + + // configure host-mode + samd_free_all_pipes(); // initializes pipe handles and usb_pipe_table + USB->HOST.DESCADD.reg = (uint32_t) (&usb_pipe_table[0]); + USB->HOST.CTRLB.reg = USB_HOST_CTRLB_SPDCONF_NORMAL | USB_HOST_CTRLB_VBUSOK; + USB->HOST.CTRLA.reg = USB_CTRLA_MODE_HOST | USB_CTRLA_ENABLE | USB_CTRLA_RUNSTDBY; + while (USB->HOST.SYNCBUSY.bit.ENABLE == 1) + ; + + // enable basic USB interrupts + USB->HOST.INTFLAG.reg |= USB->HOST.INTFLAG.reg; // clear pending + USB->HOST.INTENCLR.reg = USB_HOST_INTENCLR_MASK; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_DCONN; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_DDISC; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_WAKEUP; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_RST; + + return true; +} + +#if CFG_TUSB_MCU == OPT_MCU_SAMD51 || CFG_TUSB_MCU == OPT_MCU_SAME5X + +// Enable USB interrupt +void hcd_int_enable(uint8_t rhport) +{ + (void) rhport; + NVIC_EnableIRQ(USB_0_IRQn); + NVIC_EnableIRQ(USB_1_IRQn); + NVIC_EnableIRQ(USB_2_IRQn); + NVIC_EnableIRQ(USB_3_IRQn); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) +{ + (void) rhport; + NVIC_DisableIRQ(USB_3_IRQn); + NVIC_DisableIRQ(USB_2_IRQn); + NVIC_DisableIRQ(USB_1_IRQn); + NVIC_DisableIRQ(USB_0_IRQn); +} + +#elif CFG_TUSB_MCU == OPT_MCU_SAMD11 || CFG_TUSB_MCU == OPT_MCU_SAMD21 || \ + CFG_TUSB_MCU == OPT_MCU_SAML22 || CFG_TUSB_MCU == OPT_MCU_SAML21 + +// Enable USB interrupt +void hcd_int_enable(uint8_t rhport) +{ + (void) rhport; + NVIC_EnableIRQ(USB_IRQn); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) +{ + (void) rhport; + NVIC_DisableIRQ(USB_IRQn); +} + +#else + +#error "No implementation available for hcd_int_enable / hcd_int_disable" + +#endif + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport) +{ + (void) rhport; + +// SAMD Quirk #2: +// FNUM is stalled before enumeration of Low-Speed devices. +// internal frame counter can be used as workaround (not very accurate) +#ifdef HCD_SAMD_FAKE_FNUM + uint8_t start, current, prev; + uint8_t loop_count = (USB->HOST.STATUS.bit.SPEED == TUSB_SPEED_HIGH) ? 8 : 1; + for (uint8_t i = 0; i < loop_count; i++) { + start = USB->HOST.FLENHIGH.reg; + current = start; + // wait until wrap-around + prev = current; + while (current <= start) { + current = USB->HOST.FLENHIGH.reg; + if (current > prev) + break; + prev = current; + } + // wait until start is reached again + prev = current; + while (current > start) { + current = USB->HOST.FLENHIGH.reg; + if (current > prev) + break; + prev = current; + } + } + fake_fnum += 1; + return fake_fnum; +#else + return USB->HOST.FNUM.bit.FNUM; +#endif // HCD_SAMD_FAKE_FNUM +} + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport) +{ + TU_ASSERT(rhport == 0); + return USB->HOST.STATUS.bit.LINESTATE != 0; +} + +// 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) +{ + hcd_int_disable(rhport); + samd_free_all_pipes(); + USB->HOST.INTFLAG.reg |= USB->HOST.INTFLAG.reg; // clear pending + USB->HOST.CTRLB.bit.BUSRESET = 1; + fake_fnum = 0; +} + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport) +{ + while (USB->HOST.INTFLAG.bit.RST == 0) + ; + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RST; + USB->HOST.CTRLB.bit.SOFE = 1; + hcd_int_enable(rhport); +} + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport) +{ + (void) rhport; + + switch (USB->HOST.STATUS.bit.SPEED) { + case 0: + return TUSB_SPEED_FULL; + case 1: + return TUSB_SPEED_LOW; + case 2: + return TUSB_SPEED_HIGH; + default: + return TUSB_SPEED_INVALID; + } +} + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) +{ + (void) rhport; + + for (uint8_t pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + volatile usb_pipe_status_t* pipe_status = &usb_pipe_status_table[pipe]; + if (pipe_status->dev_addr == dev_addr) { + samd_free_pipe(pipe); + } + } +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const* ep_desc) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + const uint8_t ep_addr = ep_desc->bEndpointAddress; + const uint8_t bmAttributes = (ep_desc->bmAttributes.xfer) | + ((ep_desc->bmAttributes.sync) << 2) | + ((ep_desc->bmAttributes.usage) << 4); + + // configure the pipe + pipe = samd_configure_pipe(dev_addr, ep_addr); + if (pipe >= USB_PIPE_NUM) { + return false; + } + + // initial configuration + pipe_status = &usb_pipe_status_table[pipe]; + USB->HOST.HostPipe[pipe].PCFG.reg &= ~USB_HOST_PCFG_PTYPE_Msk; + USB->HOST.HostPipe[pipe].PCFG.bit.PTYPE = bmAttributes + 1; + USB->HOST.HostPipe[pipe].BINTERVAL.reg = ep_desc->bInterval; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_DTGL; + USB->HOST.HostPipe[pipe].PINTENCLR.reg = USB_HOST_PINTENCLR_MASK; + pipe_status->max_packet_size = ep_desc->wMaxPacketSize; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.SIZE = USB_PCKSIZE_ENUM(pipe_status->max_packet_size); + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.AUTO_ZLP = 0; + + return true; +} + +// Submit a special transfer to send 8-byte Setup Packet, when complete +// hcd_event_xfer_complete() must be invoked +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + // configure the pipe + pipe = samd_configure_pipe(dev_addr, 0); + if (pipe >= USB_PIPE_NUM) { + return false; + } + + // prepare transfer + pipe_status = &usb_pipe_status_table[pipe]; + usb_pipe_table[pipe].HostDescBank[0].ADDR.reg = (uint32_t) setup_packet; + pipe_status->xfer_remaining = 8; + pipe_status->xfer_length = 0; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = 8; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = 0; + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + + // clear pending interrupts + USB->HOST.HostPipe[pipe].PINTFLAG.reg |= USB->HOST.HostPipe[pipe].PINTFLAG.reg; + + // begin transfer + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_PFREEZE; + + return true; +} + +// Submit a transfer, when complete hcd_event_xfer_complete() must be invoked +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t* buffer, uint16_t buflen) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + // configure the pipe + pipe = samd_configure_pipe(dev_addr, ep_addr); + if (pipe >= USB_PIPE_NUM) { + return false; + } + + // prepare transfer + pipe_status = &usb_pipe_status_table[pipe]; + usb_pipe_table[pipe].HostDescBank[0].ADDR.reg = (uint32_t) buffer; + pipe_status->xfer_remaining = buflen; + pipe_status->xfer_length = 0; + // receive data + if (tu_edpt_dir(pipe_status->ep_addr) == TUSB_DIR_IN) { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = 0; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = pipe_status->max_packet_size; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + } + // send data + else { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = + (pipe_status->xfer_remaining < pipe_status->max_packet_size) ? pipe_status->xfer_remaining + : pipe_status->max_packet_size; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = 0; + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + } + + // clear pending interrupts + USB->HOST.HostPipe[pipe].PINTFLAG.reg |= USB->HOST.HostPipe[pipe].PINTFLAG.reg; + + // begin transfer + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_PFREEZE; + + return true; +} + +// 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) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + TU_LOG3("hcd_edpt_abort_xfer(dev_addr=%02X, ep_addr=%02X)=", dev_addr, ep_addr); + + // find the pipe + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + if ((pipe_status->dev_addr == dev_addr) && (pipe_status->ep_addr == ep_addr)) { + break; + } + } + + // pipe not found + if (pipe >= USB_PIPE_NUM) { + TU_LOG3("ERR_NO_PIPE\r\n"); + return false; + } + TU_LOG3("%d\r\n", pipe); + + // no transfer in progress + if (USB->HOST.HostPipe[pipe].PSTATUS.bit.PFREEZE == 1) { + return false; + } + + // abort the transfer + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + pipe_status = &usb_pipe_status_table[pipe]; + pipe_status->xfer_length = 0; + pipe_status->xfer_remaining = 0; + + return true; +} + +// clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + TU_LOG3("hcd_edpt_clear_stall(dev_addr=%02X, ep_addr=%02X)=", dev_addr, ep_addr); + + // find the pipe + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + if ((pipe_status->dev_addr == dev_addr) && (pipe_status->ep_addr == ep_addr)) { + break; + } + } + + // pipe not found + if (pipe >= USB_PIPE_NUM) { + TU_LOG3("ERR_NO_PIPE\r\n"); + return false; + } + TU_LOG3("%d\r\n", pipe); + + // clear pending interrupts + USB->HOST.HostPipe[pipe].PINTFLAG.reg |= USB->HOST.HostPipe[pipe].PINTFLAG.reg; + + // clear stalled state + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_DTGL; + + return true; +} + +#endif diff --git a/src/portable/nxp/khci/hcd_khci.c b/src/portable/nxp/khci/hcd_khci.c index 056dbf40b..45732a866 100644 --- a/src/portable/nxp/khci/hcd_khci.c +++ b/src/portable/nxp/khci/hcd_khci.c @@ -36,6 +36,7 @@ #endif #include "host/hcd.h" +#include "host/usbh.h" //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION @@ -541,6 +542,11 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const return true; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + /* The address of buffer must be aligned to 4 byte boundary. And it must be at least 4 bytes long. * DMA writes data in 4 byte unit */ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) diff --git a/src/portable/nxp/lpc17_40/hcd_lpc17_40.c b/src/portable/nxp/lpc17_40/hcd_lpc17_40.c index 090d1ba69..fea3e2a66 100644 --- a/src/portable/nxp/lpc17_40/hcd_lpc17_40.c +++ b/src/portable/nxp/lpc17_40/hcd_lpc17_40.c @@ -31,6 +31,7 @@ #include "chip.h" #include "host/hcd.h" +#include "host/usbh.h" void hcd_int_enable(uint8_t rhport) { diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c index 7d5cfb5b0..7c637ce0c 100644 --- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c @@ -297,7 +297,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { dcd_reg->EPLISTSTART = (uint32_t) _dcd.ep; dcd_reg->DATABUFSTART = tu_align((uint32_t) &_dcd, TU_BIT(22)); // 22-bit alignment - dcd_reg->INTSTAT |= dcd_reg->INTSTAT; // clear all pending interrupt + dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt dcd_reg->INTEN = INT_DEVICE_STATUS_MASK; dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK | DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK; @@ -563,7 +563,8 @@ void dcd_int_handler(uint8_t rhport) uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT; - uint32_t int_status = dcd_reg->INTSTAT & dcd_reg->INTEN; + uint32_t int_status = dcd_reg->INTSTAT; + int_status &= dcd_reg->INTEN; dcd_reg->INTSTAT = int_status; // Acknowledge handled interrupt if (int_status == 0) return; diff --git a/src/portable/ohci/ohci.c b/src/portable/ohci/ohci.c index ce35eab70..81091c9a7 100644 --- a/src/portable/ohci/ohci.c +++ b/src/portable/ohci/ohci.c @@ -38,6 +38,7 @@ #include "osal/osal.h" #include "host/hcd.h" +#include "host/usbh.h" #include "ohci.h" // TODO remove @@ -328,13 +329,13 @@ static void ed_init(ohci_ed_t *p_ed, uint8_t dev_addr, uint16_t ep_size, uint8_t tu_memclr(p_ed, sizeof(ohci_ed_t)); } - hcd_devtree_info_t devtree_info; - hcd_devtree_get_info(dev_addr, &devtree_info); + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); p_ed->dev_addr = dev_addr; p_ed->ep_number = ep_addr & 0x0F; p_ed->pid = (xfer_type == TUSB_XFER_CONTROL) ? PID_FROM_TD : (tu_edpt_dir(ep_addr) ? PID_IN : PID_OUT); - p_ed->speed = devtree_info.speed; + p_ed->speed = bus_info.speed; p_ed->is_iso = (xfer_type == TUSB_XFER_ISOCHRONOUS) ? 1 : 0; p_ed->max_packet_size = ep_size; @@ -451,7 +452,6 @@ static void td_insert_to_ed(ohci_ed_t* p_ed, ohci_gtd_t * p_gtd) //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ - bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) { (void) rhport; @@ -486,6 +486,11 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const return true; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { (void) rhport; diff --git a/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c b/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c index 6422afff1..d59a2b4ee 100644 --- a/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c +++ b/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c @@ -114,14 +114,19 @@ void hcd_int_disable(uint8_t rhport) { //--------------------------------------------------------------------+ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const *desc_ep) { - hcd_devtree_info_t dev_tree; - hcd_devtree_get_info(dev_addr, &dev_tree); - bool const need_pre = (dev_tree.hub_addr && dev_tree.speed == TUSB_SPEED_LOW); + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); + bool const need_pre = (bus_info.hub_addr && bus_info.speed == TUSB_SPEED_LOW); uint8_t const pio_rhport = RHPORT_PIO(rhport); return pio_usb_host_endpoint_open(pio_rhport, dev_addr, (uint8_t const *) desc_ep, need_pre); } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + return pio_usb_host_endpoint_close(pio_rhport, daddr, ep_addr); +} + bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { uint8_t const pio_rhport = RHPORT_PIO(rhport); return pio_usb_host_endpoint_transfer(pio_rhport, dev_addr, ep_addr, buffer, buflen); diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 358ce84bc..a89c2f42b 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -190,8 +190,9 @@ static void __tusb_irq_path_func(hw_handle_buff_status)(void) { bool done = hw_endpoint_xfer_continue(ep); if (done) { // Notify - dcd_event_xfer_complete(0, ep->ep_addr, ep->xferred_len, XFER_RESULT_SUCCESS, true); + const uint16_t xferred_len = ep->xferred_len; hw_endpoint_reset_transfer(ep); + dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); } remaining_buffers &= ~bit; } diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 478c6e789..cd8c905d5 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -514,7 +514,6 @@ void hcd_int_disable(uint8_t rhport) //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ - bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) { (void) rhport; @@ -535,6 +534,11 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const return true; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { (void) rhport; diff --git a/src/portable/renesas/rusb2/hcd_rusb2.c b/src/portable/renesas/rusb2/hcd_rusb2.c index 4c81b05be..6f6d27d0e 100644 --- a/src/portable/renesas/rusb2/hcd_rusb2.c +++ b/src/portable/renesas/rusb2/hcd_rusb2.c @@ -30,6 +30,7 @@ #if CFG_TUH_ENABLED && defined(TUP_USBIP_RUSB2) #include "host/hcd.h" +#include "host/usbh.h" #include "rusb2_type.h" #if TU_CHECK_MCU(OPT_MCU_RX63X, OPT_MCU_RX65X, OPT_MCU_RX72N) @@ -662,13 +663,13 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const if (0 == epn) { rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK; - hcd_devtree_info_t devtree; - hcd_devtree_get_info(dev_addr, &devtree); + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); uint16_t volatile *devadd = (uint16_t volatile *)(uintptr_t) &rusb->DEVADD[0]; devadd += dev_addr; while (rusb->DCPCTR_b.PBUSY) {} rusb->DCPMAXP = (dev_addr << 12) | mps; - *devadd = (TUSB_SPEED_FULL == devtree.speed) ? RUSB2_DEVADD_USBSPD_FS : RUSB2_DEVADD_USBSPD_LS; + *devadd = (TUSB_SPEED_FULL == bus_info.speed) ? RUSB2_DEVADD_USBSPD_FS : RUSB2_DEVADD_USBSPD_LS; _hcd.ctl_mps[dev_addr] = mps; return true; } @@ -718,6 +719,11 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const return true; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { bool r; diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c index ef58957bb..3b9825e25 100644 --- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c @@ -117,6 +117,8 @@ #include "fsdev_stm32.h" #elif defined(TUP_USBIP_FSDEV_CH32) #include "fsdev_ch32.h" +#elif defined(TUP_USBIP_FSDEV_AT32) + #include "fsdev_at32.h" #else #error "Unknown USB IP" #endif @@ -331,8 +333,13 @@ static void handle_ctr_rx(uint32_t ep_id) { xfer_ctl_t* xfer = xfer_ctl_ptr(ep_num, TUSB_DIR_OUT); uint8_t buf_id; - if (is_iso) { - buf_id = (ep_reg & USB_EP_DTOG_RX) ? 0 : 1; // ISO are double buffered +#if FSDEV_USE_SBUF_ISO == 0 + bool const dbl_buf = is_iso; +#else + bool const dbl_buf = false; +#endif + if (dbl_buf) { + buf_id = (ep_reg & USB_EP_DTOG_RX) ? 0 : 1; } else { buf_id = BTABLE_BUF_RX; } @@ -527,10 +534,16 @@ static uint8_t dcd_ep_alloc(uint8_t ep_addr, uint8_t ep_type) return i; } +#if FSDEV_USE_SBUF_ISO == 0 + bool const dbl_buf = ep_type == TUSB_XFER_ISOCHRONOUS; +#else + bool const dbl_buf = false; +#endif + // If EP of current direction is not allocated - // Except for ISO endpoint, both direction should be free + // For double-buffered mode both directions needs to be free if (!ep_alloc_status[i].allocated[dir] && - (ep_type != TUSB_XFER_ISOCHRONOUS || !ep_alloc_status[i].allocated[dir ^ 1])) { + (!dbl_buf || !ep_alloc_status[i].allocated[dir ^ 1])) { // Check if EP number is the same if (ep_alloc_status[i].ep_num == 0xFF || ep_alloc_status[i].ep_num == epnum) { // One EP pair has to be the same type @@ -652,9 +665,7 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet uint8_t const dir = tu_edpt_dir(ep_addr); uint8_t const ep_idx = dcd_ep_alloc(ep_addr, TUSB_XFER_ISOCHRONOUS); - /* Create a packet memory buffer area. Enable double buffering for devices with 2048 bytes PMA, - for smaller devices double buffering occupy too much space. */ -#if FSDEV_PMA_SIZE > 1024u +#if CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP != 0 uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, true); uint16_t pma_addr2 = pma_addr >> 16; #else @@ -662,8 +673,12 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet uint16_t pma_addr2 = pma_addr; #endif +#if FSDEV_USE_SBUF_ISO == 0 btable_set_addr(ep_idx, 0, pma_addr); btable_set_addr(ep_idx, 1, pma_addr2); +#else + btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr); +#endif xfer_ctl_t* xfer = xfer_ctl_ptr(ep_num, dir); xfer->ep_idx = ep_idx; @@ -684,10 +699,23 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *desc_ep) uint32_t ep_reg = ep_read(ep_idx) & ~USB_EPREG_MASK; ep_reg |= tu_edpt_number(ep_addr) | USB_EP_ISOCHRONOUS | USB_EP_CTR_TX | USB_EP_CTR_RX; +#if FSDEV_USE_SBUF_ISO != 0 + ep_reg |= USB_EP_KIND; + + ep_change_status(&ep_reg, dir, EP_STAT_DISABLED); + ep_change_dtog(&ep_reg, dir, 0); + + if (dir == TUSB_DIR_IN) { + ep_reg &= ~(USB_EPRX_STAT | USB_EP_DTOG_RX); + } else { + ep_reg &= ~(USB_EPTX_STAT | USB_EP_DTOG_TX); + } +#else ep_change_status(&ep_reg, TUSB_DIR_IN, EP_STAT_DISABLED); ep_change_status(&ep_reg, TUSB_DIR_OUT, EP_STAT_DISABLED); ep_change_dtog(&ep_reg, dir, 0); ep_change_dtog(&ep_reg, (tusb_dir_t)(1 - dir), 1); +#endif ep_write(ep_idx, ep_reg, true); @@ -702,7 +730,12 @@ static void dcd_transmit_packet(xfer_ctl_t *xfer, uint16_t ep_ix) { bool const is_iso = ep_is_iso(ep_reg); uint8_t buf_id; - if (is_iso) { +#if FSDEV_USE_SBUF_ISO == 0 + bool const dbl_buf = is_iso; +#else + bool const dbl_buf = false; +#endif + if (dbl_buf) { buf_id = (ep_reg & USB_EP_DTOG_TX) ? 1 : 0; } else { buf_id = BTABLE_BUF_TX; diff --git a/src/portable/st/stm32_fsdev/fsdev_at32.h b/src/portable/st/stm32_fsdev/fsdev_at32.h new file mode 100644 index 000000000..f7ee89995 --- /dev/null +++ b/src/portable/st/stm32_fsdev/fsdev_at32.h @@ -0,0 +1,225 @@ +/* +* The MIT License (MIT) + * + * Copyright (c) 2024, hathach (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. + * + */ +#ifndef TUSB_FSDEV_AT32_H +#define TUSB_FSDEV_AT32_H + +#include "common/tusb_compiler.h" + +#if CFG_TUSB_MCU == OPT_MCU_AT32F403A_407 + #include "at32f403a_407.h" + +#elif CFG_TUSB_MCU == OPT_MCU_AT32F413 + #include "at32f413.h" + +#endif + +#define FSDEV_PMA_SIZE (512u) +#define FSDEV_USE_SBUF_ISO 0 +#define FSDEV_REG_BASE (APB1PERIPH_BASE + 0x00005C00UL) +#define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00006000UL) + +#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 +#endif + +/**************************** ISTR interrupt events *************************/ +#define USB_ISTR_CTR ((uint16_t)0x8000U) /*!< Correct TRansfer (clear-only bit) */ +#define USB_ISTR_PMAOVR ((uint16_t)0x4000U) /*!< DMA OVeR/underrun (clear-only bit) */ +#define USB_ISTR_ERR ((uint16_t)0x2000U) /*!< ERRor (clear-only bit) */ +#define USB_ISTR_WKUP ((uint16_t)0x1000U) /*!< WaKe UP (clear-only bit) */ +#define USB_ISTR_SUSP ((uint16_t)0x0800U) /*!< SUSPend (clear-only bit) */ +#define USB_ISTR_RESET ((uint16_t)0x0400U) /*!< RESET (clear-only bit) */ +#define USB_ISTR_SOF ((uint16_t)0x0200U) /*!< Start Of Frame (clear-only bit) */ +#define USB_ISTR_ESOF ((uint16_t)0x0100U) /*!< Expected Start Of Frame (clear-only bit) */ +#define USB_ISTR_DIR ((uint16_t)0x0010U) /*!< DIRection of transaction (read-only bit) */ +#define USB_ISTR_EP_ID ((uint16_t)0x000FU) /*!< EndPoint IDentifier (read-only bit) */ + +/* Legacy defines */ +#define USB_ISTR_PMAOVRM USB_ISTR_PMAOVR + +#define USB_CLR_CTR (~USB_ISTR_CTR) /*!< clear Correct TRansfer bit */ +#define USB_CLR_PMAOVR (~USB_ISTR_PMAOVR) /*!< clear DMA OVeR/underrun bit*/ +#define USB_CLR_ERR (~USB_ISTR_ERR) /*!< clear ERRor bit */ +#define USB_CLR_WKUP (~USB_ISTR_WKUP) /*!< clear WaKe UP bit */ +#define USB_CLR_SUSP (~USB_ISTR_SUSP) /*!< clear SUSPend bit */ +#define USB_CLR_RESET (~USB_ISTR_RESET) /*!< clear RESET bit */ +#define USB_CLR_SOF (~USB_ISTR_SOF) /*!< clear Start Of Frame bit */ +#define USB_CLR_ESOF (~USB_ISTR_ESOF) /*!< clear Expected Start Of Frame bit */ + +/* Legacy defines */ +#define USB_CLR_PMAOVRM USB_CLR_PMAOVR + +/************************* CNTR control register bits definitions ***********/ +#define USB_CNTR_CTRM ((uint16_t)0x8000U) /*!< Correct TRansfer Mask */ +#define USB_CNTR_PMAOVR ((uint16_t)0x4000U) /*!< DMA OVeR/underrun Mask */ +#define USB_CNTR_ERRM ((uint16_t)0x2000U) /*!< ERRor Mask */ +#define USB_CNTR_WKUPM ((uint16_t)0x1000U) /*!< WaKe UP Mask */ +#define USB_CNTR_SUSPM ((uint16_t)0x0800U) /*!< SUSPend Mask */ +#define USB_CNTR_RESETM ((uint16_t)0x0400U) /*!< RESET Mask */ +#define USB_CNTR_SOFM ((uint16_t)0x0200U) /*!< Start Of Frame Mask */ +#define USB_CNTR_ESOFM ((uint16_t)0x0100U) /*!< Expected Start Of Frame Mask */ +#define USB_CNTR_RESUME ((uint16_t)0x0010U) /*!< RESUME request */ +#define USB_CNTR_FSUSP ((uint16_t)0x0008U) /*!< Force SUSPend */ +#define USB_CNTR_LPMODE ((uint16_t)0x0004U) /*!< Low-power MODE */ +#define USB_CNTR_PDWN ((uint16_t)0x0002U) /*!< Power DoWN */ +#define USB_CNTR_FRES ((uint16_t)0x0001U) /*!< Force USB RESet */ + +/* Legacy defines */ +#define USB_CNTR_PMAOVRM USB_CNTR_PMAOVR +#define USB_CNTR_LP_MODE USB_CNTR_LPMODE + +/******************** FNR Frame Number Register bit definitions ************/ +#define USB_FNR_RXDP ((uint16_t)0x8000U) /*!< status of D+ data line */ +#define USB_FNR_RXDM ((uint16_t)0x4000U) /*!< status of D- data line */ +#define USB_FNR_LCK ((uint16_t)0x2000U) /*!< LoCKed */ +#define USB_FNR_LSOF ((uint16_t)0x1800U) /*!< Lost SOF */ +#define USB_FNR_FN ((uint16_t)0x07FFU) /*!< Frame Number */ + +/******************** DADDR Device ADDRess bit definitions ****************/ +#define USB_DADDR_EF ((uint8_t)0x80U) /*!< USB device address Enable Function */ +#define USB_DADDR_ADD ((uint8_t)0x7FU) /*!< USB device address */ + +/****************************** Endpoint register *************************/ +#define USB_EP0R USB_BASE /*!< endpoint 0 register address */ +#define USB_EP1R (USB_BASE + 0x04U) /*!< endpoint 1 register address */ +#define USB_EP2R (USB_BASE + 0x08U) /*!< endpoint 2 register address */ +#define USB_EP3R (USB_BASE + 0x0CU) /*!< endpoint 3 register address */ +#define USB_EP4R (USB_BASE + 0x10U) /*!< endpoint 4 register address */ +#define USB_EP5R (USB_BASE + 0x14U) /*!< endpoint 5 register address */ +#define USB_EP6R (USB_BASE + 0x18U) /*!< endpoint 6 register address */ +#define USB_EP7R (USB_BASE + 0x1CU) /*!< endpoint 7 register address */ +/* bit positions */ +#define USB_EP_CTR_RX ((uint16_t)0x8000U) /*!< EndPoint Correct TRansfer RX */ +#define USB_EP_DTOG_RX ((uint16_t)0x4000U) /*!< EndPoint Data TOGGLE RX */ +#define USB_EPRX_STAT ((uint16_t)0x3000U) /*!< EndPoint RX STATus bit field */ +#define USB_EP_SETUP ((uint16_t)0x0800U) /*!< EndPoint SETUP */ +#define USB_EP_T_FIELD ((uint16_t)0x0600U) /*!< EndPoint TYPE */ +#define USB_EP_KIND ((uint16_t)0x0100U) /*!< EndPoint KIND */ +#define USB_EP_CTR_TX ((uint16_t)0x0080U) /*!< EndPoint Correct TRansfer TX */ +#define USB_EP_DTOG_TX ((uint16_t)0x0040U) /*!< EndPoint Data TOGGLE TX */ +#define USB_EPTX_STAT ((uint16_t)0x0030U) /*!< EndPoint TX STATus bit field */ +#define USB_EPADDR_FIELD ((uint16_t)0x000FU) /*!< EndPoint ADDRess FIELD */ + +/* EndPoint REGister MASK (no toggle fields) */ +#define USB_EPREG_MASK (USB_EP_CTR_RX|USB_EP_SETUP|USB_EP_T_FIELD|USB_EP_KIND|USB_EP_CTR_TX|USB_EPADDR_FIELD) + /*!< EP_TYPE[1:0] EndPoint TYPE */ +#define USB_EP_TYPE_MASK ((uint16_t)0x0600U) /*!< EndPoint TYPE Mask */ +#define USB_EP_BULK ((uint16_t)0x0000U) /*!< EndPoint BULK */ +#define USB_EP_CONTROL ((uint16_t)0x0200U) /*!< EndPoint CONTROL */ +#define USB_EP_ISOCHRONOUS ((uint16_t)0x0400U) /*!< EndPoint ISOCHRONOUS */ +#define USB_EP_INTERRUPT ((uint16_t)0x0600U) /*!< EndPoint INTERRUPT */ +#define USB_EP_T_MASK ((uint16_t) ~USB_EP_T_FIELD & USB_EPREG_MASK) + +#define USB_EPKIND_MASK ((uint16_t) ~USB_EP_KIND & USB_EPREG_MASK) /*!< EP_KIND EndPoint KIND */ + /*!< STAT_TX[1:0] STATus for TX transfer */ +#define USB_EP_TX_DIS ((uint16_t)0x0000U) /*!< EndPoint TX DISabled */ +#define USB_EP_TX_STALL ((uint16_t)0x0010U) /*!< EndPoint TX STALLed */ +#define USB_EP_TX_NAK ((uint16_t)0x0020U) /*!< EndPoint TX NAKed */ +#define USB_EP_TX_VALID ((uint16_t)0x0030U) /*!< EndPoint TX VALID */ +#define USB_EPTX_DTOG1 ((uint16_t)0x0010U) /*!< EndPoint TX Data TOGgle bit1 */ +#define USB_EPTX_DTOG2 ((uint16_t)0x0020U) /*!< EndPoint TX Data TOGgle bit2 */ +#define USB_EPTX_DTOGMASK (USB_EPTX_STAT|USB_EPREG_MASK) + /*!< STAT_RX[1:0] STATus for RX transfer */ +#define USB_EP_RX_DIS ((uint16_t)0x0000U) /*!< EndPoint RX DISabled */ +#define USB_EP_RX_STALL ((uint16_t)0x1000U) /*!< EndPoint RX STALLed */ +#define USB_EP_RX_NAK ((uint16_t)0x2000U) /*!< EndPoint RX NAKed */ +#define USB_EP_RX_VALID ((uint16_t)0x3000U) /*!< EndPoint RX VALID */ +#define USB_EPRX_DTOG1 ((uint16_t)0x1000U) /*!< EndPoint RX Data TOGgle bit1 */ +#define USB_EPRX_DTOG2 ((uint16_t)0x2000U) /*!< EndPoint RX Data TOGgle bit1 */ +#define USB_EPRX_DTOGMASK (USB_EPRX_STAT|USB_EPREG_MASK) + +#include "fsdev_type.h" + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +#if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) +static const IRQn_Type fsdev_irq[] = { + USBFS_H_CAN1_TX_IRQn, + USBFS_L_CAN1_RX0_IRQn, + USBFSWakeUp_IRQn +}; +enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) }; + +#else + #error "Unsupported MCU" +#endif + +void dcd_int_enable(uint8_t rhport) { + (void)rhport; + #if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) + // AT32F403A/407 devices allow to remap the USB interrupt vectors from + // shared USB/CAN IRQs to separate CAN and USB IRQs. + // This dynamically checks if this remap is active to enable the right IRQs. + if (CRM->intmap_bit.usbintmap) { + NVIC_DisableIRQ(USBFS_MAPH_IRQn); + NVIC_DisableIRQ(USBFS_MAPL_IRQn); + NVIC_DisableIRQ(USBFSWakeUp_IRQn); + } else + #endif + { + for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) { + NVIC_EnableIRQ(fsdev_irq[i]); + } + } +} + +void dcd_int_disable(uint8_t rhport) { + (void)rhport; + #if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) + // AT32F403A/407 devices allow to remap the USB interrupt vectors from + // shared USB/CAN IRQs to separate CAN and USB IRQs. + // This dynamically checks if this remap is active to enable the right IRQs. + if (CRM->intmap_bit.usbintmap) { + NVIC_DisableIRQ(USBFS_MAPH_IRQn); + NVIC_DisableIRQ(USBFS_MAPL_IRQn); + NVIC_DisableIRQ(USBFSWakeUp_IRQn); + } else + #endif + { + for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) { + NVIC_DisableIRQ(fsdev_irq[i]); + } + } +} + +void dcd_disconnect(uint8_t rhport) { + (void) rhport; + /* disable usb phy */ + FSDEV_REG->CNTR |= USB_CNTR_PDWN; + /* D+ 1.5k pull-up disable, USB->cfg_bit.puo = TRUE; */ + *(uint32_t *)(FSDEV_REG_BASE+0x60) |= (1u<<1); +} + +void dcd_connect(uint8_t rhport) { + (void) rhport; + /* enable usb phy */ + FSDEV_REG->CNTR &= ~USB_CNTR_PDWN; + /* Dp 1.5k pull-up enable, USB->cfg_bit.puo = 0; */ + *(uint32_t *)(FSDEV_REG_BASE+0x60) &= ~(1u<<1); +} + +#endif diff --git a/src/portable/st/stm32_fsdev/fsdev_ch32.h b/src/portable/st/stm32_fsdev/fsdev_ch32.h index 518197c47..ceebb6dab 100644 --- a/src/portable/st/stm32_fsdev/fsdev_ch32.h +++ b/src/portable/st/stm32_fsdev/fsdev_ch32.h @@ -54,9 +54,14 @@ #endif #define FSDEV_PMA_SIZE (512u) +#define FSDEV_USE_SBUF_ISO 0 #define FSDEV_REG_BASE (APB1PERIPH_BASE + 0x00005C00UL) #define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00006000UL) +#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 +#endif + /**************************** ISTR interrupt events *************************/ #define USB_ISTR_CTR ((uint16_t)0x8000U) /*!< Correct TRansfer (clear-only bit) */ #define USB_ISTR_PMAOVR ((uint16_t)0x4000U) /*!< DMA OVeR/underrun (clear-only bit) */ diff --git a/src/portable/st/stm32_fsdev/fsdev_stm32.h b/src/portable/st/stm32_fsdev/fsdev_stm32.h index ccf31e035..95f40269a 100644 --- a/src/portable/st/stm32_fsdev/fsdev_stm32.h +++ b/src/portable/st/stm32_fsdev/fsdev_stm32.h @@ -36,6 +36,7 @@ #include "stm32f0xx.h" #define FSDEV_PMA_SIZE (1024u) #define FSDEV_REG_BASE USB_BASE + #define FSDEV_HAS_SBUF_ISO 0 // F0x2 models are crystal-less // All have internal D+ pull-up // 070RB: 2 x 16 bits/word memory LPM Support, BCD Support @@ -44,6 +45,7 @@ #elif CFG_TUSB_MCU == OPT_MCU_STM32F1 #include "stm32f1xx.h" #define FSDEV_PMA_SIZE (512u) + #define FSDEV_HAS_SBUF_ISO 0 // NO internal Pull-ups // *B, and *C: 2 x 16 bits/word @@ -55,6 +57,7 @@ defined(STM32F373xC) #include "stm32f3xx.h" #define FSDEV_PMA_SIZE (512u) + #define FSDEV_HAS_SBUF_ISO 0 // NO internal Pull-ups // *B, and *C: 1 x 16 bits/word // PMA dedicated to USB (no sharing with CAN) @@ -64,6 +67,7 @@ defined(STM32F303xD) || defined(STM32F303xE) #include "stm32f3xx.h" #define FSDEV_PMA_SIZE (1024u) + #define FSDEV_HAS_SBUF_ISO 0 // NO internal Pull-ups // *6, *8, *D, and *E: 2 x 16 bits/word LPM Support // When CAN clock is enabled, USB can use first 768 bytes ONLY. @@ -71,18 +75,22 @@ #elif CFG_TUSB_MCU == OPT_MCU_STM32L0 #include "stm32l0xx.h" #define FSDEV_PMA_SIZE (1024u) + #define FSDEV_HAS_SBUF_ISO 0 #elif CFG_TUSB_MCU == OPT_MCU_STM32L1 #include "stm32l1xx.h" #define FSDEV_PMA_SIZE (512u) + #define FSDEV_HAS_SBUF_ISO 0 #elif CFG_TUSB_MCU == OPT_MCU_STM32G4 #include "stm32g4xx.h" #define FSDEV_PMA_SIZE (1024u) + #define FSDEV_HAS_SBUF_ISO 0 #elif CFG_TUSB_MCU == OPT_MCU_STM32G0 #include "stm32g0xx.h" #define FSDEV_PMA_SIZE (2048u) + #define FSDEV_HAS_SBUF_ISO 1 #define USB USB_DRD_FS #define USB_EP_CTR_RX USB_EP_VTRX @@ -107,6 +115,7 @@ #elif CFG_TUSB_MCU == OPT_MCU_STM32C0 #include "stm32c0xx.h" #define FSDEV_PMA_SIZE (2048u) + #define FSDEV_HAS_SBUF_ISO 1 #define USB USB_DRD_FS #define USB_EP_CTR_RX USB_CHEP_VTRX #define USB_EP_CTR_TX USB_CHEP_VTTX @@ -121,6 +130,7 @@ #elif CFG_TUSB_MCU == OPT_MCU_STM32H5 #include "stm32h5xx.h" #define FSDEV_PMA_SIZE (2048u) + #define FSDEV_HAS_SBUF_ISO 1 #define USB USB_DRD_FS #define USB_EP_CTR_RX USB_EP_VTRX @@ -145,16 +155,19 @@ #elif CFG_TUSB_MCU == OPT_MCU_STM32WB #include "stm32wbxx.h" #define FSDEV_PMA_SIZE (1024u) + #define FSDEV_HAS_SBUF_ISO 0 /* ST provided header has incorrect value of USB_PMAADDR */ #define FSDEV_PMA_BASE USB1_PMAADDR #elif CFG_TUSB_MCU == OPT_MCU_STM32L4 #include "stm32l4xx.h" #define FSDEV_PMA_SIZE (1024u) + #define FSDEV_HAS_SBUF_ISO 0 #elif CFG_TUSB_MCU == OPT_MCU_STM32L5 #include "stm32l5xx.h" #define FSDEV_PMA_SIZE (1024u) + #define FSDEV_HAS_SBUF_ISO 0 #ifndef USB_PMAADDR #define USB_PMAADDR (USB_BASE + (USB_PMAADDR_NS - USB_BASE_NS)) @@ -163,6 +176,7 @@ #elif CFG_TUSB_MCU == OPT_MCU_STM32U5 #include "stm32u5xx.h" #define FSDEV_PMA_SIZE (2048u) + #define FSDEV_HAS_SBUF_ISO 1 #define USB USB_DRD_FS #define USB_EP_CTR_RX USB_EP_VTRX @@ -187,6 +201,7 @@ #elif CFG_TUSB_MCU == OPT_MCU_STM32U0 #include "stm32u0xx.h" #define FSDEV_PMA_SIZE (2048u) + #define FSDEV_HAS_SBUF_ISO 1 #define USB USB_DRD_FS #define USB_EP_CTR_RX USB_EP_VTRX @@ -248,6 +263,36 @@ #define USB_ISTR_ALL_EVENTS (USB_ISTR_PMAOVR | USB_ISTR_ERR | USB_ISTR_WKUP | USB_ISTR_SUSP | \ USB_ISTR_RESET | USB_ISTR_SOF | USB_ISTR_ESOF | USB_ISTR_L1REQ_FORCED ) +#ifndef FSDEV_HAS_SBUF_ISO + #error "FSDEV_HAS_SBUF_ISO not defined" +#endif + +#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP + // Default configuration for double-buffered isochronous endpoints: + // - Enable double buffering on devices with >1KB Packet Memory Area (PMA) + // to improve isochronous transfer reliability and performance + // - Disable on devices with limited PMA to conserve memory space + #if FSDEV_PMA_SIZE > 1024u + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 1 + #else + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 + #endif +#endif + +#if FSDEV_HAS_SBUF_ISO != 0 && CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP == 0 + // SBUF_ISO configuration: + // - Some STM32 devices have special hardware support for single-buffered isochronous endpoints + // - When SBUF_ISO bit is available and double buffering is disabled: + // Enable SBUF_ISO to optimize endpoint register usage (one half of endpoint pair register) + #define FSDEV_USE_SBUF_ISO 1 +#else + // When either: + // - Hardware doesn't support SBUF_ISO feature, or + // - Double buffering is enabled for isochronous endpoints + // We must use the entire endpoint pair register + #define FSDEV_USE_SBUF_ISO 0 +#endif + //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index c461d9a79..f10f0bdc3 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -39,14 +39,9 @@ #define DWC2_DEBUG 2 #include "device/dcd.h" +#include "device/usbd_pvt.h" #include "dwc2_common.h" -#if TU_CHECK_MCU(OPT_MCU_GD32VF103) - #define DWC2_EP_COUNT(_dwc2) DWC2_EP_MAX -#else - #define DWC2_EP_COUNT(_dwc2) ((_dwc2)->ghwcfg2_bm.num_dev_ep + 1) -#endif - //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM //--------------------------------------------------------------------+ @@ -58,6 +53,7 @@ typedef struct { uint8_t interval; } xfer_ctl_t; +// This variable is modified from ISR context, so it must be protected by critical section static xfer_ctl_t xfer_status[DWC2_EP_MAX][2]; #define XFER_CTL_BASE(_ep, _dir) (&xfer_status[_ep][_dir]) @@ -79,6 +75,16 @@ CFG_TUD_MEM_SECTION static struct { TUD_EPBUF_DEF(setup_packet, 8); } _dcd_usbbuf; +TU_ATTR_ALWAYS_INLINE static inline uint8_t dwc2_ep_count(const dwc2_regs_t* dwc2) { + #if TU_CHECK_MCU(OPT_MCU_GD32VF103) + return DWC2_EP_MAX; + #else + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return ghwcfg2.num_dev_ep + 1; + #endif +} + + //-------------------------------------------------------------------- // DMA //-------------------------------------------------------------------- @@ -102,7 +108,8 @@ bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { TU_ATTR_ALWAYS_INLINE static inline bool dma_device_enabled(const dwc2_regs_t* dwc2) { (void) dwc2; // Internal DMA only - return CFG_TUD_DWC2_DMA_ENABLE && dwc2->ghwcfg2_bm.arch == GHWCFG2_ARCH_INTERNAL_DMA; + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return CFG_TUD_DWC2_DMA_ENABLE && ghwcfg2.arch == GHWCFG2_ARCH_INTERNAL_DMA; } static void dma_setup_prepare(uint8_t rhport) { @@ -250,20 +257,15 @@ static void edpt_activate(uint8_t rhport, const tusb_desc_endpoint_t* p_endpoint xfer->interval = p_endpoint_desc->bInterval; // Endpoint control - union { - uint32_t value; - dwc2_depctl_t bm; - } depctl; - depctl.value = 0; - - depctl.bm.mps = xfer->max_size; - depctl.bm.active = 1; - depctl.bm.type = p_endpoint_desc->bmAttributes.xfer; + dwc2_depctl_t depctl = {.value = 0}; + depctl.mps = xfer->max_size; + depctl.active = 1; + depctl.type = p_endpoint_desc->bmAttributes.xfer; if (p_endpoint_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS) { - depctl.bm.set_data0_iso_even = 1; + depctl.set_data0_iso_even = 1; } if (dir == TUSB_DIR_IN) { - depctl.bm.tx_fifo_num = epnum; + depctl.tx_fifo_num = epnum; } dwc2_dep_t* dep = &dwc2->ep[dir == TUSB_DIR_IN ? 0 : 1][epnum]; @@ -321,6 +323,9 @@ static void edpt_disable(uint8_t rhport, uint8_t ep_addr, bool stall) { } } +// Since this function returns void, it is not possible to return a boolean success message +// We must make sure that this function is not called when the EP is disabled +// Must be called from critical section static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uint8_t dir) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); xfer_ctl_t* const xfer = XFER_CTL_BASE(epnum, dir); @@ -343,31 +348,22 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin } // transfer size: A full OUT transfer (multiple packets, possibly) triggers XFRC. - union { - uint32_t value; - dwc2_ep_tsize_t bm; - } deptsiz; - deptsiz.value = 0; - deptsiz.bm.xfer_size = total_bytes; - deptsiz.bm.packet_count = num_packets; - + dwc2_ep_tsize_t deptsiz = {.value = 0}; + deptsiz.xfer_size = total_bytes; + deptsiz.packet_count = num_packets; dep->tsiz = deptsiz.value; // control - union { - dwc2_depctl_t bm; - uint32_t value; - } depctl; - depctl.value = dep->ctl; - - depctl.bm.clear_nak = 1; - depctl.bm.enable = 1; - if (depctl.bm.type == DEPCTL_EPTYPE_ISOCHRONOUS && xfer->interval == 1) { - const uint32_t odd_now = (dwc2->dsts_bm.frame_number & 1u); + dwc2_depctl_t depctl = {.value = dep->ctl}; + depctl.clear_nak = 1; + depctl.enable = 1; + if (depctl.type == DEPCTL_EPTYPE_ISOCHRONOUS && xfer->interval == 1) { + const dwc2_dsts_t dsts = {.value = dwc2->dsts}; + const uint32_t odd_now = dsts.frame_number & 1u; if (odd_now) { - depctl.bm.set_data0_iso_even = 1; + depctl.set_data0_iso_even = 1; } else { - depctl.bm.set_data1_iso_odd = 1; + depctl.set_data1_iso_odd = 1; } } @@ -410,7 +406,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // XCVRDLY: transceiver delay between xcvr_sel and txvalid during device chirp is required // when using with some PHYs such as USB334x (USB3341, USB3343, USB3346, USB3347) - if (dwc2->ghwcfg2_bm.hs_phy_type == GHWCFG2_HSPHY_ULPI) { + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + if (ghwcfg2.hs_phy_type == GHWCFG2_HSPHY_ULPI) { dcfg |= DCFG_XCVRDLY; } } else { @@ -428,8 +425,13 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // Clear A override, force B Valid dwc2->gotgctl = (dwc2->gotgctl & ~GOTGCTL_AVALOEN) | GOTGCTL_BVALOEN | GOTGCTL_BVALOVAL; +#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 + // Enable required interrupts - dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_USBSUSPM | GINTMSK_USBRST | GINTMSK_ENUMDNEM | GINTMSK_WUIM; + dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_USBRST | GINTMSK_ENUMDNEM | GINTMSK_WUIM; // TX FIFO empty level for interrupt is complete empty uint32_t gahbcfg = dwc2->gahbcfg; @@ -539,6 +541,8 @@ void dcd_edpt_close_all(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); uint8_t const ep_count = _dwc2_controller[rhport].ep_count; + usbd_spin_lock(false); + _dcd_data.allocated_epin_count = 0; // Disable non-control interrupt @@ -556,8 +560,9 @@ void dcd_edpt_close_all(uint8_t rhport) { dfifo_flush_tx(dwc2, 0x10); // all tx fifo dfifo_flush_rx(dwc2); - dfifo_device_init(rhport); // re-init dfifo + + usbd_spin_unlock(false); } bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { @@ -575,21 +580,31 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * p_endpo bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, dir); - xfer->buffer = buffer; - xfer->ff = NULL; - xfer->total_len = total_bytes; + bool ret; - // EP0 can only handle one packet - if (epnum == 0) { - _dcd_data.ep0_pending[dir] = total_bytes; + usbd_spin_lock(false); + + if (xfer->max_size == 0) { + ret = false; // Endpoint is closed + } else { + xfer->buffer = buffer; + xfer->ff = NULL; + xfer->total_len = total_bytes; + + // EP0 can only handle one packet + if (epnum == 0) { + _dcd_data.ep0_pending[dir] = total_bytes; + } + + // Schedule packets to be sent within interrupt + edpt_schedule_packets(rhport, epnum, dir); + ret = true; } - // Schedule packets to be sent within interrupt - edpt_schedule_packets(rhport, epnum, dir); + usbd_spin_unlock(false); - return true; + return ret; } // The number of bytes has to be given explicitly to allow more flexible control of how many @@ -602,17 +617,27 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_t uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, dir); - xfer->buffer = NULL; - xfer->ff = ff; - xfer->total_len = total_bytes; + bool ret; - // Schedule packets to be sent within interrupt - // TODO xfer fifo may only available for slave mode - edpt_schedule_packets(rhport, epnum, dir); + usbd_spin_lock(false); - return true; + if (xfer->max_size == 0) { + ret = false; // Endpoint is closed + } else { + xfer->buffer = NULL; + xfer->ff = ff; + xfer->total_len = total_bytes; + + // Schedule packets to be sent within interrupt + // TODO xfer fifo may only available for slave mode + edpt_schedule_packets(rhport, epnum, dir); + ret = true; + } + + usbd_spin_unlock(false); + + return ret; } void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { @@ -639,9 +664,10 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { //-------------------------------------------------------------------- // 7.4.1 Initialization on USB Reset +// Must be called from critical section static void handle_bus_reset(uint8_t rhport) { dwc2_regs_t *dwc2 = DWC2_REG(rhport); - const uint8_t ep_count = DWC2_EP_COUNT(dwc2); + const uint8_t ep_count = dwc2_ep_count(dwc2); tu_memclr(xfer_status, sizeof(xfer_status)); @@ -671,7 +697,9 @@ static void handle_bus_reset(uint8_t rhport) { dfifo_device_init(rhport); // 5. Reset device address - dwc2->dcfg_bm.address = 0; + dwc2_dcfg_t dcfg = {.value = dwc2->dcfg}; + dcfg.address = 0; + dwc2->dcfg = dcfg.value; // Fixed both control EP0 size to 64 bytes dwc2->epin[0].ctl &= ~(0x03 << DIEPCTL_MPSIZ_Pos); @@ -691,8 +719,9 @@ static void handle_bus_reset(uint8_t rhport) { static void handle_enum_done(uint8_t rhport) { dwc2_regs_t *dwc2 = DWC2_REG(rhport); + const dwc2_dsts_t dsts = {.value = dwc2->dsts}; tusb_speed_t speed; - switch (dwc2->dsts_bm.enum_speed) { + switch (dsts.enum_speed) { case DCFG_SPEED_HIGH: speed = TUSB_SPEED_HIGH; break; @@ -737,12 +766,12 @@ static void handle_rxflvl_irq(uint8_t rhport) { const volatile uint32_t* rx_fifo = dwc2->fifo[0]; // Pop control word off FIFO - const dwc2_grxstsp_t grxstsp_bm = dwc2->grxstsp_bm; - const uint8_t epnum = grxstsp_bm.ep_ch_num; + const dwc2_grxstsp_t grxstsp = {.value = dwc2->grxstsp}; + const uint8_t epnum = grxstsp.ep_ch_num; dwc2_dep_t* epout = &dwc2->epout[epnum]; - switch (grxstsp_bm.packet_status) { + switch (grxstsp.packet_status) { case GRXSTS_PKTSTS_GLOBAL_OUT_NAK: // Global OUT NAK: do nothing break; @@ -764,7 +793,7 @@ static void handle_rxflvl_irq(uint8_t rhport) { case GRXSTS_PKTSTS_RX_DATA: { // Out packet received - const uint16_t byte_count = grxstsp_bm.byte_count; + const uint16_t byte_count = grxstsp.byte_count; xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); if (byte_count) { @@ -778,7 +807,8 @@ static void handle_rxflvl_irq(uint8_t rhport) { // short packet, minus remaining bytes (xfer_size) if (byte_count < xfer->max_size) { - xfer->total_len -= epout->tsiz_bm.xfer_size; + const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz}; + xfer->total_len -= tsiz.xfer_size; if (epnum == 0) { xfer->total_len -= _dcd_data.ep0_pending[TUSB_DIR_OUT]; _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; @@ -840,11 +870,13 @@ static void handle_epin_slave(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diep // - 64 bytes or // - Half/Empty of TX FIFO size (configured by GAHBCFG.TXFELVL) if (diepint_bm.txfifo_empty && (dwc2->diepempmsk & (1 << epnum))) { - const uint16_t remain_packets = epin->tsiz_bm.packet_count; + dwc2_ep_tsize_t tsiz = {.value = epin->tsiz}; + const uint16_t remain_packets = tsiz.packet_count; // Process every single packet (only whole packets can be written to fifo) for (uint16_t i = 0; i < remain_packets; i++) { - const uint16_t remain_bytes = (uint16_t) epin->tsiz_bm.xfer_size; + tsiz.value = epin->tsiz; + const uint16_t remain_bytes = (uint16_t) tsiz.xfer_size; const uint16_t xact_bytes = tu_min16(remain_bytes, xfer->max_size); // Check if dtxfsts has enough space available @@ -863,7 +895,8 @@ static void handle_epin_slave(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diep } // Turn off TXFE if all bytes are written. - if (epin->tsiz_bm.xfer_size == 0) { + tsiz.value = epin->tsiz; + if (tsiz.xfer_size == 0) { dwc2->diepempmsk &= ~(1 << epnum); } } @@ -894,7 +927,8 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); // determine actual received bytes - const uint16_t remain = epout->tsiz_bm.xfer_size; + const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz}; + const uint16_t remain = tsiz.xfer_size; xfer->total_len -= remain; // this is ZLP, so prepare EP0 for next setup @@ -930,7 +964,7 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin static void handle_ep_irq(uint8_t rhport, uint8_t dir) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); const bool is_dma = dma_device_enabled(dwc2); - const uint8_t ep_count = DWC2_EP_COUNT(dwc2); + const uint8_t ep_count = dwc2_ep_count(dwc2); const uint8_t daint_offset = (dir == TUSB_DIR_IN) ? DAINT_IEPINT_Pos : DAINT_OEPINT_Pos; dwc2_dep_t* ep_base = &dwc2->ep[dir == TUSB_DIR_IN ? 0 : 1][0]; @@ -983,29 +1017,34 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) { */ void dcd_int_handler(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); - const uint32_t gintmask = dwc2->gintmsk; const uint32_t gintsts = dwc2->gintsts & gintmask; if (gintsts & GINTSTS_USBRST) { // USBRST is start of reset. dwc2->gintsts = GINTSTS_USBRST; + + usbd_spin_lock(true); handle_bus_reset(rhport); + usbd_spin_unlock(true); } if (gintsts & GINTSTS_ENUMDNE) { // ENUMDNE is the end of reset where speed of the link is detected dwc2->gintsts = GINTSTS_ENUMDNE; + dwc2->gintmsk |= GINTMSK_USBSUSPM; handle_enum_done(rhport); } if (gintsts & GINTSTS_USBSUSP) { dwc2->gintsts = GINTSTS_USBSUSP; + dwc2->gintmsk &= ~GINTMSK_USBSUSPM; dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); } if (gintsts & GINTSTS_WKUINT) { dwc2->gintsts = GINTSTS_WKUINT; + dwc2->gintmsk |= GINTMSK_USBSUSPM; dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); } @@ -1025,6 +1064,7 @@ void dcd_int_handler(uint8_t rhport) { if(gintsts & GINTSTS_SOF) { dwc2->gintsts = GINTSTS_SOF; + dwc2->gintmsk |= GINTMSK_USBSUSPM; const uint32_t frame = (dwc2->dsts & DSTS_FNSOF) >> DSTS_FNSOF_Pos; // Disable SOF interrupt if SOF was not explicitly enabled since SOF was used for remote wakeup detection diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h new file mode 100644 index 000000000..37b6592c4 --- /dev/null +++ b/src/portable/synopsys/dwc2/dwc2_at32.h @@ -0,0 +1,121 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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 DWC2_AT32_H_ +#define DWC2_AT32_H_ + +#define DWC2_EP_MAX TUP_DCD_ENDPOINT_MAX + +#if CFG_TUSB_MCU == OPT_MCU_AT32F415 + #include <at32f415.h> + #define OTG1_FIFO_SIZE 1280 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F435_437 + #include <at32f435_437.h> + #define OTG1_FIFO_SIZE 1280 + #define OTG2_FIFO_SIZE 1280 + #define OTG1_IRQn OTGFS1_IRQn + #define OTG2_IRQn OTGFS2_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL + #define DWC2_OTG2_REG_BASE 0x40040000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F423 + #include <at32f423.h> + #define OTG1_FIFO_SIZE 1280 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F402_405 + #include <at32f402_405.h> + #define OTG1_FIFO_SIZE 1280 + #define OTG2_FIFO_SIZE 4096 + #define OTG1_IRQn OTGFS1_IRQn + #define OTG2_IRQn OTGHS_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL + #define DWC2_OTG2_REG_BASE 0x40040000UL //OTGHS +#elif CFG_TUSB_MCU == OPT_MCU_AT32F425 + #include <at32f425.h> + #define OTG1_FIFO_SIZE 1280 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL +#endif + +#ifdef __cplusplus + extern "C" { +#endif + + static const dwc2_controller_t _dwc2_controller[] = { +{.reg_base = DWC2_OTG1_REG_BASE, .irqnum = OTG1_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = OTG1_FIFO_SIZE}, +#if defined DWC2_OTG2_REG_BASE + {.reg_base = DWC2_OTG2_REG_BASE, .irqnum = OTG2_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = OTG2_FIFO_SIZE} +#endif + }; + + TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) { + (void) role; + const IRQn_Type irqn = (IRQn_Type) _dwc2_controller[rhport].irqnum; + if (enabled) { + NVIC_EnableIRQ(irqn); + } else { + NVIC_DisableIRQ(irqn); + } + } + + TU_ATTR_ALWAYS_INLINE static inline void dwc2_dcd_int_enable(uint8_t rhport) { NVIC_EnableIRQ(_dwc2_controller[rhport].irqnum); + } + + TU_ATTR_ALWAYS_INLINE static inline void dwc2_dcd_int_disable(uint8_t rhport) { + NVIC_DisableIRQ(_dwc2_controller[rhport].irqnum); + } + + TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { + // try to delay for 1 ms + uint32_t count = system_core_clock / 1000; + while (count--) __asm volatile("nop"); + } + + // MCU specific PHY init, called BEFORE core reset + TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void) dwc2; + // Enable on-chip HS PHY + if (hs_phy_type == GHWCFG2_HSPHY_UTMI || hs_phy_type == GHWCFG2_HSPHY_UTMI_ULPI) { + } else if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { + } + } + + // 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; + (void) hs_phy_type; + + dwc2->stm32_gccfg |= STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN; + } + +#ifdef __cplusplus +} +#endif + +#endif /* DWC2_GD32_H_ */ diff --git a/src/portable/synopsys/dwc2/dwc2_common.c b/src/portable/synopsys/dwc2/dwc2_common.c index f80ae9acb..5ff18ab94 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.c +++ b/src/portable/synopsys/dwc2/dwc2_common.c @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2024 Ha Thach (tinyusb.org) + * Copyright (c) 2024-2025 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 @@ -36,6 +36,7 @@ #if CFG_TUH_ENABLED #include "host/hcd.h" +#include "host/usbh.h" #endif #include "dwc2_common.h" @@ -44,17 +45,26 @@ // //-------------------------------------------------------------------- static void reset_core(dwc2_regs_t* dwc2) { + // The software must check that bit 31 in this register is set to 1 (AHB Master is Idle) before starting any operation + 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; - if ((dwc2->gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) { - // prior v42.0 CSRST is self-clearing + 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 + // 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) {} } else { - // From v4.20a CSRST bit is write only, CSRT_DONE (w1c) is introduced for checking. - // CSRST must also be explicitly cleared + // From v4.20a: CSRST bit is write only. The application must clear this bit after checking the bit 29 of this + // register i.e Core Soft Reset Done CSRT_DONE (w1c) while (!(dwc2->grstctl & GRSTCTL_CSRST_DONE)) {} - dwc2->grstctl = (dwc2->grstctl & ~GRSTCTL_CSRST) | GRSTCTL_CSRST_DONE; + dwc2->grstctl = (dwc2->grstctl & ~GRSTCTL_CSRST) | GRSTCTL_CSRST_DONE; } while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {} // wait for AHB master IDLE @@ -88,11 +98,21 @@ static void phy_fs_init(dwc2_regs_t* dwc2) { static void phy_hs_init(dwc2_regs_t* dwc2) { uint32_t gusbcfg = dwc2->gusbcfg; + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + const dwc2_ghwcfg4_t ghwcfg4 = {.value = dwc2->ghwcfg4}; + + uint8_t phy_width; + if (CFG_TUSB_MCU != OPT_MCU_AT32F402_405 && // at32f402_405 does not support 16-bit + ghwcfg4.phy_data_width) { + phy_width = 16; // 16-bit PHY interface if supported + } else { + phy_width = 8; // 8-bit PHY interface + } // De-select FS PHY gusbcfg &= ~GUSBCFG_PHYSEL; - if (dwc2->ghwcfg2_bm.hs_phy_type == GHWCFG2_HSPHY_ULPI) { + if (ghwcfg2.hs_phy_type == GHWCFG2_HSPHY_ULPI) { TU_LOG(DWC2_COMMON_DEBUG, "Highspeed ULPI PHY init\r\n"); // Select ULPI PHY (external) @@ -116,10 +136,10 @@ static void phy_hs_init(dwc2_regs_t* dwc2) { gusbcfg &= ~GUSBCFG_ULPI_UTMI_SEL; // Set 16-bit interface if supported - if (dwc2->ghwcfg4_bm.phy_data_width) { - gusbcfg |= GUSBCFG_PHYIF16; // 16 bit + if (phy_width == 16) { + gusbcfg |= GUSBCFG_PHYIF16; } else { - gusbcfg &= ~GUSBCFG_PHYIF16; // 8 bit + gusbcfg &= ~GUSBCFG_PHYIF16; } } @@ -127,7 +147,7 @@ static void phy_hs_init(dwc2_regs_t* dwc2) { dwc2->gusbcfg = gusbcfg; // mcu specific phy init - dwc2_phy_init(dwc2, dwc2->ghwcfg2_bm.hs_phy_type); + dwc2_phy_init(dwc2, ghwcfg2.hs_phy_type); // Reset core after selecting PHY reset_core(dwc2); @@ -136,11 +156,11 @@ static void phy_hs_init(dwc2_regs_t* dwc2) { // - 9 if using 8-bit PHY interface // - 5 if using 16-bit PHY interface gusbcfg &= ~GUSBCFG_TRDT_Msk; - gusbcfg |= (dwc2->ghwcfg4_bm.phy_data_width ? 5u : 9u) << GUSBCFG_TRDT_Pos; + gusbcfg |= (phy_width == 16 ? 5u : 9u) << GUSBCFG_TRDT_Pos; dwc2->gusbcfg = gusbcfg; // MCU specific PHY update post reset - dwc2_phy_update(dwc2, dwc2->ghwcfg2_bm.hs_phy_type); + dwc2_phy_update(dwc2, ghwcfg2.hs_phy_type); } static bool check_dwc2(dwc2_regs_t* dwc2) { @@ -171,7 +191,6 @@ static bool check_dwc2(dwc2_regs_t* dwc2) { //-------------------------------------------------------------------- bool dwc2_core_is_highspeed(dwc2_regs_t* dwc2, tusb_role_t role) { (void)dwc2; - #if CFG_TUD_ENABLED if (role == TUSB_ROLE_DEVICE && !TUD_OPT_HIGH_SPEED) { return false; @@ -183,7 +202,8 @@ bool dwc2_core_is_highspeed(dwc2_regs_t* dwc2, tusb_role_t role) { } #endif - return dwc2->ghwcfg2_bm.hs_phy_type != GHWCFG2_HSPHY_NOT_SUPPORTED; + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return ghwcfg2.hs_phy_type != GHWCFG2_HSPHY_NOT_SUPPORTED; } /* dwc2 has several PHYs option diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h index 18b93894f..33219f786 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.h +++ b/src/portable/synopsys/dwc2/dwc2_common.h @@ -49,6 +49,8 @@ #include "dwc2_efm32.h" #elif TU_CHECK_MCU(OPT_MCU_XMC4000) #include "dwc2_xmc.h" +#elif defined(TUP_USBIP_DWC2_AT32) + #include "dwc2_at32.h" #else #error "Unsupported MCUs" #endif diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h index 3309760ff..49b8c54cb 100644 --- a/src/portable/synopsys/dwc2/dwc2_esp32.h +++ b/src/portable/synopsys/dwc2/dwc2_esp32.h @@ -55,8 +55,8 @@ static const dwc2_controller_t _dwc2_controller[] = { // On ESP32 for consistency we associate // - Port0 to OTG_FS, and Port1 to OTG_HS static const dwc2_controller_t _dwc2_controller[] = { -{ .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .ep_fifo_size = 1024 }, -{ .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .ep_fifo_size = 4096 } + { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .ep_fifo_size = 1024 }, + { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .ep_fifo_size = 4096 } }; #endif diff --git a/src/portable/synopsys/dwc2/dwc2_info.md b/src/portable/synopsys/dwc2/dwc2_info.md index dec021f59..f655e4dba 100644 --- a/src/portable/synopsys/dwc2/dwc2_info.md +++ b/src/portable/synopsys/dwc2/dwc2_info.md @@ -1,58 +1,58 @@ -| | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | ST F207/F407/411/429 FS | ST F407/429 HS | ST F412/76x FS | ST F723/L4P5 FS | ST F723 HS | ST F76x HS | ST H743/H750 | ST L476 FS | ST U5A5 HS | XMC4500 | GD32VF103 | -|:---------------------------|:----------------|:-------------|:--------------|:-------------|:--------------------------|:-----------------|:-----------------|:------------------|:-------------|:-------------|:---------------|:-------------|:-------------|:-------------|:------------| -| GUID | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001200 | 0x00001100 | 0x00002000 | 0x00003000 | 0x00003100 | 0x00002100 | 0x00002300 | 0x00002000 | 0x00005000 | 0x00AEC000 | 0x00001000 | -| GSNPSID | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54281A | 0x4F54281A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54320A | 0x4F54330A | 0x4F54310A | 0x4F54411A | 0x4F54292A | 0x00000000 | -| - specs version | 2.80a | 3.30a | 4.00a | 4.00a | 2.81a | 2.81a | 3.20a | 3.30a | 3.30a | 3.20a | 3.30a | 3.10a | 4.11a | 2.92a | 0.00W | -| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | -| GHWCFG2 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x229DCD20 | 0x229ED590 | 0x229ED520 | 0x229ED520 | 0x229FE1D0 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x228FE052 | 0x228F5930 | 0x00000000 | -| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | -| - arch | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | DMA internal | Slave only | Slave only | DMA internal | DMA internal | DMA internal | Slave only | DMA internal | DMA internal | Slave only | -| - single_point | hub | hub | n/a | hub | n/a | hub | n/a | n/a | hub | hub | hub | n/a | hub | n/a | hub | -| - hs_phy_type | UTMI+ | n/a | n/a | UTMI+/ULPI | n/a | ULPI | n/a | n/a | UTMI+/ULPI | ULPI | ULPI | n/a | UTMI+ | n/a | n/a | -| - fs_phy_type | Dedicated | Dedicated | Dedicated | Shared ULPI | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a | -| - num_dev_ep | 7 | 6 | 6 | 15 | 3 | 5 | 5 | 5 | 8 | 8 | 8 | 5 | 8 | 6 | 0 | -| - num_host_ch | 7 | 13 | 7 | 15 | 7 | 11 | 11 | 11 | 15 | 15 | 15 | 11 | 15 | 13 | 0 | -| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - mul_proc_intrpt | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | -| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - nptx_q_depth | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | -| - ptx_q_depth | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | -| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 | -| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| GHWCFG3 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x020001E8 | 0x03F403E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 | -| - xfer_size_width | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 | -| - packet_size_width | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 | -| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - i2c_enable | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | -| - vendor_ctrl_itf | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | -| - optional_feature_removed | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - synch_reset | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - otg_adp_support | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | -| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - battery_charger_support | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | -| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | -| - dfifo_depth | 4080 | 498 | 200 | 896 | 512 | 1012 | 512 | 512 | 1006 | 1006 | 952 | 512 | 952 | 634 | 0 | -| GHWCFG4 | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x0FF08030 | 0x17F00030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0xE2103E30 | 0xDBF08030 | 0x00000000 | -| - num_dev_period_in_ep | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - partial_powerdown | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - ahb_freq_min | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - enhanced_lpm_support | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - phy_data_width | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | -| - ctrl_ep_num | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - iddg_filter | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - vbus_valid_filter | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - a_valid_filter | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - b_valid_filter | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - session_end_filter | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - dedicated_fifos | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - num_dev_in_eps | 7 | 6 | 4 | 7 | 3 | 5 | 5 | 5 | 8 | 8 | 8 | 5 | 8 | 6 | 0 | -| - dma_desc_enable | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | -| - dma_desc_dynamic | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | +| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54 | ST F207/F407/411/429 FS | ST F407/429 HS | ST F412/76x FS | ST F723/L4P5 FS | ST F723 HS | ST F76x HS | ST H743/H750 | ST L476 FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 | +|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:--------------------------|:-----------------|:-----------------|:------------------|:-------------|:-------------|:---------------|:-------------|:---------------------|:-------------|:------------| +| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001200 | 0x00001100 | 0x00002000 | 0x00003000 | 0x00003100 | 0x00002100 | 0x00002300 | 0x00002000 | 0x00005000 | 0x00AEC000 | 0x00001000 | +| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54281A | 0x4F54281A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54320A | 0x4F54330A | 0x4F54310A | 0x4F54411A | 0x4F54292A | 0x00000000 | +| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 2.81a | 2.81a | 3.20a | 3.30a | 3.30a | 3.20a | 3.30a | 3.10a | 4.11a | 2.92a | 0.00W | +| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | +| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x229DCD20 | 0x229ED590 | 0x229ED520 | 0x229ED520 | 0x229FE1D0 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x228FE052 | 0x228F5930 | 0x00000000 | +| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | +| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | DMA internal | Slave only | Slave only | DMA internal | DMA internal | DMA internal | Slave only | DMA internal | DMA internal | Slave only | +| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | n/a | hub | n/a | n/a | hub | hub | hub | n/a | hub | n/a | hub | +| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | n/a | ULPI | n/a | n/a | UTMI+/ULPI | ULPI | ULPI | n/a | UTMI+ | n/a | n/a | +| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a | +| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 3 | 5 | 5 | 5 | 8 | 8 | 8 | 5 | 8 | 6 | 0 | +| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 7 | 11 | 11 | 11 | 15 | 15 | 15 | 11 | 15 | 13 | 0 | +| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | +| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | +| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | +| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 | +| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x020001E8 | 0x03F403E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 | +| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 | +| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 | +| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | +| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | +| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | +| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | +| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | +| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 512 | 1012 | 512 | 512 | 1006 | 1006 | 952 | 512 | 952 | 634 | 0 | +| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x0FF08030 | 0x17F00030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0xE2103E30 | 0xDBF08030 | 0x00000000 | +| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | +| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | +| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | +| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | +| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | +| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 3 | 5 | 5 | 5 | 8 | 8 | 8 | 5 | 8 | 6 | 0 | +| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | +| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | diff --git a/src/portable/synopsys/dwc2/dwc2_info.py b/src/portable/synopsys/dwc2/dwc2_info.py index 25edcf22d..f6bd2785a 100755 --- a/src/portable/synopsys/dwc2/dwc2_info.py +++ b/src/portable/synopsys/dwc2/dwc2_info.py @@ -9,10 +9,14 @@ import pandas as pd # Note: FS is FullSpeed, HS is HighSpeed dwc2_reg_list = ['GUID', 'GSNPSID', 'GHWCFG1', 'GHWCFG2', 'GHWCFG3', 'GHWCFG4'] dwc2_reg_value = { + 'AT32 F405 FS': [0x00002000, 0x4F54400A, 0x00000000, 0x228FDD00, 0x020004E8, 0x1FF0A020], + 'AT32 F405 HS': [0x00000000, 0x4F54400A, 0x00000000, 0x229FDDD0, 0x03F006E8, 0x1FF0A020], + 'AT32 F415': [0x00001000, 0x4F54400A, 0x00000000, 0x228DCD00, 0x020004E8, 0x0F], 'BCM2711 (Pi4)': [0x2708A000, 0x4F54280A, 0, 0x228DDD50, 0xFF000E8, 0x1FF00020], 'EFM32GG': [0, 0x4F54330A, 0, 0x228F5910, 0x01F204E8, 0x1BF08030], 'ESP32-S2/S3': [0, 0x4F54400A, 0, 0x224DD930, 0x0C804B5, 0xD3F0A030], 'ESP32-P4': [0, 0x4F54400A, 0, 0x215FFFD0, 0x03805EB5, 0xDFF1A030], + 'nRF54': [0, 0x4F54430A, 0xAA555000, 0x228BFC72, 0x0BEAC0E8, 0x1E10AA60], 'ST F207/F407/411/429 FS': [0x1200, 0x4F54281A, 0, 0x229DCD20, 0x020001E8, 0x0FF08030], 'ST F407/429 HS': [0x1100, 0x4F54281A, 0, 0x229ED590, 0x03F403E8, 0x17F00030], 'ST F412/76x FS': [0x2000, 0x4F54320A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], @@ -21,7 +25,7 @@ dwc2_reg_value = { 'ST F76x HS': [0x2100, 0x4F54320A, 0, 0x229FE190, 0x03EED2E8, 0x23F00030], 'ST H743/H750': [0x2300, 0x4F54330A, 0, 0x229FE190, 0x03B8D2E8, 0xE3F00030], 'ST L476 FS': [0x2000, 0x4F54310A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], - 'ST U5A5 HS': [0x5000, 0x4F54411A, 0, 0x228FE052, 0x03B882E8, 0xE2103E30], + 'ST U5A5/H7RS/N6 HS': [0x5000, 0x4F54411A, 0, 0x228FE052, 0x03B882E8, 0xE2103E30], 'XMC4500': [0xAEC000, 0x4F54292A, 0, 0x228F5930, 0x027A01E5, 0xDBF08030], 'GD32VF103': [0x1000, 0, 0, 0, 0, 0], } diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h index c11c1eb05..08950ccc0 100644 --- a/src/portable/synopsys/dwc2/dwc2_stm32.h +++ b/src/portable/synopsys/dwc2/dwc2_stm32.h @@ -77,6 +77,17 @@ extern "C" { #define EP_MAX_HS 9 #define EP_FIFO_SIZE_HS 4096 +#elif CFG_TUSB_MCU == OPT_MCU_STM32N6 + #include "stm32n6xx.h" + #define EP_MAX_FS 9 + #define EP_FIFO_SIZE_FS 4096 + + #define EP_MAX_HS 9 + #define EP_FIFO_SIZE_HS 4096 + + #define USB_OTG_HS_PERIPH_BASE USB1_OTG_HS_BASE + #define OTG_HS_IRQn USB1_OTG_HS_IRQn + #elif CFG_TUSB_MCU == OPT_MCU_STM32F7 #include "stm32f7xx.h" #define EP_MAX_FS 6 @@ -102,6 +113,22 @@ extern "C" { #define EP_MAX_HS 9 #define EP_FIFO_SIZE_HS 4096 #endif + +#elif CFG_TUSB_MCU == OPT_MCU_STM32WBA + #if defined(STM32WBA62xx) + #include "stm32wba62xx.h" + #elif defined(STM32WBA64xx) + #include "stm32wba64xx.h" + #elif defined(STM32WBA65xx) + #include "stm32wba65xx.h" + #else + #error "The selected STM32WBA series chip does not support OTG USB HS" + #endif + + #define USB_OTG_HS_PERIPH_BASE USB_OTG_HS_BASE_NS + #define OTG_HS_IRQn USB_OTG_HS_IRQn + #define EP_MAX_HS 9 + #define EP_FIFO_SIZE_HS 4096 #else #error "Unsupported MCUs" #endif @@ -155,6 +182,7 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { // MCU specific PHY init, called BEFORE core reset // - dwc2 3.30a (H5) use USB_HS_PHYC // - dwc2 4.11a (U5) use femtoPHY +// - dwc2 x.xxx (WBA) use USB_OTG_HS static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { // Enable on-chip FS PHY @@ -183,11 +211,10 @@ static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { #endif } else { -#if CFG_TUSB_MCU != OPT_MCU_STM32U5 +#if CFG_TUSB_MCU != OPT_MCU_STM32U5 && CFG_TUSB_MCU != OPT_MCU_STM32WBA // Disable FS PHY, TODO on U5A5 (dwc2 4.11a) 16th bit is 'Host CDP behavior enable' dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN; #endif - // Enable on-chip HS PHY if (hs_phy_type == GHWCFG2_HSPHY_UTMI || hs_phy_type == GHWCFG2_HSPHY_UTMI_ULPI) { #ifdef USB_HS_PHYC @@ -222,6 +249,9 @@ static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { // Enable PLL internal PHY USB_HS_PHYC->USB_HS_PHYC_PLL |= USB_HS_PHYC_PLL_PLLEN; + + // Wait ~2ms until the PLL is ready (there's no RDY bit to query) + tusb_time_delay_ms_api(2); #else #endif @@ -268,6 +298,77 @@ static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { } } +//------------- 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_DWC2_MEM_UNCACHED_REGIONS +#define CFG_DWC2_MEM_UNCACHED_REGIONS +#endif + +static mem_region_t uncached_regions[] = { + // DTCM (although USB DMA can't transfer to/from DTCM) +#if CFG_TUSB_MCU == OPT_MCU_STM32H7 + {.start = 0x20000000, .end = 0x2001FFFF}, +#elif CFG_TUSB_MCU == OPT_MCU_STM32H7RS + // DTCM (although USB DMA can't transfer to/from DTCM) + {.start = 0x20000000, .end = 0x2002FFFF}, +#elif CFG_TUSB_MCU == OPT_MCU_STM32F7 + // DTCM + {.start = 0x20000000, .end = 0x2000FFFF}, +#else +#error "Cache maintenance is not supported yet" +#endif + CFG_DWC2_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) { + 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 dwc2_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 dwc2_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 dwc2_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 + #ifdef __cplusplus } #endif diff --git a/src/portable/synopsys/dwc2/dwc2_type.h b/src/portable/synopsys/dwc2/dwc2_type.h index 812096759..0a8dacf5f 100644 --- a/src/portable/synopsys/dwc2/dwc2_type.h +++ b/src/portable/synopsys/dwc2/dwc2_type.h @@ -184,415 +184,470 @@ enum { //-------------------------------------------------------------------- // Common Register Bitfield //-------------------------------------------------------------------- -typedef struct TU_ATTR_PACKED { - uint32_t ses_req_scs : 1; // 0 Session request success - uint32_t ses_req : 1; // 1 Session request - uint32_t vbval_ov_en : 1; // 2 VBUS valid override enable - uint32_t vbval_ov_val : 1; // 3 VBUS valid override value - uint32_t aval_ov_en : 1; // 4 A-peripheral session valid override enable - uint32_t aval_ov_al : 1; // 5 A-peripheral session valid override value - uint32_t bval_ov_en : 1; // 6 B-peripheral session valid override enable - uint32_t bval_ov_val : 1; // 7 B-peripheral session valid override value - uint32_t hng_scs : 1; // 8 Host negotiation success - uint32_t hnp_rq : 1; // 9 HNP (host negotiation protocol) request - uint32_t host_set_hnp_en : 1; // 10 Host set HNP enable - uint32_t dev_hnp_en : 1; // 11 Device HNP enabled - uint32_t embedded_host_en : 1; // 12 Embedded host enable - uint32_t rsv13_14 : 2; // 13.14 Reserved - uint32_t dbnc_filter_bypass : 1; // 15 Debounce filter bypass - uint32_t cid_status : 1; // 16 Connector ID status - uint32_t dbnc_done : 1; // 17 Debounce done - uint32_t ases_valid : 1; // 18 A-session valid - uint32_t bses_valid : 1; // 19 B-session valid - uint32_t otg_ver : 1; // 20 OTG version 0: v1.3, 1: v2.0 - uint32_t current_mode : 1; // 21 Current mode of operation. Only from v3.00a - uint32_t mult_val_id_bc : 5; // 22..26 Multi-valued input pin ID battery charger - uint32_t chirp_en : 1; // 27 Chirp detection enable - uint32_t rsv28_30 : 3; // 28.30: Reserved - uint32_t test_mode_corr_eusb2 : 1; // 31 Test mode control for eUSB2 PHY +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t ses_req_scs : 1; // 0 Session request success + uint32_t ses_req : 1; // 1 Session request + uint32_t vbval_ov_en : 1; // 2 VBUS valid override enable + uint32_t vbval_ov_val : 1; // 3 VBUS valid override value + uint32_t aval_ov_en : 1; // 4 A-peripheral session valid override enable + uint32_t aval_ov_al : 1; // 5 A-peripheral session valid override value + uint32_t bval_ov_en : 1; // 6 B-peripheral session valid override enable + uint32_t bval_ov_val : 1; // 7 B-peripheral session valid override value + uint32_t hng_scs : 1; // 8 Host negotiation success + uint32_t hnp_rq : 1; // 9 HNP (host negotiation protocol) request + uint32_t host_set_hnp_en : 1; // 10 Host set HNP enable + uint32_t dev_hnp_en : 1; // 11 Device HNP enabled + uint32_t embedded_host_en : 1; // 12 Embedded host enable + uint32_t rsv13_14 : 2; // 13.14 Reserved + uint32_t dbnc_filter_bypass : 1; // 15 Debounce filter bypass + uint32_t cid_status : 1; // 16 Connector ID status + uint32_t dbnc_done : 1; // 17 Debounce done + uint32_t ases_valid : 1; // 18 A-session valid + uint32_t bses_valid : 1; // 19 B-session valid + uint32_t otg_ver : 1; // 20 OTG version 0: v1.3, 1: v2.0 + uint32_t current_mode : 1; // 21 Current mode of operation. Only from v3.00a + uint32_t mult_val_id_bc : 5; // 22..26 Multi-valued input pin ID battery charger + uint32_t chirp_en : 1; // 27 Chirp detection enable + uint32_t rsv28_30 : 3; // 28.30: Reserved + uint32_t test_mode_corr_eusb2 : 1; // 31 Test mode control for eUSB2 PHY + }; } dwc2_gotgctl_t; TU_VERIFY_STATIC(sizeof(dwc2_gotgctl_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t rsv0_1 : 2; // 0..1 Reserved - uint32_t ses_end_det : 1; // 2 Session end detected - uint32_t rsv3_7 : 5; // 3..7 Reserved - uint32_t srs_status_change : 1; // 8 Session request success status change - uint32_t hns_status_change : 1; // 9 Host negotiation success status change - uint32_t rsv10_16 : 7; // 10..16 Reserved - uint32_t hng_det : 1; // 17 Host negotiation detected - uint32_t adev_timeout_change : 1; // 18 A-device timeout change - uint32_t dbnc_done : 1; // 19 Debounce done - uint32_t mult_val_lp_change : 1; // 20 Multi-valued input pin change - uint32_t rsv21_31 :11; // 21..31 Reserved +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t rsv0_1 : 2; // 0..1 Reserved + uint32_t ses_end_det : 1; // 2 Session end detected + uint32_t rsv3_7 : 5; // 3..7 Reserved + uint32_t srs_status_change : 1; // 8 Session request success status change + uint32_t hns_status_change : 1; // 9 Host negotiation success status change + uint32_t rsv10_16 : 7; // 10..16 Reserved + uint32_t hng_det : 1; // 17 Host negotiation detected + uint32_t adev_timeout_change : 1; // 18 A-device timeout change + uint32_t dbnc_done : 1; // 19 Debounce done + uint32_t mult_val_lp_change : 1; // 20 Multi-valued input pin change + uint32_t rsv21_31 :11; // 21..31 Reserved + }; } dwc2_gotgint_t; TU_VERIFY_STATIC(sizeof(dwc2_gotgint_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t gintmask : 1; // 0 Global interrupt mask - uint32_t hbst_len : 4; // 1..4 Burst length/type - uint32_t dma_en : 1; // 5 DMA enable - uint32_t rsv6 : 1; // 6 Reserved - uint32_t nptxf_empty_lvl : 1; // 7 Non-periodic Tx FIFO empty level - uint32_t ptxf_empty_lvl : 1; // 8 Periodic Tx FIFO empty level - uint32_t rsv9_20 : 12; // 9.20: Reserved - uint32_t remote_mem_support : 1; // 21 Remote memory support - uint32_t notify_all_dma_write : 1; // 22 Notify all DMA writes - uint32_t ahb_single : 1; // 23 AHB single - uint32_t inv_desc_endian : 1; // 24 Inverse descriptor endian - uint32_t rsv25_31 : 7; // 25..31 Reserved +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t gintmask : 1; // 0 Global interrupt mask + uint32_t hbst_len : 4; // 1..4 Burst length/type + uint32_t dma_en : 1; // 5 DMA enable + uint32_t rsv6 : 1; // 6 Reserved + uint32_t nptxf_empty_lvl : 1; // 7 Non-periodic Tx FIFO empty level + uint32_t ptxf_empty_lvl : 1; // 8 Periodic Tx FIFO empty level + uint32_t rsv9_20 : 12; // 9.20: Reserved + uint32_t remote_mem_support : 1; // 21 Remote memory support + uint32_t notify_all_dma_write : 1; // 22 Notify all DMA writes + uint32_t ahb_single : 1; // 23 AHB single + uint32_t inv_desc_endian : 1; // 24 Inverse descriptor endian + uint32_t rsv25_31 : 7; // 25..31 Reserved + }; } dwc2_gahbcfg_t; TU_VERIFY_STATIC(sizeof(dwc2_gahbcfg_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t timeout_cal : 3; /* 0..2 Timeout calibration. - The USB standard timeout value for high-speed operation is 736 to 816 (inclusive) bit times. The USB standard - timeout value for full- speed operation is 16 to 18 (inclusive) bit times. The application must program this field - based on the speed of enumeration. The number of bit times added per PHY clock are as follows: - - High-speed: PHY clock One 30-MHz = 16 bit times, One 60-MHz = 8 bit times - - Full-speed: PHY clock One 30-MHz = 0.4 bit times, One 60-MHz = 0.2 bit times, One 48-MHz = 0.25 bit times */ - uint32_t phy_if16 : 1; // 3 PHY interface. 0: 8 bits, 1: 16 bits - uint32_t ulpi_utmi_sel : 1; // 4 ULPI/UTMI select. 0: UTMI+, 1: ULPI - uint32_t fs_intf_sel : 1; // 5 Fullspeed serial interface select. 0: 6-pin, 1: 3-pin - uint32_t phy_sel : 1; // 6 HS/FS PHY selection. 0: HS UTMI+ or ULPI, 1: FS serial transceiver - uint32_t ddr_sel : 1; // 7 ULPI DDR select. 0: Single data rate 8-bit, 1: Double data rate 4-bit - uint32_t srp_capable : 1; // 8 SRP-capable - uint32_t hnp_capable : 1; // 9 HNP-capable - uint32_t turnaround_time : 4; // 10..13 Turnaround time. 9: 8-bit UTMI+, 5: 16-bit UTMI+ - uint32_t rsv14 : 1; // 14 Reserved - uint32_t phy_low_power_clk_sel : 1; /* 15 PHY low-power clock select either 480-MHz or 48-MHz (low-power) PHY mode. - In FS/LS modes, the PHY can usually operate on a 48-MHz clock to save power. This bit is valid only for UTMI+ PHYs. - - 0: 480 Mhz internal PLL: the UTMI interface operates at either 60 MHz (8 bit) or 30 MHz (16-bit) - - 1 48 Mhz external clock: the UTMI interface operates at 48 MHz in FS mode and at either 48 or 6 MHz in LS mode */ - uint32_t otg_i2c_sel : 1; // 16 OTG I2C interface select. 0: UTMI-FS, 1: I2C for OTG signals - uint32_t ulpi_fsls : 1; /* 17 ULPI FS/LS select. 0: ULPI, 1: ULPI FS/LS. - valid only when the FS serial transceiver is selected on the ULPI PHY. */ - uint32_t ulpi_auto_resume : 1; // 18 ULPI Auto-resume - uint32_t ulpi_clk_sus_m : 1; // 19 ULPI Clock SuspendM - uint32_t ulpi_ext_vbus_drv : 1; // 20 ULPI External VBUS Drive - uint32_t ulpi_int_vbus_indicator : 1; // 21 ULPI Internal VBUS Indicator - uint32_t term_sel_dl_pulse : 1; // 22 TermSel DLine pulsing - uint32_t indicator_complement : 1; // 23 Indicator complement - uint32_t indicator_pass_through : 1; // 24 Indicator pass through - uint32_t ulpi_if_protect_disable : 1; // 25 ULPI interface protect disable - uint32_t ic_usb_capable : 1; // 26 IC_USB Capable - uint32_t ic_usb_traf_ctl : 1; // 27 IC_USB Traffic Control - uint32_t tx_end_delay : 1; // 28 TX end delay - uint32_t force_host_mode : 1; // 29 Force host mode - uint32_t force_dev_mode : 1; // 30 Force device mode - uint32_t corrupt_tx_pkt : 1; // 31 Corrupt Tx packet. 0: normal, 1: debug +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t timeout_cal : 3; /* 0..2 Timeout calibration. + The USB standard timeout value for high-speed operation is 736 to 816 (inclusive) bit times. The USB standard + timeout value for full- speed operation is 16 to 18 (inclusive) bit times. The application must program this field + based on the speed of enumeration. The number of bit times added per PHY clock are as follows: + - High-speed: PHY clock One 30-MHz = 16 bit times, One 60-MHz = 8 bit times + - Full-speed: PHY clock One 30-MHz = 0.4 bit times, One 60-MHz = 0.2 bit times, One 48-MHz = 0.25 bit times */ + uint32_t phy_if16 : 1; // 3 PHY interface. 0: 8 bits, 1: 16 bits + uint32_t ulpi_utmi_sel : 1; // 4 ULPI/UTMI select. 0: UTMI+, 1: ULPI + uint32_t fs_intf_sel : 1; // 5 Fullspeed serial interface select. 0: 6-pin, 1: 3-pin + uint32_t phy_sel : 1; // 6 HS/FS PHY selection. 0: HS UTMI+ or ULPI, 1: FS serial transceiver + uint32_t ddr_sel : 1; // 7 ULPI DDR select. 0: Single data rate 8-bit, 1: Double data rate 4-bit + uint32_t srp_capable : 1; // 8 SRP-capable + uint32_t hnp_capable : 1; // 9 HNP-capable + uint32_t turnaround_time : 4; // 10..13 Turnaround time. 9: 8-bit UTMI+, 5: 16-bit UTMI+ + uint32_t rsv14 : 1; // 14 Reserved + uint32_t phy_low_power_clk_sel : 1; /* 15 PHY low-power clock select either 480-MHz or 48-MHz (low-power) PHY mode. + In FS/LS modes, the PHY can usually operate on a 48-MHz clock to save power. This bit is valid only for UTMI+ PHYs. + - 0: 480 Mhz internal PLL: the UTMI interface operates at either 60 MHz (8 bit) or 30 MHz (16-bit) + - 1 48 Mhz external clock: the UTMI interface operates at 48 MHz in FS mode and at either 48 or 6 MHz in LS mode */ + uint32_t otg_i2c_sel : 1; // 16 OTG I2C interface select. 0: UTMI-FS, 1: I2C for OTG signals + uint32_t ulpi_fsls : 1; /* 17 ULPI FS/LS select. 0: ULPI, 1: ULPI FS/LS. + valid only when the FS serial transceiver is selected on the ULPI PHY. */ + uint32_t ulpi_auto_resume : 1; // 18 ULPI Auto-resume + uint32_t ulpi_clk_sus_m : 1; // 19 ULPI Clock SuspendM + uint32_t ulpi_ext_vbus_drv : 1; // 20 ULPI External VBUS Drive + uint32_t ulpi_int_vbus_indicator : 1; // 21 ULPI Internal VBUS Indicator + uint32_t term_sel_dl_pulse : 1; // 22 TermSel DLine pulsing + uint32_t indicator_complement : 1; // 23 Indicator complement + uint32_t indicator_pass_through : 1; // 24 Indicator pass through + uint32_t ulpi_if_protect_disable : 1; // 25 ULPI interface protect disable + uint32_t ic_usb_capable : 1; // 26 IC_USB Capable + uint32_t ic_usb_traf_ctl : 1; // 27 IC_USB Traffic Control + uint32_t tx_end_delay : 1; // 28 TX end delay + uint32_t force_host_mode : 1; // 29 Force host mode + uint32_t force_dev_mode : 1; // 30 Force device mode + uint32_t corrupt_tx_pkt : 1; // 31 Corrupt Tx packet. 0: normal, 1: debug + }; } dwc2_gusbcfg_t; TU_VERIFY_STATIC(sizeof(dwc2_gusbcfg_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t core_soft_rst : 1; // 0 Core Soft Reset - uint32_t piufs_soft_rst : 1; // 1 PIU FS Dedicated Controller Soft Reset - uint32_t frame_counter_rst : 1; // 2 Frame Counter Reset (host) - uint32_t intoken_q_flush : 1; // 3 IN Token Queue Flush - uint32_t rx_fifo_flush : 1; // 4 RX FIFO Flush - uint32_t tx_fifo_flush : 1; // 5 TX FIFO Flush - uint32_t tx_fifo_num : 5; // 6..10 TX FIFO Number - uint32_t rsv11_28 :18; // 11..28 Reserved - uint32_t core_soft_rst_done : 1; // 29 Core Soft Reset Done, from v4.20a - uint32_t dma_req : 1; // 30 DMA Request - uint32_t ahb_idle : 1; // 31 AHB Idle +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t core_soft_rst : 1; // 0 Core Soft Reset + uint32_t piufs_soft_rst : 1; // 1 PIU FS Dedicated Controller Soft Reset + uint32_t frame_counter_rst : 1; // 2 Frame Counter Reset (host) + uint32_t intoken_q_flush : 1; // 3 IN Token Queue Flush + uint32_t rx_fifo_flush : 1; // 4 RX FIFO Flush + uint32_t tx_fifo_flush : 1; // 5 TX FIFO Flush + uint32_t tx_fifo_num : 5; // 6..10 TX FIFO Number + uint32_t rsv11_28 :18; // 11..28 Reserved + uint32_t core_soft_rst_done : 1; // 29 Core Soft Reset Done, from v4.20a + uint32_t dma_req : 1; // 30 DMA Request + uint32_t ahb_idle : 1; // 31 AHB Idle + }; } dwc2_grstctl_t; TU_VERIFY_STATIC(sizeof(dwc2_grstctl_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t ep_ch_num : 4; // 0..3 Endpoint/Channel Number - uint32_t byte_count :11; // 4..14 Byte Count - uint32_t dpid : 2; // 15..16 Data PID - uint32_t packet_status : 4; // 17..20 Packet Status - uint32_t frame_number : 4; // 21..24 Frame Number - uint32_t rsv25_31 : 7; // 25..31 Reserved +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t ep_ch_num : 4; // 0..3 Endpoint/Channel Number + uint32_t byte_count :11; // 4..14 Byte Count + uint32_t dpid : 2; // 15..16 Data PID + uint32_t packet_status : 4; // 17..20 Packet Status + uint32_t frame_number : 4; // 21..24 Frame Number + uint32_t rsv25_31 : 7; // 25..31 Reserved + }; } dwc2_grxstsp_t; TU_VERIFY_STATIC(sizeof(dwc2_grxstsp_t) == 4, "incorrect size"); -// Hardware Configuration -typedef struct TU_ATTR_PACKED { - uint32_t op_mode : 3; // 0..2 HNP/SRP Host/Device/OTG mode - uint32_t arch : 2; // 3..4 Slave/External/Internal DMA - uint32_t single_point : 1; // 5 0: support hub and split | 1: no hub, no split - uint32_t hs_phy_type : 2; // 6..7 0: not supported | 1: UTMI+ | 2: ULPI | 3: UTMI+ and ULPI - uint32_t fs_phy_type : 2; // 8..9 0: not supported | 1: dedicated | 2: UTMI+ | 3: ULPI - uint32_t num_dev_ep : 4; // 10..13 Number of device endpoints (excluding EP0) - uint32_t num_host_ch : 4; // 14..17 Number of host channel (excluding control) - uint32_t period_channel_support : 1; // 18 Support Periodic OUT Host Channel - uint32_t enable_dynamic_fifo : 1; // 19 Dynamic FIFO Sizing Enabled - uint32_t mul_proc_intrpt : 1; // 20 Multi-Processor Interrupt enabled (OTG_MULTI_PROC_INTRPT) - uint32_t reserved21 : 1; // 21 reserved - uint32_t nptx_q_depth : 2; // 22..23 Non-periodic request queue depth: 0 = 2. 1 = 4, 2 = 8 - uint32_t ptx_q_depth : 2; // 24..25 Host periodic request queue depth: 0 = 2. 1 = 4, 2 = 8 - uint32_t token_q_depth : 5; // 26..30 Device IN token sequence learning queue depth: 0-30 - uint32_t otg_enable_ic_usb : 1; // 31 IC_USB mode specified for mode of operation +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t op_mode : 3; // 0..2 HNP/SRP Host/Device/OTG mode + uint32_t arch : 2; // 3..4 Slave/External/Internal DMA + uint32_t single_point : 1; // 5 0: support hub and split | 1: no hub, no split + uint32_t hs_phy_type : 2; // 6..7 0: not supported | 1: UTMI+ | 2: ULPI | 3: UTMI+ and ULPI + uint32_t fs_phy_type : 2; // 8..9 0: not supported | 1: dedicated | 2: UTMI+ | 3: ULPI + uint32_t num_dev_ep : 4; // 10..13 Number of device endpoints (excluding EP0) + uint32_t num_host_ch : 4; // 14..17 Number of host channel (excluding control) + uint32_t period_channel_support : 1; // 18 Support Periodic OUT Host Channel + uint32_t enable_dynamic_fifo : 1; // 19 Dynamic FIFO Sizing Enabled + uint32_t mul_proc_intrpt : 1; // 20 Multi-Processor Interrupt enabled (OTG_MULTI_PROC_INTRPT) + uint32_t reserved21 : 1; // 21 reserved + uint32_t nptx_q_depth : 2; // 22..23 Non-periodic request queue depth: 0 = 2. 1 = 4, 2 = 8 + uint32_t ptx_q_depth : 2; // 24..25 Host periodic request queue depth: 0 = 2. 1 = 4, 2 = 8 + uint32_t token_q_depth : 5; // 26..30 Device IN token sequence learning queue depth: 0-30 + uint32_t otg_enable_ic_usb : 1; // 31 IC_USB mode specified for mode of operation + }; } dwc2_ghwcfg2_t; TU_VERIFY_STATIC(sizeof(dwc2_ghwcfg2_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t xfer_size_width : 4; // 0..3 Transfer size counter in bits = 11 + n (max 19 bits) - uint32_t packet_size_width : 3; // 4..6 Packet size counter in bits = 4 + n (max 10 bits) - uint32_t otg_enable : 1; // 7 OTG capable - uint32_t i2c_enable : 1; // 8 I2C interface is available - uint32_t vendor_ctrl_itf : 1; // 9 Vendor control interface is available - uint32_t optional_feature_removed : 1; // 10 remove User ID, GPIO, SOF toggle & counter to save gate count - uint32_t synch_reset : 1; // 11 0: async reset | 1: synch reset - uint32_t otg_adp_support : 1; // 12 ADP logic is present along with HSOTG controller - uint32_t otg_enable_hsic : 1; // 13 1: HSIC-capable with shared UTMI PHY interface | 0: non-HSIC - uint32_t battery_charger_support : 1; // s14 upport battery charger - uint32_t lpm_mode : 1; // 15 LPM mode - uint32_t dfifo_depth : 16; // DFIFO depth - EP_LOC_CNT in terms of 32-bit words -}dwc2_ghwcfg3_t; +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_size_width : 4; // 0..3 Transfer size counter in bits = 11 + n (max 19 bits) + uint32_t packet_size_width : 3; // 4..6 Packet size counter in bits = 4 + n (max 10 bits) + uint32_t otg_enable : 1; // 7 OTG capable + uint32_t i2c_enable : 1; // 8 I2C interface is available + uint32_t vendor_ctrl_itf : 1; // 9 Vendor control interface is available + uint32_t optional_feature_removed : 1; // 10 remove User ID, GPIO, SOF toggle & counter to save gate count + uint32_t synch_reset : 1; // 11 0: async reset | 1: synch reset + uint32_t otg_adp_support : 1; // 12 ADP logic is present along with HSOTG controller + uint32_t otg_enable_hsic : 1; // 13 1: HSIC-capable with shared UTMI PHY interface | 0: non-HSIC + uint32_t battery_charger_support : 1; // s14 upport battery charger + uint32_t lpm_mode : 1; // 15 LPM mode + uint32_t dfifo_depth : 16; // DFIFO depth - EP_LOC_CNT in terms of 32-bit words + }; +} dwc2_ghwcfg3_t; TU_VERIFY_STATIC(sizeof(dwc2_ghwcfg3_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t num_dev_period_in_ep : 4; // 0..3 Number of Device Periodic IN Endpoints - uint32_t partial_powerdown : 1; // 4 Partial Power Down Enabled - uint32_t ahb_freq_min : 1; // 5 1: minimum of AHB frequency is less than 60 MHz - uint32_t hibernation : 1; // 6 Hibernation feature is enabled - uint32_t extended_hibernation : 1; // 7 Extended Hibernation feature is enabled - uint32_t reserved8 : 1; // 8 Reserved - uint32_t enhanced_lpm_support1 : 1; // 9 Enhanced LPM Support1 - uint32_t service_interval_flow : 1; // 10 Service Interval flow is supported - uint32_t ipg_isoc_support : 1; // 11 Interpacket GAP ISO OUT worst-case is supported - uint32_t acg_support : 1; // 12 Active clock gating is supported - uint32_t enhanced_lpm_support : 1; // 13 Enhanced LPM Support - uint32_t phy_data_width : 2; // 14..15 0: 8 bits | 1: 16 bits | 2: 8/16 software selectable - uint32_t ctrl_ep_num : 4; // 16..19 Number of Device control endpoints in addition to EP0 - uint32_t iddg_filter : 1; // 20 IDDG Filter Enabled - uint32_t vbus_valid_filter : 1; // 21 VBUS Valid Filter Enabled - uint32_t a_valid_filter : 1; // 22 A Valid Filter Enabled - uint32_t b_valid_filter : 1; // 23 B Valid Filter Enabled - uint32_t session_end_filter : 1; // 24 Session End Filter Enabled - uint32_t dedicated_fifos : 1; // 25 Dedicated tx fifo for device IN Endpoint - uint32_t num_dev_in_eps : 4; // 26..29 Number of Device IN Endpoints including EP0 - uint32_t dma_desc_enabled : 1; // scatter/gather DMA configuration enabled - uint32_t dma_desc_dynamic : 1; // Dynamic scatter/gather DMA -}dwc2_ghwcfg4_t; +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t num_dev_period_in_ep : 4; // 0..3 Number of Device Periodic IN Endpoints + uint32_t partial_powerdown : 1; // 4 Partial Power Down Enabled + uint32_t ahb_freq_min : 1; // 5 1: minimum of AHB frequency is less than 60 MHz + uint32_t hibernation : 1; // 6 Hibernation feature is enabled + uint32_t extended_hibernation : 1; // 7 Extended Hibernation feature is enabled + uint32_t reserved8 : 1; // 8 Reserved + uint32_t enhanced_lpm_support1 : 1; // 9 Enhanced LPM Support1 + uint32_t service_interval_flow : 1; // 10 Service Interval flow is supported + uint32_t ipg_isoc_support : 1; // 11 Interpacket GAP ISO OUT worst-case is supported + uint32_t acg_support : 1; // 12 Active clock gating is supported + uint32_t enhanced_lpm_support : 1; // 13 Enhanced LPM Support + uint32_t phy_data_width : 2; // 14..15 0: 8 bits | 1: 16 bits | 2: 8/16 software selectable + uint32_t ctrl_ep_num : 4; // 16..19 Number of Device control endpoints in addition to EP0 + uint32_t iddg_filter : 1; // 20 IDDG Filter Enabled + uint32_t vbus_valid_filter : 1; // 21 VBUS Valid Filter Enabled + uint32_t a_valid_filter : 1; // 22 A Valid Filter Enabled + uint32_t b_valid_filter : 1; // 23 B Valid Filter Enabled + uint32_t session_end_filter : 1; // 24 Session End Filter Enabled + uint32_t dedicated_fifos : 1; // 25 Dedicated tx fifo for device IN Endpoint + uint32_t num_dev_in_eps : 4; // 26..29 Number of Device IN Endpoints including EP0 + uint32_t dma_desc_enabled : 1; // scatter/gather DMA configuration enabled + uint32_t dma_desc_dynamic : 1; // Dynamic scatter/gather DMA + }; +} dwc2_ghwcfg4_t; TU_VERIFY_STATIC(sizeof(dwc2_ghwcfg4_t) == 4, "incorrect size"); -//-------------------------------------------------------------------- -// Host Register Bitfield -//-------------------------------------------------------------------- - -typedef struct TU_ATTR_PACKED { - uint32_t fifo_available : 16; // 0..15 Number of words available in the Tx FIFO - uint32_t req_queue_available : 8; // 16..23 Number of spaces available in the NPT transmit request queue for both IN and OU - // 24..31 is top entry in the request queue that is currently being processed by the MAC - uint32_t qtop_terminate : 1; // 24 Last entry for selected channel - uint32_t qtop_type : 2; // 25..26 Token (0) In/Out (1) ZLP, (2) Ping/cspit, (3) Channel halt command - uint32_t qtop_ch_num : 4; // 27..30 Channel number +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t fifo_available : 16; // 0..15 Number of words available in the Tx FIFO + uint32_t req_queue_available : 8; // 16..23 Number of spaces available in the NPT transmit request queue for both IN and OU + // 24..31 is top entry in the request queue that is currently being processed by the MAC + uint32_t qtop_terminate : 1; // 24 Last entry for selected channel + uint32_t qtop_type : 2; // 25..26 Token (0) In/Out (1) ZLP, (2) Ping/cspit, (3) Channel halt command + uint32_t qtop_ch_num : 4; // 27..30 Channel number + }; } dwc2_hnptxsts_t; TU_VERIFY_STATIC(sizeof(dwc2_hnptxsts_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t fifo_available : 16; // 0..15 Number of words available in the Tx FIFO - uint32_t req_queue_available : 7; // 16..22 Number of spaces available in the PTX transmit request queue - uint32_t qtop_terminate : 1; // 23 Last entry for selected channel - uint32_t qtop_last_period : 1; // 24 Last entry for selected channel is a periodic entry - uint32_t qtop_type : 2; // 25..26 Token (0) In/Out (1) ZLP, (2) Ping/cspit, (3) Channel halt command - uint32_t qtop_ch_num : 4; // 27..30 Channel number - uint32_t qtop_odd_frame : 1; // 31 Send in odd frame +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t fifo_available :16; // 0..15 Number of words available in the Tx FIFO + uint32_t req_queue_available : 7; // 16..22 Number of spaces available in the PTX transmit request queue + uint32_t qtop_terminate : 1; // 23 Last entry for selected channel + uint32_t qtop_last_period : 1; // 24 Last entry for selected channel is a periodic entry + uint32_t qtop_type : 2; // 25..26 Token (0) In/Out (1) ZLP, (2) Ping/cspit, (3) Channel halt command + uint32_t qtop_ch_num : 4; // 27..30 Channel number + uint32_t qtop_odd_frame : 1; // 31 Send in odd frame + }; } dwc2_hptxsts_t; TU_VERIFY_STATIC(sizeof(dwc2_hptxsts_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t conn_status : 1; // 0 Port connect status - uint32_t conn_detected : 1; // 1 Port connect detected - uint32_t enable : 1; // 2 Port enable status - uint32_t enable_change : 1; // 3 Port enable change - uint32_t over_current_active : 1; // 4 Port Over-current active - uint32_t over_current_change : 1; // 5 Port Over-current change - uint32_t resume : 1; // 6 Port resume - uint32_t suspend : 1; // 7 Port suspend - uint32_t reset : 1; // 8 Port reset - uint32_t rsv9 : 1; // 9 Reserved - uint32_t line_status : 2; // 10..11 Line status - uint32_t power : 1; // 12 Port power - uint32_t test_control : 4; // 13..16 Port Test control - uint32_t speed : 2; // 17..18 Port speed - uint32_t rsv19_31 :13; // 19..31 Reserved -}dwc2_hprt_t; +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t conn_status : 1; // 0 Port connect status + uint32_t conn_detected : 1; // 1 Port connect detected + uint32_t enable : 1; // 2 Port enable status + uint32_t enable_change : 1; // 3 Port enable change + uint32_t over_current_active : 1; // 4 Port Over-current active + uint32_t over_current_change : 1; // 5 Port Over-current change + uint32_t resume : 1; // 6 Port resume + uint32_t suspend : 1; // 7 Port suspend + uint32_t reset : 1; // 8 Port reset + uint32_t rsv9 : 1; // 9 Reserved + uint32_t line_status : 2; // 10..11 Line status + uint32_t power : 1; // 12 Port power + uint32_t test_control : 4; // 13..16 Port Test control + uint32_t speed : 2; // 17..18 Port speed + uint32_t rsv19_31 :13; // 19..31 Reserved + }; +} dwc2_hprt_t; TU_VERIFY_STATIC(sizeof(dwc2_hprt_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t ep_size : 11; // 0..10 Maximum packet size - uint32_t ep_num : 4; // 11..14 Endpoint number - uint32_t ep_dir : 1; // 15 Endpoint direction - uint32_t rsv16 : 1; // 16 Reserved - uint32_t low_speed_dev : 1; // 17 Low-speed device - uint32_t ep_type : 2; // 18..19 Endpoint type - uint32_t err_multi_count : 2; // 20..21 Error (splitEn = 1) / Multi (SplitEn = 0) count - uint32_t dev_addr : 7; // 22..28 Device address - uint32_t odd_frame : 1; // 29 Odd frame - uint32_t disable : 1; // 30 Channel disable - uint32_t enable : 1; // 31 Channel enable +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t ep_size : 11; // 0..10 Maximum packet size + uint32_t ep_num : 4; // 11..14 Endpoint number + uint32_t ep_dir : 1; // 15 Endpoint direction + uint32_t rsv16 : 1; // 16 Reserved + uint32_t low_speed_dev : 1; // 17 Low-speed device + uint32_t ep_type : 2; // 18..19 Endpoint type + uint32_t err_multi_count : 2; // 20..21 Error (splitEn = 1) / Multi (SplitEn = 0) count + uint32_t dev_addr : 7; // 22..28 Device address + uint32_t odd_frame : 1; // 29 Odd frame + uint32_t disable : 1; // 30 Channel disable + uint32_t enable : 1; // 31 Channel enable + }; } dwc2_channel_char_t; TU_VERIFY_STATIC(sizeof(dwc2_channel_char_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t hub_port : 7; // 0..6 Hub port number - uint32_t hub_addr : 7; // 7..13 Hub address - uint32_t xact_pos : 2; // 14..15 Transaction position - uint32_t split_compl : 1; // 16 Split completion - uint32_t rsv17_30 : 14; // 17..30 Reserved - uint32_t split_en : 1; // 31 Split enable +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t hub_port : 7; // 0..6 Hub port number + uint32_t hub_addr : 7; // 7..13 Hub address + uint32_t xact_pos : 2; // 14..15 Transaction position + uint32_t split_compl : 1; // 16 Split completion + uint32_t rsv17_30 : 14; // 17..30 Reserved + uint32_t split_en : 1; // 31 Split enable + }; } dwc2_channel_split_t; TU_VERIFY_STATIC(sizeof(dwc2_channel_split_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t xfer_size : 19; // 0..18 Transfer size in bytes - uint32_t packet_count : 10; // 19..28 Number of packets - uint32_t pid : 2; // 29..30 Packet ID - uint32_t do_ping : 1; // 31 Do PING +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_size : 19; // 0..18 Transfer size in bytes + uint32_t packet_count : 10; // 19..28 Number of packets + uint32_t pid : 2; // 29..30 Packet ID + uint32_t do_ping : 1; // 31 Do PING + }; } dwc2_channel_tsize_t; TU_VERIFY_STATIC(sizeof(dwc2_channel_tsize_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t num : 16; // 0..15 Frame number - uint32_t remainning : 16; // 16..31 Frame remaining +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t num : 16; // 0..15 Frame number + uint32_t remainning : 16; // 16..31 Frame remaining + }; } dwc2_hfnum_t; TU_VERIFY_STATIC(sizeof(dwc2_hfnum_t) == 4, "incorrect size"); // Host Channel typedef struct { - union { - volatile uint32_t hcchar; // 500 + 20*ch Host Channel Characteristics - volatile dwc2_channel_char_t hcchar_bm; - }; - union { - volatile uint32_t hcsplt; // 504 + 20*ch Host Channel Split Control - volatile dwc2_channel_split_t hcsplt_bm; - }; - volatile uint32_t hcint; // 508 + 20*ch Host Channel Interrupt - volatile uint32_t hcintmsk; // 50C + 20*ch Host Channel Interrupt Mask - union { - volatile uint32_t hctsiz; // 510 + 20*ch Host Channel Transfer Size - volatile dwc2_channel_tsize_t hctsiz_bm; - }; - volatile uint32_t hcdma; // 514 + 20*ch Host Channel DMA Address - uint32_t reserved518; // 518 + 20*ch - volatile uint32_t hcdmab; // 51C + 20*ch Host Channel DMA Address + volatile uint32_t hcchar; // 500 + 20*ch Host Channel Characteristics + volatile uint32_t hcsplt; // 504 + 20*ch Host Channel Split Control + volatile uint32_t hcint; // 508 + 20*ch Host Channel Interrupt + volatile uint32_t hcintmsk; // 50C + 20*ch Host Channel Interrupt Mask + volatile uint32_t hctsiz; // 510 + 20*ch Host Channel Transfer Size + volatile uint32_t hcdma; // 514 + 20*ch Host Channel DMA Address + uint32_t reserved518; // 518 + 20*ch + volatile uint32_t hcdmab; // 51C + 20*ch Host Channel DMA Address } dwc2_channel_t; //-------------------------------------------------------------------- // Device Register Bitfield //-------------------------------------------------------------------- -typedef struct TU_ATTR_PACKED { - uint32_t speed : 2; // 0..1 Speed - uint32_t nzsts_out_handshake : 1; // 2 Non-zero-length status OUT handshake - uint32_t en_32khz_suspsend : 1; // 3 Enable 32-kHz SUSPEND mode - uint32_t address : 7; // 4..10 Device address - uint32_t period_frame_interval : 2; // 11..12 Periodic frame interval - uint32_t en_out_nak : 1; // 13 Enable Device OUT NAK - uint32_t xcvr_delay : 1; // 14 Transceiver delay - uint32_t erratic_int_mask : 1; // 15 Erratic interrupt mask - uint32_t rsv16 : 1; // 16 Reserved - uint32_t ipg_iso_support : 1; // 17 Interpacket gap ISO support - uint32_t epin_mismatch_count : 5; // 18..22 EP IN mismatch count - uint32_t dma_desc : 1; // 23 Enable scatter/gatter DMA descriptor - uint32_t period_schedule_interval : 2; // 24..25 Periodic schedule interval for scatter/gatter DMA - uint32_t resume_valid : 6; // 26..31 Resume valid period +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t speed : 2; // 0..1 Speed + uint32_t nzsts_out_handshake : 1; // 2 Non-zero-length status OUT handshake + uint32_t en_32khz_suspsend : 1; // 3 Enable 32-kHz SUSPEND mode + uint32_t address : 7; // 4..10 Device address + uint32_t period_frame_interval : 2; // 11..12 Periodic frame interval + uint32_t en_out_nak : 1; // 13 Enable Device OUT NAK + uint32_t xcvr_delay : 1; // 14 Transceiver delay + uint32_t erratic_int_mask : 1; // 15 Erratic interrupt mask + uint32_t rsv16 : 1; // 16 Reserved + uint32_t ipg_iso_support : 1; // 17 Interpacket gap ISO support + uint32_t epin_mismatch_count : 5; // 18..22 EP IN mismatch count + uint32_t dma_desc : 1; // 23 Enable scatter/gather DMA descriptor + uint32_t period_schedule_interval : 2; // 24..25 Periodic schedule interval for scatter/gather DMA + uint32_t resume_valid : 6; // 26..31 Resume valid period + }; } dwc2_dcfg_t; TU_VERIFY_STATIC(sizeof(dwc2_dcfg_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t remote_wakeup_signal : 1; // 0 Remote wakeup signal - uint32_t soft_disconnet : 1; // 1 Soft disconnect - uint32_t gnp_in_nak_status : 1; // 2 Global non-periodic NAK IN status - uint32_t gout_nak_status : 1; // 3 Global OUT NAK status - uint32_t test_control : 3; // 4..6 Test control - uint32_t set_gnp_in_nak : 1; // 7 Set global non-periodic IN NAK - uint32_t clear_gnp_in_nak : 1; // 8 Clear global non-periodic IN NAK - uint32_t set_gout_nak : 1; // 9 Set global OUT NAK - uint32_t clear_gout_nak : 1; // 10 Clear global OUT NAK - uint32_t poweron_prog_done : 1; // 11 Power-on programming done - uint32_t rsv12 : 1; // 12 Reserved - uint32_t global_multi_count : 2; // 13..14 Global multi-count - uint32_t ignore_frame_number : 1; // 15 Ignore frame number - uint32_t nak_on_babble : 1; // 16 NAK on babble - uint32_t en_cont_on_bna : 1; // 17 Enable continue on BNA - uint32_t deep_sleep_besl_reject : 1; // 18 Deep sleep BESL reject - uint32_t service_interval : 1; // 19 Service interval for ISO IN endpoint - uint32_t rsv20_31 :12; // 20..31 Reserved +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t remote_wakeup_signal : 1; // 0 Remote wakeup signal + uint32_t soft_disconnet : 1; // 1 Soft disconnect + uint32_t gnp_in_nak_status : 1; // 2 Global non-periodic NAK IN status + uint32_t gout_nak_status : 1; // 3 Global OUT NAK status + uint32_t test_control : 3; // 4..6 Test control + uint32_t set_gnp_in_nak : 1; // 7 Set global non-periodic IN NAK + uint32_t clear_gnp_in_nak : 1; // 8 Clear global non-periodic IN NAK + uint32_t set_gout_nak : 1; // 9 Set global OUT NAK + uint32_t clear_gout_nak : 1; // 10 Clear global OUT NAK + uint32_t poweron_prog_done : 1; // 11 Power-on programming done + uint32_t rsv12 : 1; // 12 Reserved + uint32_t global_multi_count : 2; // 13..14 Global multi-count + uint32_t ignore_frame_number : 1; // 15 Ignore frame number + uint32_t nak_on_babble : 1; // 16 NAK on babble + uint32_t en_cont_on_bna : 1; // 17 Enable continue on BNA + uint32_t deep_sleep_besl_reject : 1; // 18 Deep sleep BESL reject + uint32_t service_interval : 1; // 19 Service interval for ISO IN endpoint + uint32_t rsv20_31 :12; // 20..31 Reserved + }; } dwc2_dctl_t; TU_VERIFY_STATIC(sizeof(dwc2_dctl_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t suspend_status : 1; // 0 Suspend status - uint32_t enum_speed : 2; // 1..2 Enumerated speed - uint32_t erratic_err : 1; // 3 Erratic error - uint32_t rsv4_7 : 4; // 4..7 Reserved - uint32_t frame_number : 14; // 8..21 Frame/MicroFrame number - uint32_t line_status : 2; // 22..23 Line status - uint32_t rsv24_31 : 8; // 24..31 Reserved +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t suspend_status : 1; // 0 Suspend status + uint32_t enum_speed : 2; // 1..2 Enumerated speed + uint32_t erratic_err : 1; // 3 Erratic error + uint32_t rsv4_7 : 4; // 4..7 Reserved + uint32_t frame_number :14; // 8..21 Frame/MicroFrame number + uint32_t line_status : 2; // 22..23 Line status + uint32_t rsv24_31 : 8; // 24..31 Reserved + }; } dwc2_dsts_t; TU_VERIFY_STATIC(sizeof(dwc2_dsts_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t xfer_complete : 1; // 0 Transfer complete - uint32_t disabled : 1; // 1 Endpoint disabled - uint32_t ahb_err : 1; // 2 AHB error - uint32_t timeout : 1; // 3 Timeout - uint32_t in_rx_txfe : 1; // 4 IN token received when TxFIFO is empty - uint32_t in_rx_ep_mismatch : 1; // 5 IN token received with EP mismatch - uint32_t in_ep_nak_effective : 1; // 6 IN endpoint NAK effective - uint32_t txfifo_empty : 1; // 7 TX FIFO empty - uint32_t txfifo_underrun : 1; // 8 Tx FIFO under run - uint32_t bna : 1; // 9 Buffer not available - uint32_t rsv10 : 1; // 10 Reserved - uint32_t iso_packet_drop : 1; // 11 Isochronous OUT packet drop status - uint32_t babble_err : 1; // 12 Babble error - uint32_t nak : 1; // 13 NAK - uint32_t nyet : 1; // 14 NYET - uint32_t rsv14_31 :17; // 15..31 Reserved +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_complete : 1; // 0 Transfer complete + uint32_t disabled : 1; // 1 Endpoint disabled + uint32_t ahb_err : 1; // 2 AHB error + uint32_t timeout : 1; // 3 Timeout + uint32_t in_rx_txfe : 1; // 4 IN token received when TxFIFO is empty + uint32_t in_rx_ep_mismatch : 1; // 5 IN token received with EP mismatch + uint32_t in_ep_nak_effective : 1; // 6 IN endpoint NAK effective + uint32_t txfifo_empty : 1; // 7 TX FIFO empty + uint32_t txfifo_underrun : 1; // 8 Tx FIFO under run + uint32_t bna : 1; // 9 Buffer not available + uint32_t rsv10 : 1; // 10 Reserved + uint32_t iso_packet_drop : 1; // 11 Isochronous OUT packet drop status + uint32_t babble_err : 1; // 12 Babble error + uint32_t nak : 1; // 13 NAK + uint32_t nyet : 1; // 14 NYET + uint32_t rsv14_31 :17; // 15..31 Reserved + }; } dwc2_diepint_t; TU_VERIFY_STATIC(sizeof(dwc2_diepint_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t mps : 11; // 0..10 Maximum packet size, EP0 only use 2 bit - uint32_t next_ep : 4; // 11..14 Next endpoint number - uint32_t active : 1; // 15 Active - const uint32_t dpid_iso_odd : 1; // 16 DATA0/DATA1 for bulk/interrupt, odd frame for isochronous - const uint32_t nak_status : 1; // 17 NAK status - uint32_t type : 2; // 18..19 Endpoint type - uint32_t rsv20 : 1; // 20 Reserved - uint32_t stall : 1; // 21 Stall - uint32_t tx_fifo_num : 4; // 22..25 Tx FIFO number (IN) - uint32_t clear_nak : 1; // 26 Clear NAK - uint32_t set_nak : 1; // 27 Set NAK - uint32_t set_data0_iso_even : 1; // 28 Set DATA0 if bulk/interrupt, even frame for isochronous - uint32_t set_data1_iso_odd : 1; // 29 Set DATA1 if bulk/interrupt, odd frame for isochronous - uint32_t disable : 1; // 30 Disable - uint32_t enable : 1; // 31 Enable +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t mps : 11; // 0..10 Maximum packet size, EP0 only use 2 bits + uint32_t next_ep : 4; // 11..14 Next endpoint number + uint32_t active : 1; // 15 Active + uint32_t dpid_iso_odd : 1; // 16 DATA0/DATA1 for bulk/interrupt, odd frame for isochronous + uint32_t nak_status : 1; // 17 NAK status + uint32_t type : 2; // 18..19 Endpoint type + uint32_t rsv20 : 1; // 20 Reserved + uint32_t stall : 1; // 21 Stall + uint32_t tx_fifo_num : 4; // 22..25 Tx FIFO number (IN) + uint32_t clear_nak : 1; // 26 Clear NAK + uint32_t set_nak : 1; // 27 Set NAK + uint32_t set_data0_iso_even : 1; // 28 Set DATA0 if bulk/interrupt, even frame for isochronous + uint32_t set_data1_iso_odd : 1; // 29 Set DATA1 if bulk/interrupt, odd frame for isochronous + uint32_t disable : 1; // 30 Disable + uint32_t enable : 1; // 31 Enable + }; } dwc2_depctl_t; TU_VERIFY_STATIC(sizeof(dwc2_depctl_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t xfer_complete : 1; // 0 Transfer complete - uint32_t disabled : 1; // 1 Endpoint disabled - uint32_t ahb_err : 1; // 2 AHB error - uint32_t setup_phase_done : 1; // 3 Setup phase done - uint32_t out_rx_ep_disabled : 1; // 4 OUT token received when endpoint disabled - uint32_t status_phase_rx : 1; // 5 Status phase received - uint32_t setup_b2b : 1; // 6 Setup packet back-to-back - uint32_t rsv7 : 1; // 7 Reserved - uint32_t out_packet_err : 1; // 8 OUT packet error - uint32_t bna : 1; // 9 Buffer not available - uint32_t rsv10 : 1; // 10 Reserved - uint32_t iso_packet_drop : 1; // 11 Isochronous OUT packet drop status - uint32_t babble_err : 1; // 12 Babble error - uint32_t nak : 1; // 13 NAK - uint32_t nyet : 1; // 14 NYET - uint32_t setup_packet_rx : 1; // 15 Setup packet received (Buffer DMA Mode only) - uint32_t rsv16_31 :16; // 16..31 Reserved +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_complete : 1; // 0 Transfer complete + uint32_t disabled : 1; // 1 Endpoint disabled + uint32_t ahb_err : 1; // 2 AHB error + uint32_t setup_phase_done : 1; // 3 Setup phase done + uint32_t out_rx_ep_disabled : 1; // 4 OUT token received when endpoint disabled + uint32_t status_phase_rx : 1; // 5 Status phase received + uint32_t setup_b2b : 1; // 6 Setup packet back-to-back + uint32_t rsv7 : 1; // 7 Reserved + uint32_t out_packet_err : 1; // 8 OUT packet error + uint32_t bna : 1; // 9 Buffer not available + uint32_t rsv10 : 1; // 10 Reserved + uint32_t iso_packet_drop : 1; // 11 Isochronous OUT packet drop status + uint32_t babble_err : 1; // 12 Babble error + uint32_t nak : 1; // 13 NAK + uint32_t nyet : 1; // 14 NYET + uint32_t setup_packet_rx : 1; // 15 Setup packet received (Buffer DMA Mode only) + uint32_t rsv16_31 :16; // 16..31 Reserved + }; } dwc2_doepint_t; TU_VERIFY_STATIC(sizeof(dwc2_doepint_t) == 4, "incorrect size"); -typedef struct TU_ATTR_PACKED { - uint32_t xfer_size : 19; // 0..18 Transfer size in bytes - uint32_t packet_count : 10; // 19..28 Number of packets - uint32_t mc_pid : 2; // 29..30 IN: Multi Count, OUT: PID +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_size : 19; // 0..18 Transfer size in bytes + uint32_t packet_count : 10; // 19..28 Number of packets + uint32_t mc_pid : 2; // 29..30 IN: Multi Count, OUT: PID + }; } dwc2_ep_tsize_t; TU_VERIFY_STATIC(sizeof(dwc2_ep_tsize_t) == 4, "incorrect size"); @@ -601,26 +656,19 @@ typedef struct { union { volatile uint32_t diepctl; volatile uint32_t doepctl; - volatile uint32_t ctl; - volatile dwc2_depctl_t ctl_bm; }; uint32_t rsv04; union { volatile uint32_t intr; - volatile uint32_t diepint; - volatile dwc2_diepint_t diepint_bm; - volatile uint32_t doepint; - volatile dwc2_doepint_t doepint_bm; }; uint32_t rsv0c; union { volatile uint32_t dieptsiz; volatile uint32_t doeptsiz; volatile uint32_t tsiz; - volatile dwc2_ep_tsize_t tsiz_bm; }; union { volatile uint32_t diepdma; @@ -628,7 +676,7 @@ typedef struct { }; volatile uint32_t dtxfsts; uint32_t rsv1c; -}dwc2_dep_t; +} dwc2_dep_t; TU_VERIFY_STATIC(sizeof(dwc2_dep_t) == 0x20, "incorrect size"); @@ -636,157 +684,108 @@ TU_VERIFY_STATIC(sizeof(dwc2_dep_t) == 0x20, "incorrect size"); // CSR Register Map //-------------------------------------------------------------------- typedef struct { - //------------- Core Global -------------// - union { - volatile uint32_t gotgctl; // 000 OTG Control and Status - volatile dwc2_gotgctl_t gotgctl_bm; - }; - union { - volatile uint32_t gotgint; // 004 OTG Interrupt - volatile dwc2_gotgint_t gotgint_bm; - }; - union { - volatile uint32_t gahbcfg; // 008 AHB Configuration - volatile dwc2_gahbcfg_t gahbcfg_bm; - }; - union { - volatile uint32_t gusbcfg; // 00c USB Configuration - volatile dwc2_gusbcfg_t gusbcfg_bm; - }; - union { - volatile uint32_t grstctl; // 010 Reset - volatile dwc2_grstctl_t grstctl_bm; - }; - volatile uint32_t gintsts; // 014 Interrupt - volatile uint32_t gintmsk; // 018 Interrupt Mask - volatile uint32_t grxstsr; // 01c Receive Status Debug Read - union { - volatile uint32_t grxstsp; // 020 Receive Status Read/Pop - volatile dwc2_grxstsp_t grxstsp_bm; - }; - volatile uint32_t grxfsiz; // 024 Receive FIFO Size + //------------- Core Global ------------- + volatile uint32_t gotgctl; // 000 OTG Control and Status + volatile uint32_t gotgint; // 004 OTG Interrupt + volatile uint32_t gahbcfg; // 008 AHB Configuration + volatile uint32_t gusbcfg; // 00c USB Configuration + volatile uint32_t grstctl; // 010 Reset + volatile uint32_t gintsts; // 014 Interrupt + volatile uint32_t gintmsk; // 018 Interrupt Mask + volatile uint32_t grxstsr; // 01c Receive Status Debug Read + volatile uint32_t grxstsp; // 020 Receive Status Read/Pop + volatile uint32_t grxfsiz; // 024 Receive FIFO Size union { volatile uint32_t dieptxf0; // 028 EP0 Tx FIFO Size volatile uint32_t gnptxfsiz; // 028 Non-periodic Transmit FIFO Size }; union { volatile uint32_t hnptxsts; // 02c Non-periodic Transmit FIFO/Queue Status - volatile dwc2_hnptxsts_t hnptxsts_bm; volatile uint32_t gnptxsts; }; - volatile uint32_t gi2cctl; // 030 I2C Address - volatile uint32_t gpvndctl; // 034 PHY Vendor Control + volatile uint32_t gi2cctl; // 030 I2C Address + volatile uint32_t gpvndctl; // 034 PHY Vendor Control union { volatile uint32_t ggpio; // 038 General Purpose IO volatile uint32_t stm32_gccfg; // 038 STM32 General Core Configuration }; - volatile uint32_t guid; // 03C User (Application programmable) ID - volatile uint32_t gsnpsid; // 040 Synopsys ID + Release version - volatile uint32_t ghwcfg1; // 044 User Hardware Configuration1: endpoint dir (2 bit per ep) - union { - volatile uint32_t ghwcfg2; // 048 User Hardware Configuration2 - volatile dwc2_ghwcfg2_t ghwcfg2_bm; - }; - union { - volatile uint32_t ghwcfg3; // 04C User Hardware Configuration3 - volatile dwc2_ghwcfg3_t ghwcfg3_bm; - }; + volatile uint32_t guid; // 03C User (Application programmable) ID + volatile uint32_t gsnpsid; // 040 Synopsys ID + Release version + volatile uint32_t ghwcfg1; // 044 User Hardware Configuration1: endpoint dir (2 bit per ep) + volatile uint32_t ghwcfg2; // 048 User Hardware Configuration2 + volatile uint32_t ghwcfg3; // 04C User Hardware Configuration3 union { volatile uint32_t ghwcfg4; // 050 User Hardware Configuration4 volatile dwc2_ghwcfg4_t ghwcfg4_bm; }; - volatile uint32_t glpmcfg; // 054 Core LPM Configuration - volatile uint32_t gpwrdn; // 058 Power Down - volatile uint32_t gdfifocfg; // 05C DFIFO Software Configuration - volatile uint32_t gadpctl; // 060 ADP Timer, Control and Status - uint32_t reserved64[39]; // 064..0FF - volatile uint32_t hptxfsiz; // 100 Host Periodic Tx FIFO Size - volatile uint32_t dieptxf[15]; // 104..13C Device Periodic Transmit FIFO Size - uint32_t reserved140[176]; // 140..3FF + volatile uint32_t glpmcfg; // 054 Core LPM Configuration + volatile uint32_t gpwrdn; // 058 Power Down + volatile uint32_t gdfifocfg; // 05C DFIFO Software Configuration + volatile uint32_t gadpctl; // 060 ADP Timer, Control and Status + uint32_t reserved64[39]; // 064..0FF + volatile uint32_t hptxfsiz; // 100 Host Periodic Tx FIFO Size + volatile uint32_t dieptxf[15]; // 104..13C Device Periodic Transmit FIFO Size + uint32_t reserved140[176]; // 140..3FF - //------------ Host -------------// - volatile uint32_t hcfg; // 400 Host Configuration - volatile uint32_t hfir; // 404 Host Frame Interval - union { - volatile uint32_t hfnum; // 408 Host Frame Number / Frame Remaining - volatile dwc2_hfnum_t hfnum_bm; - }; - uint32_t reserved40c; // 40C - union { - volatile uint32_t hptxsts; // 410 Host Periodic TX FIFO / Queue Status - volatile dwc2_hptxsts_t hptxsts_bm; - }; - volatile uint32_t haint; // 414 Host All Channels Interrupt - volatile uint32_t haintmsk; // 418 Host All Channels Interrupt Mask - volatile uint32_t hflbaddr; // 41C Host Frame List Base Address - uint32_t reserved420[8]; // 420..43F - union { - volatile uint32_t hprt; // 440 Host Port Control and Status - volatile dwc2_hprt_t hprt_bm; - }; - uint32_t reserved444[47]; // 444..4FF + //------------ Host ------------- + volatile uint32_t hcfg; // 400 Host Configuration + volatile uint32_t hfir; // 404 Host Frame Interval + volatile uint32_t hfnum; // 408 Host Frame Number / Frame Remaining + uint32_t reserved40c; // 40C + volatile uint32_t hptxsts; // 410 Host Periodic TX FIFO / Queue Status + volatile uint32_t haint; // 414 Host All Channels Interrupt + volatile uint32_t haintmsk; // 418 Host All Channels Interrupt Mask + volatile uint32_t hflbaddr; // 41C Host Frame List Base Address + uint32_t reserved420[8]; // 420..43F + volatile uint32_t hprt; // 440 Host Port Control and Status + uint32_t reserved444[47]; // 444..4FF - //------------- Host Channel -------------// - dwc2_channel_t channel[16]; // 500..6FF Host Channels 0-15 - uint32_t reserved700[64]; // 700..7FF + //------------- Host Channel -------- + dwc2_channel_t channel[16]; // 500..6FF Host Channels 0-15 + uint32_t reserved700[64]; // 700..7FF - //------------- Device -----------// - union { - volatile uint32_t dcfg; // 800 Device Configuration - volatile dwc2_dcfg_t dcfg_bm; - }; - union { - volatile uint32_t dctl; // 804 Device Control - volatile dwc2_dctl_t dctl_bm; - }; - union { - volatile uint32_t dsts; // 808 Device Status (RO) - volatile dwc2_dsts_t dsts_bm; - }; - uint32_t reserved80c; // 80C - union { - volatile uint32_t diepmsk; // 810 Device IN Endpoint Interrupt Mask - volatile dwc2_diepint_t diepmsk_bm; - }; - union { - volatile uint32_t doepmsk; // 814 Device OUT Endpoint Interrupt Mask - volatile dwc2_doepint_t doepmsk_bm; - }; - volatile uint32_t daint; // 818 Device All Endpoints Interrupt - volatile uint32_t daintmsk; // 81C Device All Endpoints Interrupt Mask - volatile uint32_t dtknqr1; // 820 Device IN token sequence learning queue read1 - volatile uint32_t dtknqr2; // 824 Device IN token sequence learning queue read2 - volatile uint32_t dvbusdis; // 828 Device VBUS Discharge Time - volatile uint32_t dvbuspulse; // 82C Device VBUS Pulsing Time - volatile uint32_t dthrctl; // 830 Device threshold Control - volatile uint32_t diepempmsk; // 834 Device IN Endpoint FIFO Empty Interrupt Mask + //------------- Device ----------- + volatile uint32_t dcfg; // 800 Device Configuration + volatile uint32_t dctl; // 804 Device Control + volatile uint32_t dsts; // 808 Device Status (RO) + uint32_t reserved80c; // 80C + volatile uint32_t diepmsk; // 810 Device IN Endpoint Interrupt Mask + volatile uint32_t doepmsk; // 814 Device OUT Endpoint Interrupt Mask + volatile uint32_t daint; // 818 Device All Endpoints Interrupt + volatile uint32_t daintmsk; // 81C Device All Endpoints Interrupt Mask + volatile uint32_t dtknqr1; // 820 Device IN token sequence learning queue read1 + volatile uint32_t dtknqr2; // 824 Device IN token sequence learning queue read2 + volatile uint32_t dvbusdis; // 828 Device VBUS Discharge Time + volatile uint32_t dvbuspulse; // 82C Device VBUS Pulsing Time + volatile uint32_t dthrctl; // 830 Device threshold Control + volatile uint32_t diepempmsk; // 834 Device IN Endpoint FIFO Empty Interrupt Mask - // Device Each Endpoint (IN/OUT) Interrupt/Mask for generating dedicated EP interrupt line - // require OTG_MULTI_PROC_INTRPT=1 - volatile uint32_t deachint; // 838 Device Each Endpoint Interrupt - volatile uint32_t deachmsk; // 83C Device Each Endpoint Interrupt mask - volatile uint32_t diepeachmsk[16]; // 840..87C Device Each IN Endpoint mask - volatile uint32_t doepeachmsk[16]; // 880..8BF Device Each OUT Endpoint mask - uint32_t reserved8c0[16]; // 8C0..8FF + // Device Each Endpoint (IN/OUT) Interrupt/Mask for generating dedicated EP interrupt line require + // OTG_MULTI_PROC_INTRPT=1 + volatile uint32_t deachint; // 838 Device Each Endpoint Interrupt + volatile uint32_t deachmsk; // 83C Device Each Endpoint Interrupt mask + volatile uint32_t diepeachmsk[16]; // 840..87C Device Each IN Endpoint mask + volatile uint32_t doepeachmsk[16]; // 880..8BF Device Each OUT Endpoint mask + uint32_t reserved8c0[16]; // 8C0..8FF - //------------- Device Endpoint -------------// - union { - dwc2_dep_t ep[2][16]; // 0: IN, 1 OUT - struct { - dwc2_dep_t epin[16]; // 900..AFF IN Endpoints - dwc2_dep_t epout[16]; // B00..CFF OUT Endpoints - }; + //------------- Device Endpoint ----- + union { + dwc2_dep_t ep[2][16]; // 0: IN, 1 OUT + struct { + dwc2_dep_t epin[16]; // 900..AFF IN Endpoints + dwc2_dep_t epout[16]; // B00..CFF OUT Endpoints }; - uint32_t reservedd00[64]; // D00..DFF + }; + uint32_t reservedd00[64]; // D00..DFF - //------------- Power Clock -------------// - volatile uint32_t pcgcctl; // E00 Power and Clock Gating Characteristic Control - volatile uint32_t pcgcctl1; // E04 Power and Clock Gating Characteristic Control 1 - uint32_t reservede08[126]; // E08..FFF + //------------- Power Clock --------- + volatile uint32_t pcgcctl; // E00 Power and Clock Gating Characteristic Control + volatile uint32_t pcgcctl1; // E04 Power and Clock Gating Characteristic Control 1 + uint32_t reservede08[126]; // E08..FFF - //------------- FIFOs -------------// - // Word-accessed only using first pointer since it auto shift - volatile uint32_t fifo[16][0x400]; // 1000..FFFF Endpoint FIFO + //------------- FIFOs ------------- + // Word-accessed only using first pointer since it auto shift + volatile uint32_t fifo[16][0x400]; // 1000..FFFF Endpoint FIFO } dwc2_regs_t; TU_VERIFY_STATIC(offsetof(dwc2_regs_t, hcfg ) == 0x0400, "incorrect size"); @@ -2089,9 +2088,9 @@ TU_VERIFY_STATIC(offsetof(dwc2_regs_t, fifo ) == 0x1000, "incorrect size"); #define HCTSIZ_DOPING_Pos (31U) #define HCTSIZ_DOPING_Msk (0x1UL << HCTSIZ_DOPING_Pos) // 0x80000000 #define HCTSIZ_DOPING HCTSIZ_DOPING_Msk // Do PING -#define HCTSIZ_PID_Pos (29U) -#define HCTSIZ_PID_Msk (0x3UL << HCTSIZ_PID_Pos) // 0x60000000 -#define HCTSIZ_PID HCTSIZ_PID_Msk // Data PID +#define HCTSIZ_PID_Pos (29U) +#define HCTSIZ_PID_Msk (0x3UL << HCTSIZ_PID_Pos) // 0x60000000 +#define HCTSIZ_PID HCTSIZ_PID_Msk // Data PID /******************** Bit definition for DIEPDMA register ********************/ #define DIEPDMA_DMAADDR_Pos (0U) diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index b13479b02..257fa2833 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -36,6 +36,7 @@ #define DWC2_DEBUG 2 #include "host/hcd.h" +#include "host/usbh.h" #include "dwc2_common.h" // Max number of endpoints application can open, can be larger than DWC2_CHANNEL_COUNT_MAX @@ -44,8 +45,6 @@ #endif #define DWC2_CHANNEL_COUNT_MAX 16 // absolute max channel count -#define DWC2_CHANNEL_COUNT(_dwc2) tu_min8((_dwc2)->ghwcfg2_bm.num_host_ch + 1, DWC2_CHANNEL_COUNT_MAX) - TU_VERIFY_STATIC(CFG_TUH_DWC2_ENDPOINT_MAX <= 255, "currently only use 8-bit for index"); enum { @@ -77,9 +76,9 @@ typedef struct { struct TU_ATTR_PACKED { uint32_t uframe_interval : 18; // micro-frame interval - uint32_t speed : 2; - uint32_t next_pid : 2; - uint32_t do_ping : 1; + uint32_t speed : 2; + uint32_t next_pid : 2; // PID for next transfer + uint32_t next_do_ping : 1; // Do PING for next transfer if possible (highspeed OUT) // uint32_t : 9; }; @@ -97,7 +96,6 @@ typedef struct { uint8_t err_count : 3; uint8_t period_split_nyet_count : 3; uint8_t halted_nyet : 1; - uint8_t halted_sof_schedule : 1; }; uint8_t result; @@ -116,9 +114,15 @@ hcd_data_t _hcd_data; //-------------------------------------------------------------------- // //-------------------------------------------------------------------- +TU_ATTR_ALWAYS_INLINE static inline uint8_t dwc2_channel_count(const dwc2_regs_t* dwc2) { + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return tu_min8(ghwcfg2.num_host_ch + 1, DWC2_CHANNEL_COUNT_MAX); +} + TU_ATTR_ALWAYS_INLINE static inline tusb_speed_t hprt_speed_get(dwc2_regs_t* dwc2) { tusb_speed_t speed; - switch(dwc2->hprt_bm.speed) { + const dwc2_hprt_t hprt = {.value = dwc2->hprt}; + switch(hprt.speed) { case HPRT_SPEED_HIGH: speed = TUSB_SPEED_HIGH; break; case HPRT_SPEED_FULL: speed = TUSB_SPEED_FULL; break; case HPRT_SPEED_LOW : speed = TUSB_SPEED_LOW ; break; @@ -133,7 +137,8 @@ TU_ATTR_ALWAYS_INLINE static inline tusb_speed_t hprt_speed_get(dwc2_regs_t* dwc TU_ATTR_ALWAYS_INLINE static inline bool dma_host_enabled(const dwc2_regs_t* dwc2) { (void) dwc2; // Internal DMA only - return CFG_TUH_DWC2_DMA_ENABLE && dwc2->ghwcfg2_bm.arch == GHWCFG2_ARCH_INTERNAL_DMA; + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return CFG_TUH_DWC2_DMA_ENABLE && ghwcfg2.arch == GHWCFG2_ARCH_INTERNAL_DMA; } #if CFG_TUH_MEM_DCACHE_ENABLE @@ -155,7 +160,7 @@ bool hcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { // Allocate a channel for new transfer TU_ATTR_ALWAYS_INLINE static inline uint8_t channel_alloc(dwc2_regs_t* dwc2) { - const uint8_t max_channel = DWC2_CHANNEL_COUNT(dwc2); + const uint8_t max_channel = dwc2_channel_count(dwc2); for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; if (!xfer->allocated) { @@ -168,15 +173,18 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t channel_alloc(dwc2_regs_t* dwc2) { } // Check if is periodic (interrupt/isochronous) -TU_ATTR_ALWAYS_INLINE static inline bool edpt_is_periodic(uint8_t ep_type) { - return ep_type == HCCHAR_EPTYPE_INTERRUPT || ep_type == HCCHAR_EPTYPE_ISOCHRONOUS; +TU_ATTR_ALWAYS_INLINE static inline bool channel_is_periodic(uint32_t hcchar) { + const dwc2_channel_char_t hcchar_bm = {.value = hcchar}; + return hcchar_bm.ep_type == HCCHAR_EPTYPE_INTERRUPT || hcchar_bm.ep_type == HCCHAR_EPTYPE_ISOCHRONOUS; } TU_ATTR_ALWAYS_INLINE static inline uint8_t req_queue_avail(const dwc2_regs_t* dwc2, bool is_period) { if (is_period) { - return dwc2->hptxsts_bm.req_queue_available; + const dwc2_hptxsts_t hptxsts = {.value = dwc2->hptxsts}; + return hptxsts.req_queue_available; } else { - return dwc2->hnptxsts_bm.req_queue_available; + const dwc2_hnptxsts_t hnptxsts = {.value = dwc2->hnptxsts}; + return hnptxsts.req_queue_available; } } @@ -188,7 +196,7 @@ TU_ATTR_ALWAYS_INLINE static inline void channel_dealloc(dwc2_regs_t* dwc2, uint TU_ATTR_ALWAYS_INLINE static inline bool channel_disable(const dwc2_regs_t* dwc2, dwc2_channel_t* channel) { // disable also require request queue - TU_ASSERT(req_queue_avail(dwc2, edpt_is_periodic(channel->hcchar_bm.ep_type))); + TU_ASSERT(req_queue_avail(dwc2, channel_is_periodic(channel->hcchar))); channel->hcintmsk |= HCINT_HALTED; channel->hcchar |= HCCHAR_CHDIS | HCCHAR_CHENA; // must set both CHDIS and CHENA return true; @@ -196,18 +204,18 @@ TU_ATTR_ALWAYS_INLINE static inline bool channel_disable(const dwc2_regs_t* dwc2 // attempt to send IN token to receive data TU_ATTR_ALWAYS_INLINE static inline bool channel_send_in_token(const dwc2_regs_t* dwc2, dwc2_channel_t* channel) { - TU_ASSERT(req_queue_avail(dwc2, edpt_is_periodic(channel->hcchar_bm.ep_type))); + TU_ASSERT(req_queue_avail(dwc2, channel_is_periodic(channel->hcchar))); channel->hcchar |= HCCHAR_CHENA; return true; } // Find currently enabled channel. Note: EP0 is bidirectional TU_ATTR_ALWAYS_INLINE static inline uint8_t channel_find_enabled(dwc2_regs_t* dwc2, uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) { - const uint8_t max_channel = DWC2_CHANNEL_COUNT(dwc2); + const uint8_t max_channel = dwc2_channel_count(dwc2); for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { if (_hcd_data.xfer[ch_id].allocated) { - const dwc2_channel_char_t hcchar_bm = dwc2->channel[ch_id].hcchar_bm; - if (hcchar_bm.dev_addr == dev_addr && hcchar_bm.ep_num == ep_num && (ep_num == 0 || hcchar_bm.ep_dir == ep_dir)) { + const dwc2_channel_char_t hcchar = {.value = dwc2->channel[ch_id].hcchar}; + if (hcchar.dev_addr == dev_addr && hcchar.ep_num == ep_num && (ep_num == 0 || hcchar.ep_dir == ep_dir)) { return ch_id; } } @@ -304,12 +312,13 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t cal_next_pid(uint8_t pid, uint8_t pa static void dfifo_host_init(uint8_t rhport) { const dwc2_controller_t* dwc2_controller = &_dwc2_controller[rhport]; dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; // Scatter/Gather DMA mode is not yet supported. Buffer DMA only need 1 words per channel const bool is_dma = dma_host_enabled(dwc2); uint16_t dfifo_top = dwc2_controller->ep_fifo_size/4; if (is_dma) { - dfifo_top -= dwc2->ghwcfg2_bm.num_host_ch; + dfifo_top -= ghwcfg2.num_host_ch; } // fixed allocation for now, improve later: @@ -319,7 +328,7 @@ static void dfifo_host_init(uint8_t rhport) { uint32_t ptx_largest = is_highspeed ? TUSB_EPSIZE_ISO_HS_MAX/4 : 256/4; uint16_t nptxfsiz = 2 * nptx_largest; - uint16_t rxfsiz = 2 * (ptx_largest + 2) + dwc2->ghwcfg2_bm.num_host_ch; + uint16_t rxfsiz = 2 * (ptx_largest + 2) + ghwcfg2.num_host_ch; TU_ASSERT(dfifo_top >= (nptxfsiz + rxfsiz),); uint16_t ptxfsiz = dfifo_top - (nptxfsiz + rxfsiz); @@ -372,6 +381,10 @@ 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) {} // configure fixed-allocated fifo scheme @@ -381,7 +394,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; + dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_CONIDSTSCHNG | 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; @@ -461,8 +474,8 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t* dwc2_regs_t* dwc2 = DWC2_REG(rhport); const tusb_speed_t rh_speed = hprt_speed_get(dwc2); - hcd_devtree_info_t devtree_info; - hcd_devtree_get_info(dev_addr, &devtree_info); + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); // find a free endpoint const uint8_t ep_id = edpt_alloc(); @@ -473,7 +486,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t* hcchar_bm->ep_size = tu_edpt_packet_size(desc_ep); hcchar_bm->ep_num = tu_edpt_number(desc_ep->bEndpointAddress); hcchar_bm->ep_dir = tu_edpt_dir(desc_ep->bEndpointAddress); - hcchar_bm->low_speed_dev = (devtree_info.speed == TUSB_SPEED_LOW) ? 1 : 0; + hcchar_bm->low_speed_dev = (bus_info.speed == TUSB_SPEED_LOW) ? 1 : 0; hcchar_bm->ep_type = desc_ep->bmAttributes.xfer; // ep_type matches TUSB_XFER_* hcchar_bm->err_multi_count = 0; hcchar_bm->dev_addr = dev_addr; @@ -482,21 +495,21 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t* hcchar_bm->enable = 1; dwc2_channel_split_t* hcsplt_bm = &edpt->hcsplt_bm; - hcsplt_bm->hub_port = devtree_info.hub_port; - hcsplt_bm->hub_addr = devtree_info.hub_addr; + hcsplt_bm->hub_port = bus_info.hub_port; + hcsplt_bm->hub_addr = bus_info.hub_addr; hcsplt_bm->xact_pos = 0; hcsplt_bm->split_compl = 0; - hcsplt_bm->split_en = (rh_speed == TUSB_SPEED_HIGH && devtree_info.speed != TUSB_SPEED_HIGH) ? 1 : 0; + hcsplt_bm->split_en = (rh_speed == TUSB_SPEED_HIGH && bus_info.speed != TUSB_SPEED_HIGH) ? 1 : 0; - edpt->speed = devtree_info.speed; + edpt->speed = bus_info.speed; edpt->next_pid = HCTSIZ_PID_DATA0; if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { edpt->uframe_interval = 1 << (desc_ep->bInterval - 1); - if (devtree_info.speed == TUSB_SPEED_FULL) { + if (bus_info.speed == TUSB_SPEED_FULL) { edpt->uframe_interval <<= 3; } } else if (desc_ep->bmAttributes.xfer == TUSB_XFER_INTERRUPT) { - if (devtree_info.speed == TUSB_SPEED_HIGH) { + if (bus_info.speed == TUSB_SPEED_HIGH) { edpt->uframe_interval = 1 << (desc_ep->bInterval - 1); } else { edpt->uframe_interval = desc_ep->bInterval << 3; @@ -506,13 +519,19 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t* return true; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + // clean up channel after part of transfer is done but the whole urb is not complete static void channel_xfer_out_wrapup(dwc2_regs_t* dwc2, uint8_t ch_id) { hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; - dwc2_channel_t* channel = &dwc2->channel[ch_id]; + const dwc2_channel_t* channel = &dwc2->channel[ch_id]; hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; - edpt->next_pid = channel->hctsiz_bm.pid; // save PID + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + edpt->next_pid = hctsiz.pid; // save PID /* Since hctsiz.xfersize field reflects the number of bytes transferred via the AHB, not the USB) * For IN: we can use hctsiz.xfersize as remaining bytes. @@ -520,9 +539,10 @@ static void channel_xfer_out_wrapup(dwc2_regs_t* dwc2, uint8_t ch_id) { * number of packets that have been transferred via the USB. This is always an integral number of packets if the * transfer was halted before its normal completion. */ - const uint16_t remain_packets = channel->hctsiz_bm.packet_count; - const uint16_t total_packets = cal_packet_count(edpt->buflen, channel->hcchar_bm.ep_size); - const uint16_t actual_bytes = (total_packets - remain_packets) * channel->hcchar_bm.ep_size; + const uint16_t remain_packets = hctsiz.packet_count; + const dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + const uint16_t total_packets = cal_packet_count(edpt->buflen, hcchar.ep_size); + const uint16_t actual_bytes = (total_packets - remain_packets) * hcchar.ep_size; xfer->fifo_bytes = 0; xfer->xferred_bytes += actual_bytes; @@ -535,7 +555,7 @@ static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) { hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; dwc2_channel_char_t* hcchar_bm = &edpt->hcchar_bm; dwc2_channel_t* channel = &dwc2->channel[ch_id]; - bool const is_period = edpt_is_periodic(hcchar_bm->ep_type); + bool const is_period = channel_is_periodic(edpt->hcchar); // clear previous state xfer->fifo_bytes = 0; @@ -548,13 +568,16 @@ static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) { // hctsiz: zero length packet still count as 1 const uint16_t packet_count = cal_packet_count(edpt->buflen, hcchar_bm->ep_size); - uint32_t hctsiz = (edpt->next_pid << HCTSIZ_PID_Pos) | (packet_count << HCTSIZ_PKTCNT_Pos) | edpt->buflen; - if (edpt->do_ping && edpt->speed == TUSB_SPEED_HIGH && + dwc2_channel_tsize_t hctsiz = {.value = 0}; + hctsiz.pid = edpt->next_pid; // next PID is set in transfer complete interrupt + hctsiz.packet_count = packet_count; + hctsiz.xfer_size = edpt->buflen; + if (edpt->next_do_ping && edpt->speed == TUSB_SPEED_HIGH && edpt->next_pid != HCTSIZ_PID_SETUP && hcchar_bm->ep_dir == TUSB_DIR_OUT) { - hctsiz |= HCTSIZ_DOPING; + hctsiz.do_ping = 1; } - channel->hctsiz = hctsiz; - edpt->do_ping = 0; + channel->hctsiz = hctsiz.value; + edpt->next_do_ping = 0; // pre-calculate next PID based on packet count, adjusted in transfer complete interrupt if short packet if (hcchar_bm->ep_num == 0) { @@ -585,7 +608,7 @@ static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) { hcintmsk |= HCINT_BABBLE_ERR | HCINT_DATATOGGLE_ERR | HCINT_ACK; } else { hcintmsk |= HCINT_NYET; - if (edpt->hcsplt_bm.split_en) { + if (edpt->hcsplt_bm.split_en || hctsiz.do_ping) { hcintmsk |= HCINT_ACK; } } @@ -694,18 +717,23 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { //-------------------------------------------------------------------- // HCD Event Handler //-------------------------------------------------------------------- + +// retry an IN transfer, channel must be halted static void channel_xfer_in_retry(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcint) { hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; - dwc2_channel_t* channel = &dwc2->channel[ch_id]; hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + dwc2_channel_char_t hcchar = {.value = channel->hcchar}; - if (edpt_is_periodic(channel->hcchar_bm.ep_type)){ + if (channel_is_periodic(hcchar.value)){ + const dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; // retry immediately for periodic split NYET if we haven't reach max retry - if (channel->hcsplt_bm.split_en && channel->hcsplt_bm.split_compl && (hcint & HCINT_NYET || xfer->halted_nyet)) { + if (hcsplt.split_en && hcsplt.split_compl && (hcint & HCINT_NYET || xfer->halted_nyet)) { xfer->period_split_nyet_count++; xfer->halted_nyet = 0; if (xfer->period_split_nyet_count < HCD_XFER_PERIOD_SPLIT_NYET_MAX) { - channel->hcchar_bm.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame + hcchar.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame + channel->hcchar = hcchar.value; channel_send_in_token(dwc2, channel); return; } else { @@ -714,18 +742,24 @@ static void channel_xfer_in_retry(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci } } - // for periodic, de-allocate channel, enable SOF set frame counter for later transfer - edpt->next_pid = channel->hctsiz_bm.pid; // save PID - edpt->uframe_countdown = edpt->uframe_interval; - dwc2->gintmsk |= GINTSTS_SOF; - - if (hcint & HCINT_HALTED) { + const uint32_t ucount = (hprt_speed_get(dwc2) == TUSB_SPEED_HIGH ? 1 : 8); + if (edpt->uframe_interval == ucount) { + // retry on next frame if bInterval is 1 + hcchar.odd_frame = 1 - (dwc2->hfnum & 1); + channel->hcchar = hcchar.value; + channel_send_in_token(dwc2, channel); + } else { + // otherwise, de-allocate channel, enable SOF set frame counter for later transfer + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + edpt->next_pid = hctsiz.pid; // save PID + edpt->uframe_countdown = edpt->uframe_interval - ucount; + // enable SOF interrupt if not already enabled + if (!(dwc2->gintmsk & GINTMSK_SOFM)) { + dwc2->gintsts = GINTSTS_SOF; + dwc2->gintmsk |= GINTMSK_SOFM; + } // already halted, de-allocate channel (called from DMA isr) channel_dealloc(dwc2, ch_id); - } else { - // disable channel first if not halted (called slave isr) - xfer->halted_sof_schedule = 1; - channel_disable(dwc2, channel); } } else { // for control/bulk: retry immediately @@ -756,13 +790,13 @@ static void handle_rxflvl_irq(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); // Pop control word off FIFO - const dwc2_grxstsp_t grxstsp_bm = dwc2->grxstsp_bm; - const uint8_t ch_id = grxstsp_bm.ep_ch_num; + const dwc2_grxstsp_t grxstsp = {.value= dwc2->grxstsp}; + const uint8_t ch_id = grxstsp.ep_ch_num; - switch (grxstsp_bm.packet_status) { + switch (grxstsp.packet_status) { case GRXSTS_PKTSTS_RX_DATA: { // In packet received, pop this entry --> ACK interrupt - const uint16_t byte_count = grxstsp_bm.byte_count; + const uint16_t byte_count = grxstsp.byte_count; hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX,); hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; @@ -796,25 +830,26 @@ static void handle_rxflvl_irq(uint8_t rhport) { // return true if there is still pending data and need more ISR static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) - volatile dwc2_hptxsts_t* txsts_bm = (volatile dwc2_hptxsts_t*) (is_periodic ? &dwc2->hptxsts : &dwc2->hnptxsts); + const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; - const uint8_t max_channel = DWC2_CHANNEL_COUNT(dwc2); + const uint8_t max_channel = dwc2_channel_count(dwc2); for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { dwc2_channel_t* channel = &dwc2->channel[ch_id]; + const dwc2_channel_char_t hcchar = {.value = channel->hcchar}; // skip writing to FIFO if channel is expecting halted. - if (!(channel->hcintmsk & HCINT_HALTED) && (channel->hcchar_bm.ep_dir == TUSB_DIR_OUT)) { + if (!(channel->hcintmsk & HCINT_HALTED) && (hcchar.ep_dir == TUSB_DIR_OUT)) { hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; - - const uint16_t remain_packets = channel->hctsiz_bm.packet_count; + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + const uint16_t remain_packets = hctsiz.packet_count; for (uint16_t i = 0; i < remain_packets; i++) { const uint16_t remain_bytes = edpt->buflen - xfer->fifo_bytes; - const uint16_t xact_bytes = tu_min16(remain_bytes, channel->hcchar_bm.ep_size); + const uint16_t xact_bytes = tu_min16(remain_bytes, hcchar.ep_size); // skip if there is not enough space in FIFO and RequestQueue. // Packet's last word written to FIFO will trigger a request queue - if ((xact_bytes > (txsts_bm->fifo_available << 2)) || (txsts_bm->req_queue_available == 0)) { + if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) { return true; } @@ -831,23 +866,26 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; dwc2_channel_t* channel = &dwc2->channel[ch_id]; hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; bool is_done = false; - // if (channel->hcsplt_bm.split_en) { + // if (hcsplt.split_en) { // if (edpt->hcchar_bm.ep_num == 1) { - // TU_LOG1("Frame %u, ch %u: ep %u, hcint 0x%04lX ", dwc2->hfnum_bm.num, ch_id, channel->hcchar_bm.ep_num, hcint); + // TU_LOG1("Frame %u, ch %u: ep %u, hcint 0x%04lX ", dwc2->hfnum_bm.num, ch_id, hcsplt.ep_num, hcint); // print_hcint(hcint); // } if (hcint & HCINT_XFER_COMPLETE) { if (edpt->hcchar_bm.ep_num != 0) { - edpt->next_pid = channel->hctsiz_bm.pid; // save pid (already toggled) + edpt->next_pid = hctsiz.pid; // save pid (already toggled) } - const uint16_t remain_packets = channel->hctsiz_bm.packet_count; - if (channel->hcsplt_bm.split_en && remain_packets && xfer->fifo_bytes == edpt->hcchar_bm.ep_size) { + const uint16_t remain_packets = hctsiz.packet_count; + if (hcsplt.split_en && remain_packets && xfer->fifo_bytes == edpt->hcchar_bm.ep_size) { // Split can only complete 1 transaction (up to 1 packet) at a time, schedule more - channel->hcsplt_bm.split_compl = 0; + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; } else { xfer->result = XFER_RESULT_SUCCESS; } @@ -866,43 +904,44 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h channel_disable(dwc2, channel); } else if (hcint & HCINT_NYET) { // restart complete split - channel->hcsplt_bm.split_compl = 1; + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; xfer->halted_nyet = 1; channel_disable(dwc2, channel); } else if (hcint & HCINT_NAK) { - // NAK received, re-enable channel if request queue is available - if (channel->hcsplt_bm.split_en) { - channel->hcsplt_bm.split_compl = 0; // restart with start-split + // NAK received, disable channel to flush all posted request and try again + if (hcsplt.split_en) { + hcsplt.split_compl = 0; // restart with start-split + channel->hcsplt = hcsplt.value; } channel_disable(dwc2, channel); } else if (hcint & HCINT_ACK) { xfer->err_count = 0; - if (channel->hcsplt_bm.split_en) { - if (!channel->hcsplt_bm.split_compl) { + if (hcsplt.split_en) { + if (!hcsplt.split_compl) { // start split is ACK --> do complete split channel->hcintmsk |= HCINT_NYET; - channel->hcsplt_bm.split_compl = 1; + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; channel_send_in_token(dwc2, channel); } else { // do nothing for complete split with DATA, this will trigger XferComplete and handled there } } else { // ACK with data - const uint16_t remain_packets = channel->hctsiz_bm.packet_count; + const uint16_t remain_packets = hctsiz.packet_count; if (remain_packets) { // still more packet to receive, also reset to start split - channel->hcsplt_bm.split_compl = 0; + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; channel_send_in_token(dwc2, channel); } } } else if (hcint & HCINT_HALTED) { channel->hcintmsk &= ~HCINT_HALTED; - if (xfer->halted_sof_schedule) { - // de-allocate channel but does not complete xfer, we schedule it in the SOF interrupt - channel_dealloc(dwc2, ch_id); - } else if (xfer->result != XFER_RESULT_INVALID) { + if (xfer->result != XFER_RESULT_INVALID) { is_done = true; } else if (xfer->err_count == HCD_XFER_ERROR_MAX) { xfer->result = XFER_RESULT_FAILED; @@ -922,23 +961,29 @@ static bool handle_channel_out_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; dwc2_channel_t* channel = &dwc2->channel[ch_id]; hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; bool is_done = false; if (hcint & HCINT_XFER_COMPLETE) { is_done = true; xfer->result = XFER_RESULT_SUCCESS; channel->hcintmsk &= ~HCINT_ACK; + if (hcint & HCINT_NYET) { + // complete transfer with NYET, do ping next time + edpt->next_do_ping = 1; + } } else if (hcint & HCINT_STALL) { xfer->result = XFER_RESULT_STALLED; channel_disable(dwc2, channel); } else if (hcint & HCINT_NYET) { xfer->err_count = 0; - if (channel->hcsplt_bm.split_en) { + if (hcsplt.split_en) { // retry complete split - channel->hcsplt_bm.split_compl = 1; + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; channel->hcchar |= HCCHAR_CHENA; } else { - edpt->do_ping = 1; + edpt->next_do_ping = 1; channel_xfer_out_wrapup(dwc2, ch_id); channel_disable(dwc2, channel); } @@ -951,7 +996,7 @@ static bool handle_channel_out_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t channel->hcintmsk |= HCINT_ACK; } else { // NAK disable channel to flush all posted request and try again - edpt->do_ping = 1; + edpt->next_do_ping = 1; xfer->err_count = 0; } } else if (hcint & HCINT_HALTED) { @@ -968,9 +1013,17 @@ static bool handle_channel_out_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t } else if (hcint & HCINT_ACK) { xfer->err_count = 0; channel->hcintmsk &= ~HCINT_ACK; - if (channel->hcsplt_bm.split_en && !channel->hcsplt_bm.split_compl) { - // start split is ACK --> do complete split - channel->hcsplt_bm.split_compl = 1; + if (hcsplt.split_en) { + if (!hcsplt.split_compl) { + // ACK for start split --> do complete split + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + channel->hcchar |= HCCHAR_CHENA; + } + } else { + // ACK interrupt is only enabled for Split and PING + // ACK for PING, which mean device is ready to receive data + channel->hctsiz &= ~HCTSIZ_DOPING; // HC already cleared PING bit, but we clear anyway channel->hcchar |= HCCHAR_CHENA; } } @@ -989,6 +1042,9 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; dwc2_channel_t* channel = &dwc2->channel[ch_id]; hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; bool is_done = false; @@ -996,8 +1052,8 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci if (hcint & HCINT_HALTED) { if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) { - const uint16_t remain_bytes = (uint16_t) channel->hctsiz_bm.xfer_size; - const uint16_t remain_packets = channel->hctsiz_bm.packet_count; + const uint16_t remain_bytes = (uint16_t) hctsiz.xfer_size; + const uint16_t remain_packets = hctsiz.packet_count; const uint16_t actual_len = edpt->buflen - remain_bytes; xfer->xferred_bytes += actual_len; @@ -1007,13 +1063,14 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci xfer->result = XFER_RESULT_STALLED; } else if (hcint & HCINT_BABBLE_ERR) { xfer->result = XFER_RESULT_FAILED; - } else if (channel->hcsplt_bm.split_en && remain_packets && actual_len == edpt->hcchar_bm.ep_size) { + } else if (hcsplt.split_en && remain_packets && actual_len == hcchar.ep_size) { // Split can only complete 1 transaction (up to 1 packet) at a time, schedule more is_done = false; edpt->buffer += actual_len; edpt->buflen -= actual_len; - channel->hcsplt_bm.split_compl = 0; + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; channel_xfer_in_retry(dwc2, ch_id, hcint); } else { xfer->result = XFER_RESULT_SUCCESS; @@ -1028,33 +1085,38 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci xfer->result = XFER_RESULT_FAILED; } else { channel->hcintmsk |= HCINT_ACK | HCINT_NAK | HCINT_DATATOGGLE_ERR; - channel->hcsplt_bm.split_compl = 0; + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; channel_xfer_in_retry(dwc2, ch_id, hcint); } } else if (hcint & HCINT_NYET) { // Must handle nyet before nak or ack. Could get a nyet at the same time as either of those on a BULK/CONTROL // OUT that started with a PING. The nyet takes precedence. - if (channel->hcsplt_bm.split_en) { + if (hcsplt.split_en) { // split not yet mean hub has no data, retry complete split - channel->hcsplt_bm.split_compl = 1; + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; channel_xfer_in_retry(dwc2, ch_id, hcint); } } else if (hcint & HCINT_ACK) { xfer->err_count = 0; channel->hcintmsk &= ~HCINT_ACK; - if (channel->hcsplt_bm.split_en) { + if (hcsplt.split_en) { // start split is ACK --> do complete split // TODO: for ISO must use xact_pos to plan complete split based on microframe (up to 187.5 bytes/uframe) - channel->hcsplt_bm.split_compl = 1; - if (edpt_is_periodic(channel->hcchar_bm.ep_type)) { - channel->hcchar_bm.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + if (channel_is_periodic(channel->hcchar)) { + hcchar.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame + channel->hcchar = hcchar.value; } channel_send_in_token(dwc2, channel); } } else if (hcint & (HCINT_NAK | HCINT_DATATOGGLE_ERR)) { xfer->err_count = 0; channel->hcintmsk &= ~(HCINT_NAK | HCINT_DATATOGGLE_ERR); - channel->hcsplt_bm.split_compl = 0; // restart with start-split + hcsplt.split_compl = 0; // restart with start-split + channel->hcsplt = hcsplt.value; channel_xfer_in_retry(dwc2, ch_id, hcint); } else if (hcint & HCINT_FARME_OVERRUN) { // retry start-split in next binterval @@ -1069,6 +1131,8 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; dwc2_channel_t* channel = &dwc2->channel[ch_id]; hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + const dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; bool is_done = false; @@ -1104,16 +1168,18 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc } } } else if (hcint & HCINT_NYET) { - if (channel->hcsplt_bm.split_en && channel->hcsplt_bm.split_compl) { + if (hcsplt.split_en && hcsplt.split_compl) { // split not yet mean hub has no data, retry complete split - channel->hcsplt_bm.split_compl = 1; + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; channel->hcchar |= HCCHAR_CHENA; } } else if (hcint & HCINT_ACK) { xfer->err_count = 0; - if (channel->hcsplt_bm.split_en && !channel->hcsplt_bm.split_compl) { + if (hcsplt.split_en && !hcsplt.split_compl) { // start split is ACK --> do complete split - channel->hcsplt_bm.split_compl = 1; + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; channel->hcchar |= HCCHAR_CHENA; } } @@ -1129,14 +1195,14 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc static void handle_channel_irq(uint8_t rhport, bool in_isr) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); const bool is_dma = dma_host_enabled(dwc2); - const uint8_t max_channel = DWC2_CHANNEL_COUNT(dwc2); + const uint8_t max_channel = dwc2_channel_count(dwc2); for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { if (tu_bit_test(dwc2->haint, ch_id)) { dwc2_channel_t* channel = &dwc2->channel[ch_id]; hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX,); - dwc2_channel_char_t hcchar_bm = channel->hcchar_bm; + dwc2_channel_char_t hcchar = {.value = channel->hcchar}; const uint32_t hcint = channel->hcint; channel->hcint = hcint; // clear interrupt @@ -1144,7 +1210,7 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) { bool is_done = false; if (is_dma) { #if CFG_TUH_DWC2_DMA_ENABLE - if (hcchar_bm.ep_dir == TUSB_DIR_OUT) { + if (hcchar.ep_dir == TUSB_DIR_OUT) { is_done = handle_channel_out_dma(dwc2, ch_id, hcint); } else { is_done = handle_channel_in_dma(dwc2, ch_id, hcint); @@ -1156,7 +1222,7 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) { #endif } else { #if CFG_TUH_DWC2_SLAVE_ENABLE - if (hcchar_bm.ep_dir == TUSB_DIR_OUT) { + if (hcchar.ep_dir == TUSB_DIR_OUT) { is_done = handle_channel_out_slave(dwc2, ch_id, hcint); } else { is_done = handle_channel_in_slave(dwc2, ch_id, hcint); @@ -1165,8 +1231,8 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) { } if (is_done) { - const uint8_t ep_addr = tu_edpt_addr(hcchar_bm.ep_num, hcchar_bm.ep_dir); - hcd_event_xfer_complete(hcchar_bm.dev_addr, ep_addr, xfer->xferred_bytes, xfer->result, in_isr); + const uint8_t ep_addr = tu_edpt_addr(hcchar.ep_num, hcchar.ep_dir); + hcd_event_xfer_complete(hcchar.dev_addr, ep_addr, xfer->xferred_bytes, (xfer_result_t)xfer->result, in_isr); channel_dealloc(dwc2, ch_id); } } @@ -1177,6 +1243,7 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) { static bool handle_sof_irq(uint8_t rhport, bool in_isr) { (void) in_isr; dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dwc2->gintsts = GINTSTS_SOF; // Clear the SOF interrupt flag bool more_isr = false; @@ -1185,7 +1252,7 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) { for(uint8_t ep_id = 0; ep_id < CFG_TUH_DWC2_ENDPOINT_MAX; ep_id++) { hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; - if (edpt->hcchar_bm.enable && edpt_is_periodic(edpt->hcchar_bm.ep_type) && edpt->uframe_countdown > 0) { + if (edpt->hcchar_bm.enable && channel_is_periodic(edpt->hcchar) && edpt->uframe_countdown > 0) { edpt->uframe_countdown -= tu_min32(ucount, edpt->uframe_countdown); if (edpt->uframe_countdown == 0) { if (!edpt_xfer_kickoff(dwc2, ep_id)) { @@ -1204,10 +1271,10 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) { static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) { uint32_t hcfg = dwc2->hcfg & ~HCFG_FSLS_PHYCLK_SEL; - const dwc2_gusbcfg_t gusbcfg_bm = dwc2->gusbcfg_bm; + const dwc2_gusbcfg_t gusbcfg = {.value = dwc2->gusbcfg}; uint32_t phy_clock; - if (gusbcfg_bm.phy_sel) { + if (gusbcfg.phy_sel) { phy_clock = 48; // dedicated FS is 48Mhz if (speed == TUSB_SPEED_LOW) { hcfg |= HCFG_FSLS_PHYCLK_SEL_6MHZ; @@ -1215,11 +1282,11 @@ static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) { hcfg |= HCFG_FSLS_PHYCLK_SEL_48MHZ; } } else { - if (gusbcfg_bm.ulpi_utmi_sel) { + if (gusbcfg.ulpi_utmi_sel) { phy_clock = 60; // ULPI 8-bit is 60Mhz } else { // UTMI+ 16-bit is 30Mhz, 8-bit is 60Mhz - phy_clock = gusbcfg_bm.phy_if16 ? 30 : 60; + phy_clock = gusbcfg.phy_if16 ? 30 : 60; // Enable UTMI+ low power mode 48Mhz external clock if not highspeed if (speed == TUSB_SPEED_HIGH) { @@ -1236,9 +1303,9 @@ static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) { uint32_t hfir = dwc2->hfir & ~HFIR_FRIVL_Msk; if (speed == TUSB_SPEED_HIGH) { - hfir |= 125*phy_clock; + hfir |= 125*phy_clock - 1; // The "- 1" is the correct value. The Synopsys databook was corrected in 3.30a } else { - hfir |= 1000*phy_clock; + hfir |= 1000*phy_clock - 1; } dwc2->hfir = hfir; @@ -1251,21 +1318,19 @@ static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) { */ static void handle_hprt_irq(uint8_t rhport, bool in_isr) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); - uint32_t hprt = dwc2->hprt & ~HPRT_W1_MASK; - const dwc2_hprt_t hprt_bm = dwc2->hprt_bm; + const dwc2_hprt_t hprt_bm = {.value = dwc2->hprt}; + uint32_t hprt = hprt_bm.value & ~HPRT_W1_MASK; - if (dwc2->hprt & HPRT_CONN_DETECT) { + if (hprt_bm.conn_detected) { // Port Connect Detect hprt |= HPRT_CONN_DETECT; if (hprt_bm.conn_status) { hcd_event_device_attach(rhport, in_isr); - } else { - hcd_event_device_remove(rhport, in_isr); } } - if (dwc2->hprt & HPRT_ENABLE_CHANGE) { + if (hprt_bm.enable_change) { // Port enable change hprt |= HPRT_ENABLE_CHANGE; @@ -1324,6 +1389,15 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) { handle_channel_irq(rhport, in_isr); } + if (gintsts & GINTSTS_DISCINT) { + // Device disconnected + dwc2->gintsts = GINTSTS_DISCINT; + + if (!(dwc2->hprt & HPRT_CONN_STATUS)) { + hcd_event_device_remove(rhport, in_isr); + } + } + #if CFG_TUH_DWC2_SLAVE_ENABLE // RxFIFO non-empty interrupt handling if (gintsts & GINTSTS_RXFLVL) { diff --git a/src/portable/template/hcd_template.c b/src/portable/template/hcd_template.c index d8bca196f..d2f304407 100644 --- a/src/portable/template/hcd_template.c +++ b/src/portable/template/hcd_template.c @@ -29,6 +29,7 @@ #if CFG_TUH_ENABLED && CFG_TUSB_MCU == OPT_MCU_NONE #include "host/hcd.h" +#include "host/usbh.h" //--------------------------------------------------------------------+ // Controller API @@ -36,24 +37,19 @@ // optional hcd configuration, called by tuh_configure() bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { - (void) rhport; - (void) cfg_id; - (void) cfg_param; - + (void) rhport; (void) cfg_id; (void) cfg_param; return false; } // Initialize controller to host mode bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; + (void) rhport; (void) rh_init; return false; } // Interrupt Handler void hcd_int_handler(uint8_t rhport, bool in_isr) { - (void) rhport; - (void) in_isr; + (void) rhport; (void) in_isr; } // Enable USB interrupt @@ -69,7 +65,6 @@ void hcd_int_disable(uint8_t rhport) { // Get frame number (1ms) uint32_t hcd_frame_number(uint8_t rhport) { (void) rhport; - return 0; } @@ -80,7 +75,6 @@ uint32_t hcd_frame_number(uint8_t rhport) { // Get the current connect status of roothub port bool hcd_port_connect_status(uint8_t rhport) { (void) rhport; - return false; } @@ -98,14 +92,12 @@ void hcd_port_reset_end(uint8_t rhport) { // Get port link speed tusb_speed_t hcd_port_speed_get(uint8_t rhport) { (void) rhport; - return TUSB_SPEED_FULL; } // HCD closes all opened endpoints belong to this device void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { - (void) rhport; - (void) dev_addr; + (void) rhport; (void) dev_addr; } //--------------------------------------------------------------------+ @@ -114,49 +106,42 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { // Open an endpoint bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) { - (void) rhport; - (void) dev_addr; - (void) ep_desc; + (void) rhport; (void) dev_addr; (void) ep_desc; + + // NOTE: ep_desc is allocated on the stack when called from usbh_edpt_control_open() + // You need to copy the data into a local variable who maintains the state of the endpoint and transfer. + // Check _hcd_data in hcd_dwc2.c for example. return false; } +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + // Submit a transfer, when complete hcd_event_xfer_complete() must be invoked bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { - (void) rhport; - (void) dev_addr; - (void) ep_addr; - (void) buffer; - (void) buflen; - + (void) rhport; (void) dev_addr; (void) ep_addr; (void) buffer; (void) buflen; return 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; - (void) dev_addr; - (void) ep_addr; - + (void) rhport; (void) dev_addr; (void) ep_addr; return false; } // Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { - (void) rhport; - (void) dev_addr; - (void) setup_packet; - + (void) rhport; (void) dev_addr; (void) setup_packet; return false; } // clear stall, data toggle is also reset to DATA0 bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void) rhport; - (void) dev_addr; - (void) ep_addr; - + (void) rhport; (void) dev_addr; (void) ep_addr; return false; } diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c index f8bf3c889..4a208b9df 100644 --- a/src/portable/wch/dcd_ch32_usbhs.c +++ b/src/portable/wch/dcd_ch32_usbhs.c @@ -27,7 +27,7 @@ #include "tusb_option.h" -#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && CFG_TUD_WCH_USBIP_USBHS +#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && defined(CFG_TUD_WCH_USBIP_USBHS) && CFG_TUD_WCH_USBIP_USBHS #include "ch32_usbhs_reg.h" #include "device/dcd.h" diff --git a/src/portable/wch/hcd_ch32_usbfs.c b/src/portable/wch/hcd_ch32_usbfs.c new file mode 100644 index 000000000..200136906 --- /dev/null +++ b/src/portable/wch/hcd_ch32_usbfs.c @@ -0,0 +1,669 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Mitsumine Suzu (verylowfreq) + * + * 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_WCH_USBFS) && defined(CFG_TUH_WCH_USBIP_USBFS) && CFG_TUH_WCH_USBIP_USBFS + +#include <stdlib.h> + +#include "host/hcd.h" +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "bsp/board_api.h" + +#include "ch32v20x.h" +#include "ch32v20x_usb.h" + +#define USBFS_RX_BUF_LEN 64 +#define USBFS_TX_BUF_LEN 64 +TU_ATTR_ALIGNED(4) static uint8_t USBFS_RX_Buf[USBFS_RX_BUF_LEN]; +TU_ATTR_ALIGNED(4) static uint8_t USBFS_TX_Buf[USBFS_TX_BUF_LEN]; + +#define USB_XFER_TIMEOUT_MILLIS 100 +// #define USB_INTERRUPT_XFER_TIMEOUT_MILLIS 1 + +#define PANIC(...) \ + do { \ + printf("%s() L%d: ", __func__, __LINE__); \ + printf("\r\n[PANIC] " __VA_ARGS__); \ + while (true) {} \ + } while (false) + +#define LOG_CH32_USBFSH(...) TU_LOG3(__VA_ARGS__) + +// Busywait for delay microseconds/nanoseconds +TU_ATTR_ALWAYS_INLINE static inline void loopdelay(uint32_t count) { + volatile uint32_t c = count / 3; + if (c == 0) { return; } + // while (c-- != 0); + asm volatile( + "1: \n" // loop label + " addi %0, %0, -1 \n" // c-- + " bne %0, zero, 1b \n" // if (c != 0) goto loop + : "+r"(c) // c is input/output operand + ); +} + +// Endpoint status +typedef struct usb_edpt { + // Is this a valid struct + bool configured; + + uint8_t dev_addr; + uint8_t ep_addr; + uint8_t max_packet_size; + + uint8_t xfer_type; + + // Data toggle (0 or not 0) for DATA0/1 + uint8_t data_toggle; + + bool is_nak_pending; + uint16_t buflen; + uint8_t* buf; +} usb_edpt_t; + +static usb_edpt_t usb_edpt_list[CFG_TUH_DEVICE_MAX * 6] = {}; + +typedef struct usb_current_xfer_st { + bool is_busy; + uint8_t dev_addr; + uint8_t ep_addr; + // Xfer started time in millis for timeout + uint32_t start_ms; + uint8_t *buffer; + uint16_t bufferlen; + uint16_t xferred_len; + bool nak_pending; +} usb_current_xfer_t; + +static volatile usb_current_xfer_t usb_current_xfer_info = {}; + +static usb_edpt_t *get_edpt_record(uint8_t dev_addr, uint8_t ep_addr) { + for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { + usb_edpt_t *cur = &usb_edpt_list[i]; + if (cur->configured && cur->dev_addr == dev_addr && cur->ep_addr == ep_addr) { + return cur; + } + } + return NULL; +} + +static usb_edpt_t *get_empty_record_slot(void) { + for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { + if (!usb_edpt_list[i].configured) { + return &usb_edpt_list[i]; + } + } + return NULL; +} + +static usb_edpt_t *add_edpt_record(uint8_t dev_addr, uint8_t ep_addr, uint16_t max_packet_size, uint8_t xfer_type) { + usb_edpt_t *slot = get_empty_record_slot(); + TU_ASSERT(slot != NULL, NULL); + + slot->dev_addr = dev_addr; + slot->ep_addr = ep_addr; + slot->max_packet_size = max_packet_size; + slot->xfer_type = xfer_type; + slot->data_toggle = 0; + slot->is_nak_pending = false; + slot->buflen = 0; + slot->buf = NULL; + + slot->configured = true; + + return slot; +} + +static usb_edpt_t *get_or_add_edpt_record(uint8_t dev_addr, uint8_t ep_addr, uint16_t max_packet_size, uint8_t xfer_type) { + usb_edpt_t *ret = get_edpt_record(dev_addr, ep_addr); + if (ret != NULL) { + return ret; + } else { + return add_edpt_record(dev_addr, ep_addr, max_packet_size, xfer_type); + } +} + +static void remove_edpt_record_for_device(uint8_t dev_addr) { + for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { + if (usb_edpt_list[i].configured && usb_edpt_list[i].dev_addr == dev_addr) { + usb_edpt_list[i].configured = false; + } + } +} + +// static void dump_edpt_record_list() { +// for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { +// usb_edpt_t* cur = &usb_edpt_list[i]; +// if (cur->configured) { +// printf("[%2d] Device 0x%02x Endpoint 0x%02x\r\n", i, cur->dev_addr, cur->ep_addr); +// } else { +// printf("[%2d] not configured\r\n", i); +// } +// } +// } + +static bool interrupt_enabled = false; + +/** Enable or disable USBFS Host function */ +static void hardware_init_host(bool enabled) { + // Reset USBOTG module + USBOTG_H_FS->BASE_CTRL = USBFS_UC_RESET_SIE | USBFS_UC_CLR_ALL; + + tusb_time_delay_ms_api(1); + USBOTG_H_FS->BASE_CTRL = 0; + + if (!enabled) { + // Disable all feature + USBOTG_H_FS->BASE_CTRL = 0; + } else { + // Enable USB Host features + // NVIC_DisableIRQ(USBFS_IRQn); + hcd_int_disable(0); + USBOTG_H_FS->BASE_CTRL = USBFS_UC_HOST_MODE | USBFS_UC_INT_BUSY | USBFS_UC_DMA_EN; + USBOTG_H_FS->HOST_EP_MOD = USBFS_UH_EP_TX_EN | USBFS_UH_EP_RX_EN; + USBOTG_H_FS->HOST_RX_DMA = (uint32_t) USBFS_RX_Buf; + USBOTG_H_FS->HOST_TX_DMA = (uint32_t) USBFS_TX_Buf; + // USBOTG_H_FS->INT_EN = USBFS_UIE_TRANSFER | USBFS_UIE_DETECT; + USBOTG_H_FS->INT_EN = USBFS_UIE_DETECT; + } +} + +static bool hardware_start_xfer(uint8_t pid, uint8_t ep_addr, uint8_t data_toggle) { + LOG_CH32_USBFSH("hardware_start_xfer(pid=%s(0x%02x), ep_addr=0x%02x, toggle=%d)\r\n", + pid == USB_PID_IN ? "IN" : pid == USB_PID_OUT ? "OUT" + : pid == USB_PID_SETUP ? "SETUP" + : "(other)", + pid, ep_addr, data_toggle); + + //WORKAROUND: For LowSpeed device, insert small delay + bool is_lowspeed_device = tuh_speed_get(usb_current_xfer_info.dev_addr) == TUSB_SPEED_LOW; + if (is_lowspeed_device) { + //NOTE: worked -> SystemCoreClock / 1000000 * 50, 25 + // NOT worked -> 20 and less (at 144MHz internal clock) + loopdelay(SystemCoreClock / 1000000 * 40); + } + + uint8_t pid_edpt = (pid << 4) | (tu_edpt_number(ep_addr) & 0x0f); + USBOTG_H_FS->HOST_TX_CTRL = (data_toggle != 0) ? USBFS_UH_T_TOG : 0; + USBOTG_H_FS->HOST_RX_CTRL = (data_toggle != 0) ? USBFS_UH_R_TOG : 0; + USBOTG_H_FS->HOST_EP_PID = pid_edpt; + USBOTG_H_FS->INT_EN |= USBFS_UIE_TRANSFER; + USBOTG_H_FS->INT_FG = USBFS_UIF_TRANSFER; + return true; +} + + +/** Set device address to communicate */ +static void hardware_update_device_address(uint8_t dev_addr) { + // Keep the bit of GP_BIT. Other 7bits are actual device address. + USBOTG_H_FS->DEV_ADDR = (USBOTG_H_FS->DEV_ADDR & USBFS_UDA_GP_BIT) | (dev_addr & USBFS_USB_ADDR_MASK); +} + +/** Set port speed */ +static void hardware_update_port_speed(tusb_speed_t speed) { + LOG_CH32_USBFSH("hardware_update_port_speed(%s)\r\n", speed == TUSB_SPEED_FULL ? "Full" : speed == TUSB_SPEED_LOW ? "Low" + : "(invalid)"); + switch (speed) { + case TUSB_SPEED_LOW: + USBOTG_H_FS->BASE_CTRL |= USBFS_UC_LOW_SPEED; + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_LOW_SPEED; + USBOTG_H_FS->HOST_SETUP |= USBFS_UH_PRE_PID_EN; + return; + case TUSB_SPEED_FULL: + USBOTG_H_FS->BASE_CTRL &= ~USBFS_UC_LOW_SPEED; + USBOTG_H_FS->HOST_CTRL &= ~USBFS_UH_LOW_SPEED; + USBOTG_H_FS->HOST_SETUP &= ~USBFS_UH_PRE_PID_EN; + return; + default: + PANIC("hardware_update_port_speed(%d)\r\n", speed); + } +} + +static void hardware_set_port_address_speed(uint8_t dev_addr) { + hardware_update_device_address(dev_addr); + tusb_speed_t rhport_speed = hcd_port_speed_get(0); + tusb_speed_t dev_speed = tuh_speed_get(dev_addr); + hardware_update_port_speed(dev_speed); + if (rhport_speed == TUSB_SPEED_FULL && dev_speed == TUSB_SPEED_LOW) { + USBOTG_H_FS->HOST_CTRL &= ~USBFS_UH_LOW_SPEED; + } +} + +static bool hardware_device_attached(void) { + return USBOTG_H_FS->MIS_ST & USBFS_UMS_DEV_ATTACH; +} + +//--------------------------------------------------------------------+ +// HCD API +//--------------------------------------------------------------------+ +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void) rhport; + (void) rh_init; + hardware_init_host(true); + + return true; +} + +bool hcd_deinit(uint8_t rhport) { + (void) rhport; + hardware_init_host(false); + + return true; +} + +static bool int_state_for_portreset = false; + +void hcd_port_reset(uint8_t rhport) { + (void) rhport; + LOG_CH32_USBFSH("hcd_port_reset()\r\n"); + int_state_for_portreset = interrupt_enabled; + // NVIC_DisableIRQ(USBFS_IRQn); + hcd_int_disable(rhport); + hardware_update_device_address(0x00); + + // USBOTG_H_FS->HOST_SETUP = 0x00; + + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_BUS_RESET; + + return; +} + +void hcd_port_reset_end(uint8_t rhport) { + (void) rhport; + LOG_CH32_USBFSH("hcd_port_reset_end()\r\n"); + + USBOTG_H_FS->HOST_CTRL &= ~USBFS_UH_BUS_RESET; + tusb_time_delay_ms_api(2); + + if ((USBOTG_H_FS->HOST_CTRL & USBFS_UH_PORT_EN) == 0) { + if (hcd_port_speed_get(0) == TUSB_SPEED_LOW) { + hardware_update_port_speed(TUSB_SPEED_LOW); + } + } + + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_PORT_EN; + USBOTG_H_FS->HOST_SETUP |= USBFS_UH_SOF_EN; + + // Suppress the attached event + USBOTG_H_FS->INT_FG |= USBFS_UIF_DETECT; + + if (int_state_for_portreset) { + hcd_int_enable(rhport); + } +} + +bool hcd_port_connect_status(uint8_t rhport) { + (void) rhport; + + return hardware_device_attached(); +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void) rhport; + if (USBOTG_H_FS->MIS_ST & USBFS_UMS_DM_LEVEL) { + return TUSB_SPEED_LOW; + } else { + return TUSB_SPEED_FULL; + } +} + +// Close all opened endpoint belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + (void) rhport; + LOG_CH32_USBFSH("hcd_device_close(%d, 0x%02x)\r\n", rhport, dev_addr); + remove_edpt_record_for_device(dev_addr); +} + +uint32_t hcd_frame_number(uint8_t rhport) { + (void) rhport; + + return tusb_time_millis_api(); +} + +void hcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USBFS_IRQn); + interrupt_enabled = true; +} + +void hcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USBFS_IRQn); + interrupt_enabled = false; +} + + +static void xfer_retry(void* _params) { + LOG_CH32_USBFSH("xfer_retry()\r\n"); + usb_edpt_t* edpt_info = (usb_edpt_t*)_params; + if (usb_current_xfer_info.nak_pending) { + usb_current_xfer_info.nak_pending = false; + edpt_info->is_nak_pending = false; + + uint8_t dev_addr = edpt_info->dev_addr; + uint8_t ep_addr = edpt_info->ep_addr; + uint16_t buflen = edpt_info->buflen; + uint8_t* buf = edpt_info->buf; + + // Check connectivity + usb_edpt_t* edpt_info_current = get_edpt_record(dev_addr, ep_addr); + if (edpt_info_current) { + hcd_edpt_xfer(0, dev_addr, ep_addr, buf, buflen); + } + } +} + + +void hcd_int_handler(uint8_t rhport, bool in_isr) { + (void) rhport; + (void) in_isr; + + if (USBOTG_H_FS->INT_FG & USBFS_UIF_DETECT) { + // Clear the flag + USBOTG_H_FS->INT_FG = USBFS_UIF_DETECT; + // Read the detection state + bool attached = hardware_device_attached(); + LOG_CH32_USBFSH("hcd_int_handler() attached = %d\r\n", attached ? 1 : 0); + if (attached) { + hcd_event_device_attach(rhport, true); + } else { + hcd_event_device_remove(rhport, true); + } + return; + } + + if (USBOTG_H_FS->INT_FG & USBFS_UIF_TRANSFER) { + // Disable transfer interrupt + USBOTG_H_FS->INT_EN &= ~USBFS_UIE_TRANSFER; + // Clear the flag + // USBOTG_H_FS->INT_FG = USBFS_UIF_TRANSFER; + // Copy PID and Endpoint + uint8_t pid_edpt = USBOTG_H_FS->HOST_EP_PID; + uint8_t status = USBOTG_H_FS->INT_ST; + uint8_t dev_addr = USBOTG_H_FS->DEV_ADDR & USBFS_USB_ADDR_MASK; + // Clear register to stop transfer + // USBOTG_H_FS->HOST_EP_PID = 0x00; + + LOG_CH32_USBFSH("hcd_int_handler() pid_edpt=0x%02x\r\n", pid_edpt); + + uint8_t request_pid = pid_edpt >> 4; + uint8_t response_pid = status & USBFS_UIS_H_RES_MASK; + uint8_t ep_addr = pid_edpt & 0x0f; + if (request_pid == USB_PID_IN) { + ep_addr |= 0x80; + } + + usb_edpt_t *edpt_info = get_edpt_record(dev_addr, ep_addr); + if (edpt_info == NULL) { + PANIC("\r\nget_edpt_record(0x%02x, 0x%02x) returned NULL in USBHD_IRQHandler\r\n", dev_addr, ep_addr); + } + + if (status & USBFS_UIS_TOG_OK) { + edpt_info->data_toggle ^= 0x01; + + switch (request_pid) { + case USB_PID_SETUP: + case USB_PID_OUT: { + uint16_t tx_len = USBOTG_H_FS->HOST_TX_LEN; + usb_current_xfer_info.bufferlen -= tx_len; + usb_current_xfer_info.xferred_len += tx_len; + if (usb_current_xfer_info.bufferlen == 0) { + LOG_CH32_USBFSH("USB_PID_%s completed %d bytes\r\n", request_pid == USB_PID_OUT ? "OUT" : "SETUP", usb_current_xfer_info.xferred_len); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, usb_current_xfer_info.xferred_len, XFER_RESULT_SUCCESS, in_isr); + return; + } else { + LOG_CH32_USBFSH("USB_PID_OUT continue...\r\n"); + usb_current_xfer_info.buffer += tx_len; + uint16_t copylen = TU_MIN(edpt_info->max_packet_size, usb_current_xfer_info.bufferlen); + memcpy(USBFS_TX_Buf, usb_current_xfer_info.buffer, copylen); + hardware_start_xfer(USB_PID_OUT, ep_addr, edpt_info->data_toggle); + return; + } + } + case USB_PID_IN: { + uint16_t received_len = USBOTG_H_FS->RX_LEN; + usb_current_xfer_info.xferred_len += received_len; + uint16_t xferred_len = usb_current_xfer_info.xferred_len; + LOG_CH32_USBFSH("Read %d bytes\r\n", received_len); + // if (received_len > 0 && (usb_current_xfer_info.buffer == NULL || usb_current_xfer_info.bufferlen == 0)) { + // PANIC("Data received but buffer not set\r\n"); + // } + memcpy(usb_current_xfer_info.buffer, USBFS_RX_Buf, received_len); + usb_current_xfer_info.buffer += received_len; + if ((received_len < edpt_info->max_packet_size) || (xferred_len == usb_current_xfer_info.bufferlen)) { + // USB device sent all data. + LOG_CH32_USBFSH("USB_PID_IN completed\r\n"); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, XFER_RESULT_SUCCESS, in_isr); + return; + } else { + // USB device may send more data. + LOG_CH32_USBFSH("Read more data\r\n"); + hardware_start_xfer(USB_PID_IN, ep_addr, edpt_info->data_toggle); + return; + } + } + default: { + LOG_CH32_USBFSH("hcd_int_handler() L%d: unexpected response PID: 0x%02x\r\n", __LINE__, response_pid); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr); + return; + } + } + } else { + if (response_pid == USB_PID_STALL) { + LOG_CH32_USBFSH("STALL response\r\n"); + hcd_edpt_clear_stall(0, dev_addr, ep_addr); + edpt_info->data_toggle = 0; + hardware_start_xfer(request_pid, ep_addr, 0); + return; + } else if (response_pid == USB_PID_NAK) { + LOG_CH32_USBFSH("NAK reposense\r\n"); + uint32_t elapsed_time = tusb_time_millis_api() - usb_current_xfer_info.start_ms; + (void)elapsed_time; + if (edpt_info->xfer_type == TUSB_XFER_INTERRUPT) { + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_SUCCESS, in_isr); + + } else { + usb_current_xfer_info.is_busy = false; + usb_current_xfer_info.nak_pending = true; + + + edpt_info->is_nak_pending = true; + edpt_info->buflen = usb_current_xfer_info.bufferlen; + edpt_info->buf = usb_current_xfer_info.buffer; + + hcd_event_t event = { + .rhport = rhport, + .dev_addr = dev_addr, + .event_id = USBH_EVENT_FUNC_CALL, + .func_call = { + .func = xfer_retry, + .param = edpt_info + } + }; + hcd_event_handler(&event, in_isr); + } + return; + } else if (response_pid == USB_PID_DATA0 || response_pid == USB_PID_DATA1) { + LOG_CH32_USBFSH("Data toggle mismatched and DATA0/1 (not STALL). RX_LEN=%d\r\n", USBOTG_H_FS->RX_LEN); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr); + return; + } else { + LOG_CH32_USBFSH("hcd_int_handler() L%d: unexpected response PID: 0x%02x\r\n", __LINE__, response_pid); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr); + return; + } + } + } +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const *ep_desc) { + (void) rhport; + uint8_t ep_addr = ep_desc->bEndpointAddress; + uint8_t ep_num = tu_edpt_number(ep_addr); + uint16_t max_packet_size = ep_desc->wMaxPacketSize; + uint8_t xfer_type = ep_desc->bmAttributes.xfer; + LOG_CH32_USBFSH("hcd_edpt_open(rhport=%d, dev_addr=0x%02x, %p) EndpointAdderss=0x%02x,maxPacketSize=%d,xfer_type=%d\r\n", rhport, dev_addr, ep_desc, ep_addr, max_packet_size, xfer_type); + + while (usb_current_xfer_info.is_busy) { } + + if (ep_num == 0x00) { + TU_ASSERT(get_or_add_edpt_record(dev_addr, 0x00, max_packet_size, xfer_type) != NULL, false); + TU_ASSERT(get_or_add_edpt_record(dev_addr, 0x80, max_packet_size, xfer_type) != NULL, false); + } else { + TU_ASSERT(get_or_add_edpt_record(dev_addr, ep_addr, max_packet_size, xfer_type) != NULL, false); + } + + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_PORT_EN; + USBOTG_H_FS->HOST_SETUP |= USBFS_UH_SOF_EN; + + hardware_set_port_address_speed(dev_addr); + + return true; +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { + (void) rhport; + + LOG_CH32_USBFSH("hcd_edpt_xfer(%d, 0x%02x, 0x%02x, ...)\r\n", rhport, dev_addr, ep_addr); + + while (usb_current_xfer_info.is_busy) {} + usb_current_xfer_info.is_busy = true; + + usb_edpt_t *edpt_info = get_edpt_record(dev_addr, ep_addr); + TU_ASSERT(edpt_info != NULL); + + hardware_set_port_address_speed(dev_addr); + + usb_current_xfer_info.dev_addr = dev_addr; + usb_current_xfer_info.ep_addr = ep_addr; + usb_current_xfer_info.buffer = buffer; + usb_current_xfer_info.bufferlen = buflen; + usb_current_xfer_info.start_ms = tusb_time_millis_api(); + usb_current_xfer_info.xferred_len = 0; + usb_current_xfer_info.nak_pending = false; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + LOG_CH32_USBFSH("hcd_edpt_xfer(): READ, dev_addr=0x%02x, ep_addr=0x%02x, len=%d\r\n", dev_addr, ep_addr, buflen); + return hardware_start_xfer(USB_PID_IN, ep_addr, edpt_info->data_toggle); + } else { + LOG_CH32_USBFSH("hcd_edpt_xfer(): WRITE, dev_addr=0x%02x, ep_addr=0x%02x, len=%d\r\n", dev_addr, ep_addr, buflen); + uint16_t copylen = TU_MIN(edpt_info->max_packet_size, buflen); + USBOTG_H_FS->HOST_TX_LEN = copylen; + memcpy(USBFS_TX_Buf, buffer, copylen); + return hardware_start_xfer(USB_PID_OUT, ep_addr, edpt_info->data_toggle); + } +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + + return false; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { + (void) rhport; + + while (usb_current_xfer_info.is_busy) {} + + usb_current_xfer_info.is_busy = true; + + LOG_CH32_USBFSH("hcd_setup_send(rhport=%d, dev_addr=0x%02x, %p)\r\n", rhport, dev_addr, setup_packet); + + hardware_set_port_address_speed(dev_addr); + + usb_edpt_t *edpt_info_tx = get_edpt_record(dev_addr, 0x00); + usb_edpt_t *edpt_info_rx = get_edpt_record(dev_addr, 0x80); + TU_ASSERT(edpt_info_tx != NULL, false); + TU_ASSERT(edpt_info_rx != NULL, false); + + // Initialize data toggle (SETUP always starts with DATA0) + // Data toggle for OUT is toggled in hcd_int_handler() + edpt_info_tx->data_toggle = 0; + // Data toggle for IN must be set 0x01 manually. + edpt_info_rx->data_toggle = 0x01; + const uint16_t setup_packet_datalen = 8; + memcpy(USBFS_TX_Buf, setup_packet, setup_packet_datalen); + USBOTG_H_FS->HOST_TX_LEN = setup_packet_datalen; + uint8_t ep_addr = (setup_packet[0] & 0x80) ? 0x80 : 0x00; + usb_current_xfer_info.dev_addr = dev_addr; + usb_current_xfer_info.ep_addr = ep_addr; + usb_current_xfer_info.start_ms = tusb_time_millis_api(); + usb_current_xfer_info.buffer = USBFS_TX_Buf; + usb_current_xfer_info.bufferlen = setup_packet_datalen; + usb_current_xfer_info.xferred_len = 0; + usb_current_xfer_info.nak_pending = false; + + hardware_start_xfer(USB_PID_SETUP, 0, 0); + + return true; +} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + LOG_CH32_USBFSH("hcd_edpt_clear_stall(rhport=%d, dev_addr=0x%02x, ep_addr=0x%02x)\r\n", rhport, dev_addr, ep_addr); + uint8_t edpt_num = tu_edpt_number(ep_addr); + uint8_t setup_request_clear_stall[8] = { + 0x02, 0x01, 0x00, 0x00, edpt_num, 0x00, 0x00, 0x00 + }; + memcpy(USBFS_TX_Buf, setup_request_clear_stall, 8); + USBOTG_H_FS->HOST_TX_LEN = 8; + + bool prev_int_state = interrupt_enabled; + hcd_int_disable(0); + + USBOTG_H_FS->HOST_EP_PID = (USB_PID_SETUP << 4) | 0x00; + USBOTG_H_FS->INT_FG |= USBFS_UIF_TRANSFER; + while ((USBOTG_H_FS->INT_FG & USBFS_UIF_TRANSFER) == 0) {} + USBOTG_H_FS->HOST_EP_PID = 0; + uint8_t response_pid = USBOTG_H_FS->INT_ST & USBFS_UIS_H_RES_MASK; + (void) response_pid; + LOG_CH32_USBFSH("hcd_edpt_clear_stall() response pid=0x%02x\r\n", response_pid); + + if (prev_int_state) { + hcd_int_enable(0); + } + + return true; +} + +#endif diff --git a/src/tusb.c b/src/tusb.c index 13b89997c..5a3ea356b 100644 --- a/src/tusb.c +++ b/src/tusb.c @@ -136,6 +136,29 @@ void tusb_int_handler(uint8_t rhport, bool in_isr) { #endif } +bool tusb_deinit(uint8_t rhport) { + TU_VERIFY(rhport < TUP_USBIP_CONTROLLER_NUM); + bool ret = false; + + #if CFG_TUD_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_DEVICE) { + TU_ASSERT(tud_deinit(rhport)); + _tusb_rhport_role[rhport] = TUSB_ROLE_INVALID; + ret = true; + } + #endif + + #if CFG_TUH_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_HOST) { + TU_ASSERT(tuh_deinit(rhport)); + _tusb_rhport_role[rhport] = TUSB_ROLE_INVALID; + ret = true; + } + #endif + + return ret; +} + //--------------------------------------------------------------------+ // Descriptor helper //--------------------------------------------------------------------+ diff --git a/src/tusb.h b/src/tusb.h index abe3e3c9b..6a469eef4 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -144,7 +144,7 @@ bool tusb_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); // Initialize roothub port with device/host role // Note: when using with RTOS, this should be called after scheduler/kernel is started. -// Otherwise, it could cause kernel issue since USB IRQ handler does use RTOS queue API. +// Since USB IRQ handler does use RTOS queue API. // Note2: defined as macro for backward compatible with tusb_init(void), can be changed to function in the future. #if defined(TUD_OPT_RHPORT) || defined(TUH_OPT_RHPORT) #define _tusb_init_arg0() tusb_rhport_init(0, NULL) @@ -162,27 +162,18 @@ bool tusb_inited(void); // Called to handle usb interrupt/event. tusb_init(rhport, role) must be called before void tusb_int_handler(uint8_t rhport, bool in_isr); -// TODO -// bool tusb_teardown(void); +// Deinit usb stack on roothub port +bool tusb_deinit(uint8_t rhport); #else #define tusb_init(...) (false) #define tusb_int_handler(...) do {}while(0) #define tusb_inited() (false) +#define tusb_deinit(...) (false) #endif -//--------------------------------------------------------------------+ -// API Implemented by user -//--------------------------------------------------------------------+ - -// Get current milliseconds, required by some port/configuration without RTOS -uint32_t tusb_time_millis_api(void); - -// Delay in milliseconds, use tusb_time_millis_api() by default. required by some port/configuration with no RTOS -void tusb_time_delay_ms_api(uint32_t ms); - #ifdef __cplusplus } #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index e36a56de9..96ebe7d59 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -35,7 +35,7 @@ #define TUSB_VERSION_REVISION 0 #define TUSB_VERSION_NUMBER (TUSB_VERSION_MAJOR * 10000 + TUSB_VERSION_MINOR * 100 + TUSB_VERSION_REVISION) -#define TUSB_VERSION_STRING TU_STRING(TUSB_VERSION_MAJOR) "." TU_STRING(TUSB_VERSION_MINOR) "." TU_STRING(TUSB_VERSION_REVISION) +#define TUSB_VERSION_STRING TU_XSTRING(TUSB_VERSION_MAJOR) "." TU_XSTRING(TUSB_VERSION_MINOR) "." TU_XSTRING(TUSB_VERSION_REVISION) //--------------------------------------------------------------------+ // Supported MCUs @@ -94,6 +94,8 @@ #define OPT_MCU_STM32U0 316 ///< ST U0 #define OPT_MCU_STM32H7RS 317 ///< ST F7RS #define OPT_MCU_STM32C0 318 ///< ST C0 +#define OPT_MCU_STM32N6 319 ///< ST N6 +#define OPT_MCU_STM32WBA 320 ///< ST WBA // Sony #define OPT_MCU_CXD56 400 ///< SONY CXD56 @@ -127,6 +129,8 @@ #define OPT_MCU_ESP32C2 905 ///< Espressif ESP32-C2 #define OPT_MCU_ESP32H2 906 ///< Espressif ESP32-H2 #define OPT_MCU_ESP32P4 907 ///< Espressif ESP32-P4 +#define OPT_MCU_ESP32C5 908 ///< Espressif ESP32-C5 +#define OPT_MCU_ESP32C61 909 ///< Espressif ESP32-C61 #define TUSB_MCU_VENDOR_ESPRESSIF (CFG_TUSB_MCU >= 900 && CFG_TUSB_MCU < 1000) // check if Espressif MCU #define TUP_MCU_ESPRESSIF TUSB_MCU_VENDOR_ESPRESSIF // for backward compatibility @@ -195,10 +199,20 @@ // Analog Devices #define OPT_MCU_MAX32690 2400 ///< ADI MAX32690 +#define OPT_MCU_MAX32665 2401 ///< ADI MAX32666/5 #define OPT_MCU_MAX32666 2401 ///< ADI MAX32666/5 #define OPT_MCU_MAX32650 2402 ///< ADI MAX32650/1/2 #define OPT_MCU_MAX78002 2403 ///< ADI MAX78002 +// ArteryTek +#define OPT_MCU_AT32F403A_407 2500 ///< ArteryTek AT32F403A_AT32F407 +#define OPT_MCU_AT32F415 2501 ///< ArteryTek AT32F415 +#define OPT_MCU_AT32F435_437 2502 ///< ArteryTek AT32F435_AT32F437 +#define OPT_MCU_AT32F423 2503 ///< ArteryTek AT32F423 +#define OPT_MCU_AT32F402_405 2504 ///< ArteryTek AT32F402_405 +#define OPT_MCU_AT32F425 2505 ///< ArteryTek AT32F425 +#define OPT_MCU_AT32F413 2506 ///< ArteryTek AT32F413 + // Check if configured MCU is one of listed // Apply _TU_CHECK_MCU with || as separator to list of input #define _TU_CHECK_MCU(_m) (CFG_TUSB_MCU == _m) @@ -267,6 +281,15 @@ #define CFG_TUD_DWC2_DMA_ENABLE CFG_TUD_DWC2_DMA_ENABLE_DEFAULT #endif +// Enable CI_HS VBUS Charge. Set this to 1 if the USB_VBUS pin is not connected to 5V VBUS (note: 3.3V is insufficient). +#ifndef CFG_TUD_CI_HS_VBUS_CHARGE + #ifndef CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT + #define CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT 0 + #endif + + #define CFG_TUD_CI_HS_VBUS_CHARGE CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT +#endif + // Enable DWC2 Slave mode for host #ifndef CFG_TUH_DWC2_SLAVE_ENABLE #ifndef CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT @@ -409,7 +432,7 @@ #ifndef CFG_TUSB_MEM_DCACHE_LINE_SIZE #ifndef CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT - #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 1 #endif #define CFG_TUSB_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT @@ -417,7 +440,7 @@ // OS selection #ifndef CFG_TUSB_OS - #define CFG_TUSB_OS OPT_OS_NONE + #define CFG_TUSB_OS OPT_OS_NONE #endif #ifndef CFG_TUSB_OS_INC_PATH @@ -595,9 +618,22 @@ // List of product IDs that can use the FTDI CDC driver. 0x0403 is FTDI's VID #ifndef CFG_TUH_CDC_FTDI_VID_PID_LIST #define CFG_TUH_CDC_FTDI_VID_PID_LIST \ - {0x0403, 0x6001}, {0x0403, 0x6006}, {0x0403, 0x6010}, {0x0403, 0x6011}, \ - {0x0403, 0x6014}, {0x0403, 0x6015}, {0x0403, 0x8372}, {0x0403, 0xFBFA}, \ - {0x0403, 0xCD18} + {0x0403, 0x6001}, /* Similar device to SIO above */ \ + {0x0403, 0x6006}, /* FTDI's alternate PID for above */ \ + {0x0403, 0x6010}, /* Dual channel device */ \ + {0x0403, 0x6011}, /* Quad channel hi-speed device */ \ + {0x0403, 0x6014}, /* Single channel hi-speed device */ \ + {0x0403, 0x6015}, /* FT-X series (FT201X, FT230X, FT231X, etc) */ \ + {0x0403, 0x6040}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6041}, /* Quad channel hi-speed device with PD */ \ + {0x0403, 0x6042}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6043}, /* Quad channel hi-speed device with PD */ \ + {0x0403, 0x6044}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6045}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6048}, /* Quad channel automotive grade hi-speed device */ \ + {0x0403, 0x8372}, /* Product Id SIO application of 8U100AX */ \ + {0x0403, 0xFBFA}, /* Product ID for FT232RL */ \ + {0x0403, 0xCD18}, /* ??? */ #endif // CP210X is not part of CDC class, only to re-use CDC driver API @@ -608,7 +644,9 @@ // List of product IDs that can use the CP210X CDC driver. 0x10C4 is Silicon Labs' VID #ifndef CFG_TUH_CDC_CP210X_VID_PID_LIST #define CFG_TUH_CDC_CP210X_VID_PID_LIST \ - {0x10C4, 0xEA60}, {0x10C4, 0xEA70} + { 0x10C4, 0xEA60 }, /* Silicon Labs factory default */ \ + { 0x10C4, 0xEA61 }, /* Silicon Labs factory default */ \ + { 0x10C4, 0xEA70 } /* Silicon Labs Dual Port factory default */ #endif #ifndef CFG_TUH_CDC_CH34X @@ -628,6 +666,24 @@ { 0x9986, 0x7523 } /* overtaken from Linux Kernel driver /drivers/usb/serial/ch341.c */ #endif +#ifndef CFG_TUH_CDC_PL2303 + // PL2303 is not part of CDC class, only to re-use CDC driver API + #define CFG_TUH_CDC_PL2303 0 +#endif + +#ifndef CFG_TUH_CDC_PL2303_VID_PID_QUIRKS_LIST + // List of product IDs that can use the PL2303 CDC driver + #define CFG_TUH_CDC_PL2303_VID_PID_LIST \ + { 0x067b, 0x2303 }, /* initial 2303 */ \ + { 0x067b, 0x2304 }, /* TB */ \ + { 0x067b, 0x23a3 }, /* GC */ \ + { 0x067b, 0x23b3 }, /* GB */ \ + { 0x067b, 0x23c3 }, /* GT */ \ + { 0x067b, 0x23d3 }, /* GL */ \ + { 0x067b, 0x23e3 }, /* GE */ \ + { 0x067b, 0x23f3 } /* GS */ +#endif + #ifndef CFG_TUH_HID #define CFG_TUH_HID 0 #endif |
