2023-03-17 14:06:25 +07:00
/*
2020-01-14 23:30:39 -05:00
* The MIT License ( MIT )
*
* Copyright ( c ) 2019 Ha Thach ( tinyusb . org )
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
*
* This file is part of the TinyUSB stack .
2024-01-15 16:17:42 +07:00
*
* Contribution
* - Heiko Kuester : CH34x support
2020-01-14 23:30:39 -05:00
*/
# include "tusb_option.h"
2022-02-23 21:49:11 +07:00
# if (CFG_TUH_ENABLED && CFG_TUH_CDC)
2020-01-14 23:30:39 -05:00
2021-05-27 18:18:30 +07:00
# include "host/usbh.h"
2023-08-15 22:51:21 +07:00
# include "host/usbh_pvt.h"
2021-06-10 16:48:20 +07:00
2020-01-14 23:30:39 -05:00
# include "cdc_host.h"
2023-08-02 15:34:18 +07:00
// 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
# endif
# define TU_LOG_DRV(...) TU_LOG(CFG_TUH_CDC_LOG_LEVEL, __VA_ARGS__)
2022-12-21 11:46:58 +07:00
2020-01-14 23:30:39 -05:00
//--------------------------------------------------------------------+
2023-04-28 19:06:08 +07:00
// Host CDC Interface
2020-01-14 23:30:39 -05:00
//--------------------------------------------------------------------+
2022-12-21 00:26:46 +07:00
2020-01-14 23:30:39 -05:00
typedef struct {
2022-12-15 15:27:39 +07:00
uint8_t daddr ;
2022-12-21 00:26:46 +07:00
uint8_t bInterfaceNumber ;
uint8_t bInterfaceSubClass ;
uint8_t bInterfaceProtocol ;
2020-01-14 23:30:39 -05:00
2023-04-28 19:06:08 +07:00
uint8_t serial_drid ; // Serial Driver ID
2020-01-14 23:30:39 -05:00
cdc_acm_capability_t acm_capability ;
2022-12-21 00:26:46 +07:00
uint8_t ep_notif ;
2020-01-14 23:30:39 -05:00
2023-04-28 11:12:03 +07:00
uint8_t line_state ; // DTR (bit0), RTS (bit1)
TU_ATTR_ALIGNED ( 4 ) cdc_line_coding_t line_coding ; // Baudrate, stop bits, parity, data width
2022-12-14 23:19:47 +07:00
2022-12-21 00:26:46 +07:00
tuh_xfer_cb_t user_control_cb ;
2022-12-14 23:19:47 +07:00
2022-12-21 00:26:46 +07:00
struct {
tu_edpt_stream_t tx ;
tu_edpt_stream_t rx ;
2022-12-14 23:19:47 +07:00
2022-12-21 00:26:46 +07:00
uint8_t tx_ff_buf [ CFG_TUH_CDC_TX_BUFSIZE ] ;
2023-03-24 14:02:09 +07:00
CFG_TUH_MEM_ALIGN uint8_t tx_ep_buf [ CFG_TUH_CDC_TX_EPSIZE ] ;
2022-12-14 23:19:47 +07:00
2022-12-21 00:26:46 +07:00
uint8_t rx_ff_buf [ CFG_TUH_CDC_TX_BUFSIZE ] ;
2023-03-24 14:02:09 +07:00
CFG_TUH_MEM_ALIGN uint8_t rx_ep_buf [ CFG_TUH_CDC_TX_EPSIZE ] ;
2022-12-21 00:26:46 +07:00
} stream ;
2022-12-14 23:19:47 +07:00
2023-12-26 20:14:03 +01:00
# if CFG_TUH_CDC_CH34X
struct {
uint32_t baud_rate ;
uint8_t mcr ;
uint8_t msr ;
uint8_t lcr ;
uint32_t quirks ;
uint8_t version ;
} ch34x ;
# endif
2022-12-14 23:19:47 +07:00
} cdch_interface_t ;
2020-01-14 23:30:39 -05:00
2023-04-28 19:06:08 +07:00
CFG_TUH_MEM_SECTION
static cdch_interface_t cdch_data [ CFG_TUH_CDC ] ;
2020-01-14 23:30:39 -05:00
//--------------------------------------------------------------------+
2023-04-28 19:06:08 +07:00
// Serial Driver
2020-01-14 23:30:39 -05:00
//--------------------------------------------------------------------+
2022-12-15 15:27:39 +07:00
2023-04-28 19:06:08 +07:00
//------------- ACM prototypes -------------//
2024-01-09 09:53:54 +01:00
static bool acm_open ( uint8_t daddr , tusb_desc_interface_t const * itf_desc , uint16_t max_len ) ;
2023-04-28 19:06:08 +07:00
static void acm_process_config ( tuh_xfer_t * 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 ) ;
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_baudrate ( cdch_interface_t * p_cdc , uint32_t baudrate , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) ;
//------------- FTDI prototypes -------------//
# if CFG_TUH_CDC_FTDI
# include "serial/ftdi_sio.h"
2024-01-16 12:54:39 +07:00
static uint16_t const ftdi_vid_pid_list [ ] [ 2 ] = { CFG_TUH_CDC_FTDI_VID_PID_LIST } ;
2023-04-28 19:06:08 +07:00
// Store last request baudrate since divisor to baudrate is not easy
static uint32_t _ftdi_requested_baud ;
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_modem_ctrl ( cdch_interface_t * p_cdc , uint16_t line_state , tuh_xfer_cb_t complete_cb , uintptr_t 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 ) ;
# endif
//------------- CP210X prototypes -------------//
# if CFG_TUH_CDC_CP210X
# include "serial/cp210x.h"
2024-01-16 12:54:39 +07:00
static uint16_t const cp210x_vid_pid_list [ ] [ 2 ] = { CFG_TUH_CDC_CP210X_VID_PID_LIST } ;
2023-04-28 19:06:08 +07:00
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_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 , uint32_t baudrate , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) ;
# endif
2023-12-26 20:14:03 +01:00
//------------- CH34x prototypes -------------//
# if CFG_TUH_CDC_CH34X
# include "serial/ch34x.h"
2024-01-16 12:54:39 +07:00
static uint16_t const ch34x_vid_pid_list [ ] [ 2 ] = { CFG_TUH_CDC_CH34X_VID_PID_LIST } ;
2023-12-26 20:14:03 +01:00
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_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 , uint32_t baudrate , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) ;
# endif
2024-01-15 16:56:18 +07:00
//------------- Common -------------//
2023-12-26 20:14:03 +01:00
2023-04-28 19:06:08 +07:00
enum {
SERIAL_DRIVER_ACM = 0 ,
# if CFG_TUH_CDC_FTDI
SERIAL_DRIVER_FTDI ,
# endif
# if CFG_TUH_CDC_CP210X
SERIAL_DRIVER_CP210X ,
# endif
2023-12-26 20:14:03 +01:00
# if CFG_TUH_CDC_CH34X
SERIAL_DRIVER_CH34X ,
# endif
2024-01-16 12:54:39 +07:00
SERIAL_DRIVER_COUNT
2023-04-28 19:06:08 +07:00
} ;
typedef struct {
2024-01-16 12:54:39 +07:00
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 ) ;
2023-04-28 19:06:08 +07:00
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 ) ;
} cdch_serial_driver_t ;
// Note driver list must be in the same order as SERIAL_DRIVER enum
static const cdch_serial_driver_t serial_drivers [ ] = {
2024-01-16 12:54:39 +07:00
{
. vid_pid_list = NULL ,
. vid_pid_count = 0 ,
. open = acm_open ,
. process_set_config = acm_process_config ,
. set_control_line_state = acm_set_control_line_state ,
. set_baudrate = acm_set_baudrate
2023-04-28 19:06:08 +07:00
} ,
# if CFG_TUH_CDC_FTDI
2024-01-16 12:54:39 +07:00
{
. 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
2023-04-28 19:06:08 +07:00
} ,
# endif
# if CFG_TUH_CDC_CP210X
2024-01-16 12:54:39 +07:00
{
. 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 ,
. set_control_line_state = cp210x_set_modem_ctrl ,
. set_baudrate = cp210x_set_baudrate
2023-04-28 19:06:08 +07:00
} ,
# endif
2023-12-26 20:14:03 +01:00
# if CFG_TUH_CDC_CH34X
2024-01-16 12:54:39 +07:00
{
. 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 ,
. set_control_line_state = ch34x_set_modem_ctrl ,
. set_baudrate = ch34x_set_baudrate
2023-12-26 20:14:03 +01:00
} ,
2024-01-16 12:54:39 +07:00
# endif
2023-04-28 19:06:08 +07:00
} ;
2024-01-16 12:54:39 +07:00
TU_VERIFY_STATIC ( TU_ARRAY_SIZE ( serial_drivers ) = = SERIAL_DRIVER_COUNT , " Serial driver count mismatch " ) ;
2023-04-28 19:06:08 +07:00
//--------------------------------------------------------------------+
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
2020-01-14 23:30:39 -05:00
2024-01-15 16:56:18 +07:00
static inline cdch_interface_t * get_itf ( uint8_t idx ) {
2022-12-21 00:26:46 +07:00
TU_ASSERT ( idx < CFG_TUH_CDC , NULL ) ;
cdch_interface_t * p_cdc = & cdch_data [ idx ] ;
return ( p_cdc - > daddr ! = 0 ) ? p_cdc : NULL ;
}
2024-01-15 16:56:18 +07:00
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 + + ) {
2022-12-21 00:26:46 +07:00
cdch_interface_t * p_cdc = & cdch_data [ i ] ;
if ( ( p_cdc - > daddr = = daddr ) & &
2024-01-15 16:56:18 +07:00
( ep_addr = = p_cdc - > ep_notif | | ep_addr = = p_cdc - > stream . rx . ep_addr | | ep_addr = = p_cdc - > stream . tx . ep_addr ) ) {
2022-12-21 13:05:45 +07:00
return i ;
2022-12-21 00:26:46 +07:00
}
2022-12-15 15:27:39 +07:00
}
2020-11-02 09:19:34 +07:00
2023-03-21 12:54:43 +07:00
return TUSB_INDEX_INVALID_8 ;
2020-01-14 23:30:39 -05:00
}
2024-01-15 16:56:18 +07:00
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 + + ) {
2023-04-27 15:20:04 +07:00
if ( cdch_data [ i ] . daddr = = 0 ) {
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 ;
return p_cdc ;
}
2020-01-14 23:30:39 -05:00
}
2022-12-15 15:27:39 +07:00
return NULL ;
2020-01-14 23:30:39 -05:00
}
2023-04-27 15:20:04 +07:00
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 ) ;
2023-04-27 17:32:56 +07:00
//--------------------------------------------------------------------+
// APPLICATION API
//--------------------------------------------------------------------+
2023-04-27 15:20:04 +07:00
2024-01-15 16:56:18 +07:00
uint8_t tuh_cdc_itf_get_index ( uint8_t daddr , uint8_t itf_num ) {
for ( uint8_t i = 0 ; i < CFG_TUH_CDC ; i + + ) {
2023-04-27 17:32:56 +07:00
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 ;
2023-04-27 15:20:04 +07:00
}
2024-01-15 16:56:18 +07:00
bool tuh_cdc_itf_get_info ( uint8_t idx , tuh_itf_info_t * info ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc & & info ) ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
info - > daddr = p_cdc - > daddr ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
// 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_cdc - > bInterfaceNumber ;
desc - > bAlternateSetting = 0 ;
desc - > bNumEndpoints = 2u + ( p_cdc - > ep_notif ? 1u : 0u ) ;
desc - > bInterfaceClass = TUSB_CLASS_CDC ;
desc - > bInterfaceSubClass = p_cdc - > bInterfaceSubClass ;
desc - > bInterfaceProtocol = p_cdc - > bInterfaceProtocol ;
desc - > iInterface = 0 ; // not used yet
return true ;
2023-04-27 16:32:42 +07:00
}
2024-01-15 16:56:18 +07:00
bool tuh_cdc_mounted ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
return p_cdc ! = NULL ;
2023-04-27 15:20:04 +07:00
}
2024-01-15 16:56:18 +07:00
bool tuh_cdc_get_dtr ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
return ( p_cdc - > line_state & CDC_CONTROL_LINE_STATE_DTR ) ? true : false ;
2023-04-27 15:20:04 +07:00
}
2024-01-15 16:56:18 +07:00
bool tuh_cdc_get_rts ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
return ( p_cdc - > line_state & CDC_CONTROL_LINE_STATE_RTS ) ? true : false ;
2023-04-27 15:20:04 +07:00
}
2024-01-15 16:56:18 +07:00
bool tuh_cdc_get_local_line_coding ( uint8_t idx , cdc_line_coding_t * line_coding ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
* line_coding = p_cdc - > line_coding ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
return true ;
}
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
//--------------------------------------------------------------------+
// Write
//--------------------------------------------------------------------+
2023-04-27 15:20:04 +07:00
2024-01-15 16:56:18 +07:00
uint32_t tuh_cdc_write ( uint8_t idx , void const * buffer , uint32_t bufsize ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
return tu_edpt_stream_write ( & p_cdc - > stream . tx , buffer , bufsize ) ;
}
2023-04-27 15:20:04 +07:00
2024-01-15 16:56:18 +07:00
uint32_t tuh_cdc_write_flush ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
return tu_edpt_stream_write_xfer ( & p_cdc - > stream . tx ) ;
2023-04-27 15:20:04 +07:00
}
2024-01-15 16:56:18 +07:00
bool tuh_cdc_write_clear ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
return tu_edpt_stream_clear ( & p_cdc - > stream . tx ) ;
}
2024-01-15 16:56:18 +07:00
uint32_t tuh_cdc_write_available ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
return tu_edpt_stream_write_available ( & p_cdc - > stream . tx ) ;
}
2023-04-27 15:20:04 +07:00
//--------------------------------------------------------------------+
2023-04-27 17:32:56 +07:00
// Read
2023-04-27 15:20:04 +07:00
//--------------------------------------------------------------------+
2024-01-15 16:56:18 +07:00
uint32_t tuh_cdc_read ( uint8_t idx , void * buffer , uint32_t bufsize ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
return tu_edpt_stream_read ( & p_cdc - > stream . rx , buffer , bufsize ) ;
}
2023-04-27 15:20:04 +07:00
2024-01-15 16:56:18 +07:00
uint32_t tuh_cdc_read_available ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
2023-04-27 16:32:42 +07:00
2023-04-27 17:32:56 +07:00
return tu_edpt_stream_read_available ( & p_cdc - > stream . rx ) ;
}
2023-04-27 15:20:04 +07:00
2024-01-15 16:56:18 +07:00
bool tuh_cdc_peek ( uint8_t idx , uint8_t * ch ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
2023-04-27 15:20:04 +07:00
TU_VERIFY ( p_cdc ) ;
2023-04-27 17:32:56 +07:00
return tu_edpt_stream_peek ( & p_cdc - > stream . rx , ch ) ;
}
2023-04-27 15:20:04 +07:00
2024-01-15 16:56:18 +07:00
bool tuh_cdc_read_clear ( uint8_t idx ) {
2023-04-27 17:32:56 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_VERIFY ( p_cdc ) ;
2023-04-27 15:20:04 +07:00
2023-04-27 17:32:56 +07:00
bool ret = tu_edpt_stream_clear ( & p_cdc - > stream . rx ) ;
tu_edpt_stream_read_xfer ( & p_cdc - > stream . rx ) ;
return ret ;
2023-04-27 15:20:04 +07:00
}
2023-04-27 17:32:56 +07:00
//--------------------------------------------------------------------+
// Control Endpoint API
//--------------------------------------------------------------------+
2022-12-21 00:26:46 +07:00
// internal control complete to update state such as line state, encoding
2024-01-15 16:56:18 +07:00
static void cdch_internal_control_complete ( tuh_xfer_t * xfer ) {
2022-12-21 00:26:46 +07:00
uint8_t const itf_num = ( uint8_t ) tu_le16toh ( xfer - > setup - > wIndex ) ;
uint8_t idx = tuh_cdc_itf_get_index ( xfer - > daddr , itf_num ) ;
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_ASSERT ( p_cdc , ) ;
2024-01-15 16:56:18 +07:00
if ( xfer - > result = = XFER_RESULT_SUCCESS ) {
2023-04-28 19:06:08 +07:00
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 ) tu_le16toh ( xfer - > setup - > wValue ) ;
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 ;
2023-04-26 12:43:46 +07:00
}
2023-04-28 19:06:08 +07:00
break ;
2023-04-26 12:43:46 +07:00
2023-04-28 19:06:08 +07:00
# if CFG_TUH_CDC_FTDI
case SERIAL_DRIVER_FTDI :
switch ( xfer - > setup - > bRequest ) {
case FTDI_SIO_MODEM_CTRL :
p_cdc - > line_state = ( uint8_t ) ( tu_le16toh ( xfer - > setup - > wValue ) & 0x00ff ) ;
break ;
2023-04-26 12:43:46 +07:00
2023-04-28 19:06:08 +07:00
case FTDI_SIO_SET_BAUD_RATE :
// convert from divisor to baudrate is not supported
p_cdc - > line_coding . bit_rate = _ftdi_requested_baud ;
break ;
2022-12-21 00:26:46 +07:00
2023-04-28 19:06:08 +07:00
default : break ;
}
break ;
# endif
2023-04-27 15:29:44 +07:00
2023-04-28 19:06:08 +07:00
# if CFG_TUH_CDC_CP210X
case SERIAL_DRIVER_CP210X :
switch ( xfer - > setup - > bRequest ) {
case CP210X_SET_MHS :
p_cdc - > line_state = ( uint8_t ) ( tu_le16toh ( xfer - > setup - > wValue ) & 0x00ff ) ;
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 ;
}
break ;
# endif
2023-04-27 15:29:44 +07:00
2023-12-26 20:14:03 +01:00
# if CFG_TUH_CDC_CH34X
case SERIAL_DRIVER_CH34X :
TU_ASSERT ( false , ) ; // see special ch34x_control_complete function
break ;
# endif
2023-04-28 19:06:08 +07:00
default : break ;
}
}
2023-04-27 15:29:44 +07:00
2023-04-28 19:06:08 +07:00
xfer - > complete_cb = p_cdc - > user_control_cb ;
if ( xfer - > complete_cb ) {
xfer - > complete_cb ( xfer ) ;
}
2023-04-27 15:29:44 +07:00
}
2023-04-28 19:06:08 +07:00
bool tuh_cdc_set_control_line_state ( uint8_t idx , uint16_t line_state , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2022-12-21 00:26:46 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
2023-04-28 19:06:08 +07:00
TU_VERIFY ( p_cdc & & p_cdc - > serial_drid < SERIAL_DRIVER_COUNT ) ;
2023-04-28 21:50:54 +07:00
cdch_serial_driver_t const * driver = & serial_drivers [ p_cdc - > serial_drid ] ;
2024-01-16 13:16:53 +07:00
if ( complete_cb ) {
2023-04-28 21:50:54 +07:00
return driver - > set_control_line_state ( p_cdc , line_state , complete_cb , user_data ) ;
2024-01-16 13:16:53 +07:00
} else {
2023-04-28 21:50:54 +07:00
// blocking
2023-04-28 22:14:14 +07:00
xfer_result_t result = XFER_RESULT_INVALID ;
2023-04-28 21:50:54 +07:00
bool ret = driver - > set_control_line_state ( p_cdc , line_state , complete_cb , ( uintptr_t ) & result ) ;
if ( user_data ) {
// user_data is not NULL, return result via user_data
* ( ( xfer_result_t * ) user_data ) = result ;
}
2023-04-28 22:14:14 +07:00
TU_VERIFY ( ret & & result = = XFER_RESULT_SUCCESS ) ;
p_cdc - > line_state = ( uint8_t ) line_state ;
return true ;
2023-04-28 21:50:54 +07:00
}
2020-11-02 09:19:34 +07:00
}
2023-04-28 19:06:08 +07:00
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 ) ;
TU_VERIFY ( p_cdc & & p_cdc - > serial_drid < SERIAL_DRIVER_COUNT ) ;
2023-04-28 21:50:54 +07:00
cdch_serial_driver_t const * driver = & serial_drivers [ p_cdc - > serial_drid ] ;
2024-01-16 13:16:53 +07:00
if ( complete_cb ) {
2023-04-28 21:50:54 +07:00
return driver - > set_baudrate ( p_cdc , baudrate , complete_cb , user_data ) ;
2024-01-16 13:16:53 +07:00
} else {
2023-04-28 21:50:54 +07:00
// blocking
2023-04-28 22:14:14 +07:00
xfer_result_t result = XFER_RESULT_INVALID ;
2023-04-28 21:50:54 +07:00
bool ret = driver - > set_baudrate ( p_cdc , baudrate , complete_cb , ( uintptr_t ) & result ) ;
if ( user_data ) {
// user_data is not NULL, return result via user_data
* ( ( xfer_result_t * ) user_data ) = result ;
}
2023-04-28 22:14:14 +07:00
TU_VERIFY ( ret & & result = = XFER_RESULT_SUCCESS ) ;
p_cdc - > line_coding . bit_rate = baudrate ;
return true ;
2023-04-28 21:50:54 +07:00
}
2023-04-27 15:29:44 +07:00
}
2024-01-16 13:16:53 +07:00
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 ) {
2022-12-22 18:28:06 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
2023-04-28 19:06:08 +07:00
// only ACM support this set line coding request
2023-04-28 21:50:54 +07:00
TU_VERIFY ( p_cdc & & p_cdc - > serial_drid = = SERIAL_DRIVER_ACM ) ;
TU_VERIFY ( p_cdc - > acm_capability . support_line_request ) ;
2024-01-16 13:16:53 +07:00
if ( complete_cb ) {
2023-04-28 21:50:54 +07:00
return acm_set_line_coding ( p_cdc , line_coding , complete_cb , user_data ) ;
2024-01-16 13:16:53 +07:00
} else {
2023-04-28 21:50:54 +07:00
// blocking
2023-04-28 22:14:14 +07:00
xfer_result_t result = XFER_RESULT_INVALID ;
2023-04-28 21:50:54 +07:00
bool ret = acm_set_line_coding ( p_cdc , line_coding , complete_cb , ( uintptr_t ) & result ) ;
if ( user_data ) {
// user_data is not NULL, return result via user_data
* ( ( xfer_result_t * ) user_data ) = result ;
}
2023-04-28 22:14:14 +07:00
TU_VERIFY ( ret & & result = = XFER_RESULT_SUCCESS ) ;
p_cdc - > line_coding = * line_coding ;
return true ;
2023-04-28 21:50:54 +07:00
}
2023-04-27 23:22:10 +07:00
}
2020-01-14 23:30:39 -05:00
//--------------------------------------------------------------------+
2022-12-14 17:35:01 +07:00
// CLASS-USBH API
2020-01-14 23:30:39 -05:00
//--------------------------------------------------------------------+
2022-12-14 17:35:01 +07:00
2024-01-15 16:56:18 +07:00
void cdch_init ( void ) {
2021-08-24 01:06:05 +07:00
tu_memclr ( cdch_data , sizeof ( cdch_data ) ) ;
2022-12-15 17:16:19 +07:00
2024-01-15 16:56:18 +07:00
for ( size_t i = 0 ; i < CFG_TUH_CDC ; i + + ) {
2022-12-21 00:26:46 +07:00
cdch_interface_t * p_cdc = & cdch_data [ i ] ;
2022-12-22 00:34:35 +07:00
tu_edpt_stream_init ( & p_cdc - > stream . tx , true , true , false ,
2024-01-15 16:56:18 +07:00
p_cdc - > stream . tx_ff_buf , CFG_TUH_CDC_TX_BUFSIZE ,
p_cdc - > stream . tx_ep_buf , CFG_TUH_CDC_TX_EPSIZE ) ;
2022-12-21 00:26:46 +07:00
2022-12-22 00:34:35 +07:00
tu_edpt_stream_init ( & p_cdc - > stream . rx , true , false , false ,
2024-01-15 16:56:18 +07:00
p_cdc - > stream . rx_ff_buf , CFG_TUH_CDC_RX_BUFSIZE ,
p_cdc - > stream . rx_ep_buf , CFG_TUH_CDC_RX_EPSIZE ) ;
2022-12-21 00:26:46 +07:00
}
2020-01-14 23:30:39 -05:00
}
2024-01-15 16:56:18 +07:00
void cdch_close ( uint8_t daddr ) {
for ( uint8_t idx = 0 ; idx < CFG_TUH_CDC ; idx + + ) {
2022-12-21 00:26:46 +07:00
cdch_interface_t * p_cdc = & cdch_data [ idx ] ;
2024-01-15 16:56:18 +07:00
if ( p_cdc - > daddr = = daddr ) {
2023-05-11 14:26:12 +07:00
TU_LOG_DRV ( " CDCh close addr = %u index = %u \r \n " , daddr , idx ) ;
2022-12-21 00:26:46 +07:00
// Invoke application callback
if ( tuh_cdc_umount_cb ) tuh_cdc_umount_cb ( idx ) ;
//tu_memclr(p_cdc, sizeof(cdch_interface_t));
p_cdc - > daddr = 0 ;
p_cdc - > bInterfaceNumber = 0 ;
2022-12-22 11:16:39 +07:00
tu_edpt_stream_close ( & p_cdc - > stream . tx ) ;
tu_edpt_stream_close ( & p_cdc - > stream . rx ) ;
2022-12-21 00:26:46 +07:00
}
2022-12-14 17:35:01 +07:00
}
}
2023-05-25 16:53:32 +07:00
bool cdch_xfer_cb ( uint8_t daddr , uint8_t ep_addr , xfer_result_t event , uint32_t xferred_bytes ) {
2022-12-21 00:26:46 +07:00
// TODO handle stall response, retry failed transfer ...
TU_ASSERT ( event = = XFER_RESULT_SUCCESS ) ;
2022-12-21 13:05:45 +07:00
uint8_t const idx = get_idx_by_ep_addr ( daddr , ep_addr ) ;
cdch_interface_t * p_cdc = get_itf ( idx ) ;
2022-12-21 00:26:46 +07:00
TU_ASSERT ( p_cdc ) ;
2023-05-25 16:53:32 +07:00
if ( ep_addr = = p_cdc - > stream . tx . ep_addr ) {
2022-12-21 13:05:45 +07:00
// invoke tx complete callback to possibly refill tx fifo
if ( tuh_cdc_tx_complete_cb ) tuh_cdc_tx_complete_cb ( idx ) ;
2023-05-25 16:53:32 +07:00
if ( 0 = = tu_edpt_stream_write_xfer ( & p_cdc - > stream . tx ) ) {
2022-12-22 11:31:37 +07:00
// If there is no data left, a ZLP should be sent if:
// - xferred_bytes is multiple of EP Packet size and not zero
2022-12-22 00:34:35 +07:00
tu_edpt_stream_write_zlp_if_needed ( & p_cdc - > stream . tx , xferred_bytes ) ;
2022-12-21 00:26:46 +07:00
}
2024-01-15 16:56:18 +07:00
} else if ( ep_addr = = p_cdc - > stream . rx . ep_addr ) {
2023-04-26 12:43:46 +07:00
# if CFG_TUH_CDC_FTDI
2023-04-28 19:06:08 +07:00
if ( p_cdc - > serial_drid = = SERIAL_DRIVER_FTDI ) {
2023-05-25 16:53:32 +07:00
// FTDI reserve 2 bytes for status
2024-01-15 16:56:18 +07:00
// uint8_t status[2] = {p_cdc->stream.rx.ep_buf[0], p_cdc->stream.rx.ep_buf[1]};
2023-05-25 16:53:32 +07:00
tu_edpt_stream_read_xfer_complete_offset ( & p_cdc - > stream . rx , xferred_bytes , 2 ) ;
} else
2023-04-26 12:43:46 +07:00
# endif
2023-05-25 16:53:32 +07:00
{
tu_edpt_stream_read_xfer_complete ( & p_cdc - > stream . rx , xferred_bytes ) ;
}
2023-04-26 12:43:46 +07:00
2022-12-21 00:26:46 +07:00
// invoke receive callback
2023-04-26 12:43:46 +07:00
if ( tuh_cdc_rx_cb ) tuh_cdc_rx_cb ( idx ) ;
2022-12-21 00:26:46 +07:00
// prepare for next transfer if needed
2022-12-22 00:34:35 +07:00
tu_edpt_stream_read_xfer ( & p_cdc - > stream . rx ) ;
2023-05-25 16:53:32 +07:00
} else if ( ep_addr = = p_cdc - > ep_notif ) {
2022-12-21 00:26:46 +07:00
// TODO handle notification endpoint
2023-05-25 16:53:32 +07:00
} else {
2022-12-21 00:26:46 +07:00
TU_ASSERT ( false ) ;
}
2022-12-14 17:35:01 +07:00
return true ;
}
//--------------------------------------------------------------------+
// Enumeration
//--------------------------------------------------------------------+
2023-04-26 12:43:46 +07:00
2024-01-15 16:56:18 +07:00
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 + + ) {
2023-04-26 12:43:46 +07:00
TU_ASSERT ( TUSB_DESC_ENDPOINT = = desc_ep - > bDescriptorType & &
2024-01-15 16:56:18 +07:00
TUSB_XFER_BULK = = desc_ep - > bmAttributes . xfer ) ;
2023-04-26 12:43:46 +07:00
TU_ASSERT ( tuh_edpt_open ( p_cdc - > daddr , desc_ep ) ) ;
2024-01-15 16:56:18 +07:00
if ( tu_edpt_dir ( desc_ep - > bEndpointAddress ) = = TUSB_DIR_IN ) {
2023-04-26 12:43:46 +07:00
tu_edpt_stream_open ( & p_cdc - > stream . rx , p_cdc - > daddr , desc_ep ) ;
2024-01-15 16:56:18 +07:00
} else {
2023-04-26 12:43:46 +07:00
tu_edpt_stream_open ( & p_cdc - > stream . tx , p_cdc - > daddr , desc_ep ) ;
}
desc_ep = ( tusb_desc_endpoint_t const * ) tu_desc_next ( desc_ep ) ;
}
return true ;
}
2024-01-15 16:56:18 +07:00
bool cdch_open ( uint8_t rhport , uint8_t daddr , tusb_desc_interface_t const * itf_desc , uint16_t max_len ) {
2023-04-26 12:43:46 +07:00
( void ) rhport ;
2024-01-15 16:56:18 +07:00
// For CDC: only support ACM subclass
2023-04-26 12:43:46 +07:00
// Note: Protocol 0xFF can be RNDIS device
2024-01-15 16:56:18 +07:00
if ( TUSB_CLASS_CDC = = itf_desc - > bInterfaceClass & &
CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL = = itf_desc - > bInterfaceSubClass ) {
2023-04-26 12:43:46 +07:00
return acm_open ( daddr , itf_desc , max_len ) ;
}
2024-01-16 12:54:39 +07:00
else if ( SERIAL_DRIVER_COUNT > 1 & &
TUSB_CLASS_VENDOR_SPECIFIC = = itf_desc - > bInterfaceClass ) {
2023-04-26 12:43:46 +07:00
uint16_t vid , pid ;
TU_VERIFY ( tuh_vid_pid_get ( daddr , & vid , & pid ) ) ;
2024-01-16 12:54:39 +07:00
for ( size_t dr = 1 ; dr < SERIAL_DRIVER_COUNT ; dr + + ) {
cdch_serial_driver_t const * 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 ) ;
}
2023-12-26 20:14:03 +01:00
}
}
2020-01-14 23:30:39 -05:00
}
2023-04-26 12:43:46 +07:00
return false ;
2020-11-02 09:19:34 +07:00
}
2020-01-14 23:30:39 -05:00
2023-04-27 15:20:04 +07:00
static void set_config_complete ( cdch_interface_t * p_cdc , uint8_t idx , uint8_t itf_num ) {
if ( tuh_cdc_mount_cb ) tuh_cdc_mount_cb ( idx ) ;
// Prepare for incoming data
tu_edpt_stream_read_xfer ( & p_cdc - > stream . rx ) ;
// notify usbh that driver enumeration is complete
usbh_driver_set_config_complete ( p_cdc - > daddr , itf_num ) ;
}
2024-01-16 12:54:39 +07:00
bool cdch_set_config ( uint8_t daddr , uint8_t itf_num ) {
2023-04-28 19:06:08 +07:00
tusb_control_request_t request ;
request . wIndex = tu_htole16 ( ( uint16_t ) itf_num ) ;
// fake transfer to kick-off process
tuh_xfer_t xfer ;
xfer . daddr = daddr ;
xfer . result = XFER_RESULT_SUCCESS ;
xfer . setup = & request ;
xfer . user_data = 0 ; // initial state
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
//--------------------------------------------------------------------+
enum {
CONFIG_ACM_SET_CONTROL_LINE_STATE = 0 ,
CONFIG_ACM_SET_LINE_CODING ,
CONFIG_ACM_COMPLETE ,
} ;
2024-01-15 16:56:18 +07:00
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 ;
2023-04-28 19:06:08 +07:00
2024-01-15 16:56:18 +07:00
cdch_interface_t * p_cdc = make_new_itf ( daddr , itf_desc ) ;
2023-04-28 19:06:08 +07:00
TU_VERIFY ( p_cdc ) ;
p_cdc - > serial_drid = SERIAL_DRIVER_ACM ;
//------------- Control Interface -------------//
2024-01-15 16:56:18 +07:00
uint8_t const * p_desc = tu_desc_next ( itf_desc ) ;
2023-04-28 19:06:08 +07:00
// Communication Functional Descriptors
2024-01-15 16:56:18 +07:00
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 ) ) {
2023-04-28 19:06:08 +07:00
// save ACM bmCapabilities
2024-01-15 16:56:18 +07:00
p_cdc - > acm_capability = ( ( cdc_desc_func_acm_t const * ) p_desc ) - > bmCapabilities ;
2023-04-28 19:06:08 +07:00
}
p_desc = tu_desc_next ( p_desc ) ;
}
// Open notification endpoint of control interface if any
2024-01-15 16:56:18 +07:00
if ( itf_desc - > bNumEndpoints = = 1 ) {
2023-04-28 19:06:08 +07:00
TU_ASSERT ( TUSB_DESC_ENDPOINT = = tu_desc_type ( p_desc ) ) ;
2024-01-15 16:56:18 +07:00
tusb_desc_endpoint_t const * desc_ep = ( tusb_desc_endpoint_t const * ) p_desc ;
2023-04-28 19:06:08 +07:00
2024-01-15 16:56:18 +07:00
TU_ASSERT ( tuh_edpt_open ( daddr , desc_ep ) ) ;
2023-04-28 19:06:08 +07:00
p_cdc - > ep_notif = desc_ep - > bEndpointAddress ;
p_desc = tu_desc_next ( p_desc ) ;
}
//------------- Data Interface (if any) -------------//
2024-01-15 16:56:18 +07:00
if ( ( TUSB_DESC_INTERFACE = = tu_desc_type ( p_desc ) ) & &
( TUSB_CLASS_CDC_DATA = = ( ( tusb_desc_interface_t const * ) p_desc ) - > bInterfaceClass ) ) {
2023-04-28 19:06:08 +07:00
// next to endpoint descriptor
p_desc = tu_desc_next ( p_desc ) ;
// data endpoints expected to be in pairs
2024-01-15 16:56:18 +07:00
TU_ASSERT ( open_ep_stream_pair ( p_cdc , ( tusb_desc_endpoint_t const * ) p_desc ) ) ;
2023-04-28 19:06:08 +07:00
}
return true ;
}
2024-01-15 16:56:18 +07:00
static void acm_process_config ( tuh_xfer_t * xfer ) {
2022-12-22 18:28:06 +07:00
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 ) ;
2024-01-15 16:56:18 +07:00
cdch_interface_t * p_cdc = get_itf ( idx ) ;
TU_ASSERT ( p_cdc , ) ;
2022-12-22 18:28:06 +07:00
2024-01-15 16:56:18 +07:00
switch ( state ) {
2023-04-27 15:29:44 +07:00
case CONFIG_ACM_SET_CONTROL_LINE_STATE :
2023-03-24 17:26:30 +07:00
# if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM
2024-01-15 16:56:18 +07:00
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 ) , ) ;
2023-03-24 17:26:30 +07:00
break ;
}
2024-01-15 16:56:18 +07:00
# endif
2023-03-24 17:26:30 +07:00
TU_ATTR_FALLTHROUGH ;
2022-12-21 00:26:46 +07:00
2023-04-27 15:29:44 +07:00
case CONFIG_ACM_SET_LINE_CODING :
2024-01-15 16:56:18 +07:00
# ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM
if ( p_cdc - > acm_capability . support_line_request ) {
2023-03-24 17:26:30 +07:00
cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM ;
2024-01-15 16:56:18 +07:00
TU_ASSERT ( acm_set_line_coding ( p_cdc , & line_coding , acm_process_config , CONFIG_ACM_COMPLETE ) , ) ;
2023-03-24 17:26:30 +07:00
break ;
}
2024-01-15 16:56:18 +07:00
# endif
2023-03-24 17:26:30 +07:00
TU_ATTR_FALLTHROUGH ;
2022-12-15 15:27:39 +07:00
2023-04-27 15:29:44 +07:00
case CONFIG_ACM_COMPLETE :
2022-12-22 18:28:06 +07:00
// itf_num+1 to account for data interface as well
2024-01-15 16:56:18 +07:00
set_config_complete ( p_cdc , idx , itf_num + 1 ) ;
2023-04-28 19:06:08 +07:00
break ;
2022-12-22 18:28:06 +07:00
2024-01-15 16:56:18 +07:00
default :
break ;
2022-12-22 18:28:06 +07:00
}
2022-12-21 00:26:46 +07:00
}
2023-04-28 19:06:08 +07:00
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 ) ;
2023-05-11 14:26:12 +07:00
TU_LOG_DRV ( " CDC ACM Set Control Line State \r \n " ) ;
2023-04-28 21:50:54 +07:00
2023-04-28 19:06:08 +07:00
tusb_control_request_t const 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 ) ,
. wIndex = tu_htole16 ( ( uint16_t ) p_cdc - > bInterfaceNumber ) ,
. wLength = 0
} ;
2022-12-21 00:26:46 +07:00
2023-04-28 19:06:08 +07:00
p_cdc - > user_control_cb = complete_cb ;
2023-04-26 12:43:46 +07:00
2023-04-28 19:06:08 +07:00
tuh_xfer_t xfer = {
. daddr = p_cdc - > daddr ,
. ep_addr = 0 ,
. setup = & request ,
. buffer = NULL ,
2023-04-28 21:50:54 +07:00
. complete_cb = complete_cb ? cdch_internal_control_complete : NULL , // complete_cb is NULL for sync call
2023-04-28 19:06:08 +07:00
. user_data = user_data
} ;
2023-04-26 12:43:46 +07:00
2023-04-28 19:06:08 +07:00
TU_ASSERT ( tuh_control_xfer ( & xfer ) ) ;
return true ;
}
2023-04-26 12:43:46 +07:00
2023-04-28 19:06:08 +07:00
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 ) {
2023-05-11 14:26:12 +07:00
TU_LOG_DRV ( " CDC ACM Set Line Conding \r \n " ) ;
2023-04-27 15:20:04 +07:00
2023-04-28 19:06:08 +07:00
tusb_control_request_t const request = {
. bmRequestType_bit = {
. recipient = TUSB_REQ_RCPT_INTERFACE ,
. type = TUSB_REQ_TYPE_CLASS ,
. direction = TUSB_DIR_OUT
} ,
. bRequest = CDC_REQUEST_SET_LINE_CODING ,
. wValue = 0 ,
. wIndex = tu_htole16 ( p_cdc - > bInterfaceNumber ) ,
. wLength = tu_htole16 ( sizeof ( cdc_line_coding_t ) )
} ;
2023-04-26 12:43:46 +07:00
2023-04-28 19:06:08 +07:00
// 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 ) ) ;
p_cdc - > user_control_cb = complete_cb ;
tuh_xfer_t xfer = {
. daddr = p_cdc - > daddr ,
. ep_addr = 0 ,
. setup = & request ,
. buffer = enum_buf ,
2023-04-28 21:50:54 +07:00
. complete_cb = complete_cb ? cdch_internal_control_complete : NULL , // complete_cb is NULL for sync call
2023-04-28 19:06:08 +07:00
. user_data = user_data
} ;
2022-12-22 18:28:06 +07:00
2023-04-28 19:06:08 +07:00
TU_ASSERT ( tuh_control_xfer ( & xfer ) ) ;
2020-01-14 23:30:39 -05:00
return true ;
}
2023-04-28 19:06:08 +07:00
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 ) ;
}
2023-04-27 17:32:56 +07:00
//--------------------------------------------------------------------+
// FTDI
//--------------------------------------------------------------------+
# 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_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 ) ;
2023-05-11 14:26:12 +07:00
TU_LOG_DRV ( " FTDI opened \r \n " ) ;
2023-04-28 19:06:08 +07:00
p_cdc - > serial_drid = SERIAL_DRIVER_FTDI ;
2023-04-27 17:32:56 +07:00
// 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 ) ;
}
// 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
} ;
tuh_xfer_t xfer = {
. daddr = p_cdc - > daddr ,
. ep_addr = 0 ,
. setup = & request ,
. buffer = NULL ,
. complete_cb = complete_cb ,
. user_data = user_data
} ;
return tuh_control_xfer ( & xfer ) ;
}
2024-01-15 16:56:18 +07:00
static bool ftdi_sio_reset ( cdch_interface_t * p_cdc , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2023-04-27 17:32:56 +07:00
return ftdi_sio_set_request ( p_cdc , FTDI_SIO_RESET , FTDI_SIO_RESET_SIO , complete_cb , user_data ) ;
}
2024-01-16 13:16:53 +07:00
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 ) {
2023-05-11 14:26:12 +07:00
TU_LOG_DRV ( " CDC FTDI Set Control Line State \r \n " ) ;
2023-04-27 17:32:56 +07:00
p_cdc - > user_control_cb = complete_cb ;
2023-04-28 21:50:54 +07:00
TU_ASSERT ( ftdi_sio_set_request ( p_cdc , FTDI_SIO_MODEM_CTRL , 0x0300 | line_state ,
complete_cb ? cdch_internal_control_complete : NULL , user_data ) ) ;
2023-04-27 17:32:56 +07:00
return true ;
}
2024-01-15 16:56:18 +07:00
static uint32_t ftdi_232bm_baud_base_to_divisor ( uint32_t baud , uint32_t base ) {
2023-04-27 23:08:25 +07:00
const 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 = 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 ;
}
2024-01-15 16:56:18 +07:00
static uint32_t ftdi_232bm_baud_to_divisor ( uint32_t baud ) {
2023-04-27 23:08:25 +07:00
return ftdi_232bm_baud_base_to_divisor ( baud , 48000000u ) ;
}
2023-04-27 17:32:56 +07:00
2024-01-15 16:56:18 +07:00
static bool ftdi_sio_set_baudrate ( cdch_interface_t * p_cdc , uint32_t baudrate , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2023-04-27 23:08:25 +07:00
uint16_t const divisor = ( uint16_t ) ftdi_232bm_baud_to_divisor ( baudrate ) ;
2023-07-02 09:02:24 +02:00
TU_LOG_DRV ( " CDC FTDI Set BaudRate = %lu, divisor = 0x%04x \r \n " , baudrate , divisor ) ;
2023-04-27 17:32:56 +07:00
p_cdc - > user_control_cb = complete_cb ;
2023-04-28 19:06:08 +07:00
_ftdi_requested_baud = baudrate ;
2023-04-28 21:50:54 +07:00
TU_ASSERT ( ftdi_sio_set_request ( p_cdc , FTDI_SIO_SET_BAUD_RATE , divisor ,
complete_cb ? cdch_internal_control_complete : NULL , user_data ) ) ;
2023-04-28 19:06:08 +07:00
2023-04-27 17:32:56 +07:00
return true ;
}
2023-04-28 19:06:08 +07:00
static void ftdi_process_config ( tuh_xfer_t * xfer ) {
2023-04-27 17:32:56 +07:00
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 , ) ;
switch ( state ) {
// Note may need to read FTDI eeprom
case CONFIG_FTDI_RESET :
2023-04-28 19:06:08 +07:00
TU_ASSERT ( ftdi_sio_reset ( p_cdc , ftdi_process_config , CONFIG_FTDI_MODEM_CTRL ) , ) ;
2023-04-27 17:32:56 +07:00
break ;
case CONFIG_FTDI_MODEM_CTRL :
# if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM
2024-01-15 16:56:18 +07:00
TU_ASSERT ( ftdi_sio_set_modem_ctrl ( p_cdc , CFG_TUH_CDC_LINE_CONTROL_ON_ENUM , ftdi_process_config , CONFIG_FTDI_SET_BAUDRATE ) , ) ;
2023-04-27 17:32:56 +07:00
break ;
# else
TU_ATTR_FALLTHROUGH ;
# endif
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 ;
2023-04-28 19:06:08 +07:00
TU_ASSERT ( ftdi_sio_set_baudrate ( p_cdc , line_coding . bit_rate , ftdi_process_config , CONFIG_FTDI_SET_DATA ) , ) ;
2023-04-27 17:32:56 +07:00
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 ) , ) ;
break ;
# endif
# endif
TU_ATTR_FALLTHROUGH ;
}
case CONFIG_FTDI_COMPLETE :
set_config_complete ( p_cdc , idx , itf_num ) ;
break ;
default :
break ;
}
}
# endif
//--------------------------------------------------------------------+
// CP210x
//--------------------------------------------------------------------+
# if CFG_TUH_CDC_CP210X
enum {
CONFIG_CP210X_IFC_ENABLE = 0 ,
CONFIG_CP210X_SET_BAUDRATE ,
CONFIG_CP210X_SET_LINE_CTL ,
CONFIG_CP210X_SET_DTR_RTS ,
CONFIG_CP210X_COMPLETE
} ;
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 ) ;
2023-05-11 14:26:12 +07:00
TU_LOG_DRV ( " CP210x opened \r \n " ) ;
2023-04-28 19:06:08 +07:00
p_cdc - > serial_drid = SERIAL_DRIVER_CP210X ;
2023-04-27 17:32:56 +07:00
// 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_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 ,
. type = TUSB_REQ_TYPE_VENDOR ,
. direction = TUSB_DIR_OUT
} ,
. bRequest = command ,
. wValue = tu_htole16 ( value ) ,
. wIndex = p_cdc - > bInterfaceNumber ,
. 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 ( ) ;
tu_memcpy_s ( enum_buf , CFG_TUH_ENUMERATION_BUFSIZE , buffer , length ) ;
}
tuh_xfer_t xfer = {
. daddr = p_cdc - > daddr ,
. ep_addr = 0 ,
. setup = & request ,
. buffer = enum_buf ,
. complete_cb = complete_cb ,
. user_data = user_data
} ;
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 ) {
return cp210x_set_request ( p_cdc , CP210X_IFC_ENABLE , enabled , 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 ) {
2023-07-02 09:02:24 +02:00
TU_LOG_DRV ( " CDC CP210x Set BaudRate = %lu \r \n " , baudrate ) ;
2023-04-28 19:06:08 +07:00
uint32_t baud_le = tu_htole32 ( baudrate ) ;
p_cdc - > user_control_cb = complete_cb ;
2023-04-28 21:50:54 +07:00
return cp210x_set_request ( p_cdc , CP210X_SET_BAUDRATE , 0 , ( uint8_t * ) & baud_le , 4 ,
complete_cb ? cdch_internal_control_complete : NULL , user_data ) ;
2023-04-27 17:32:56 +07:00
}
2024-01-15 16:56:18 +07:00
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 ) {
2023-05-11 14:26:12 +07:00
TU_LOG_DRV ( " CDC CP210x Set Control Line State \r \n " ) ;
2023-04-27 17:32:56 +07:00
p_cdc - > user_control_cb = complete_cb ;
2023-04-28 21:50:54 +07:00
return cp210x_set_request ( p_cdc , CP210X_SET_MHS , 0x0300 | line_state , NULL , 0 ,
complete_cb ? cdch_internal_control_complete : NULL , user_data ) ;
2023-04-27 17:32:56 +07:00
}
2023-04-28 19:06:08 +07:00
static void cp210x_process_config ( tuh_xfer_t * xfer ) {
2023-04-27 17:32:56 +07:00
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 , ) ;
switch ( state ) {
case CONFIG_CP210X_IFC_ENABLE :
2023-04-28 19:06:08 +07:00
TU_ASSERT ( cp210x_ifc_enable ( p_cdc , 1 , cp210x_process_config , CONFIG_CP210X_SET_BAUDRATE ) , ) ;
2023-04-27 17:32:56 +07:00
break ;
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 ;
2023-04-28 19:06:08 +07:00
TU_ASSERT ( cp210x_set_baudrate ( p_cdc , line_coding . bit_rate , cp210x_process_config , CONFIG_CP210X_SET_LINE_CTL ) , ) ;
2023-04-27 17:32:56 +07:00
break ;
# else
TU_ATTR_FALLTHROUGH ;
# endif
}
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
}
case CONFIG_CP210X_SET_DTR_RTS :
# if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM
2024-01-15 16:56:18 +07:00
TU_ASSERT ( cp210x_set_modem_ctrl ( p_cdc , CFG_TUH_CDC_LINE_CONTROL_ON_ENUM , cp210x_process_config , CONFIG_CP210X_COMPLETE ) , ) ;
2023-04-27 17:32:56 +07:00
break ;
# else
TU_ATTR_FALLTHROUGH ;
# endif
case CONFIG_CP210X_COMPLETE :
set_config_complete ( p_cdc , idx , itf_num ) ;
break ;
default : break ;
}
}
# endif
2023-12-26 20:14:03 +01:00
//--------------------------------------------------------------------+
// CH34x
//--------------------------------------------------------------------+
# if CFG_TUH_CDC_CH34X
enum {
CONFIG_CH34X_STEP1 = 0 ,
CONFIG_CH34X_STEP2 ,
CONFIG_CH34X_STEP3 ,
CONFIG_CH34X_STEP4 ,
CONFIG_CH34X_STEP5 ,
CONFIG_CH34X_STEP6 ,
CONFIG_CH34X_STEP7 ,
CONFIG_CH34X_STEP8 ,
CONFIG_CH34X_COMPLETE
} ;
2024-01-16 13:16:53 +07:00
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 ) ;
2023-12-26 20:14:03 +01:00
2024-01-16 13:16:53 +07:00
cdch_interface_t * p_cdc = make_new_itf ( daddr , itf_desc ) ;
TU_VERIFY ( p_cdc ) ;
2023-12-26 20:14:03 +01:00
2024-01-16 13:16:53 +07:00
TU_LOG_DRV ( " CH34x opened \r \n " ) ;
2023-12-26 20:14:03 +01:00
p_cdc - > serial_drid = SERIAL_DRIVER_CH34X ;
2024-01-16 13:16:53 +07:00
tusb_desc_endpoint_t const * desc_ep = ( tusb_desc_endpoint_t const * ) tu_desc_next ( itf_desc ) ;
2023-12-26 20:14:03 +01:00
// data endpoints expected to be in pairs
2024-01-16 13:16:53 +07:00
TU_ASSERT ( open_ep_stream_pair ( p_cdc , desc_ep ) ) ;
desc_ep + = 2 ;
// 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 ;
return true ;
2023-12-26 20:14:03 +01:00
}
2024-01-16 13:16:53 +07:00
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 ) {
2023-12-26 20:14:03 +01:00
tusb_control_request_t const request_setup = {
. bmRequestType_bit = {
. recipient = TUSB_REQ_RCPT_DEVICE ,
. type = TUSB_REQ_TYPE_VENDOR ,
2024-01-15 18:25:15 +07:00
. direction = direction & 0x01u
2023-12-26 20:14:03 +01:00
} ,
. bRequest = request ,
2024-01-16 13:16:53 +07:00
. wValue = tu_htole16 ( value ) ,
. wIndex = tu_htole16 ( index ) ,
. wLength = tu_htole16 ( length )
2023-12-26 20:14:03 +01:00
} ;
// use usbh enum buf since application variable does not live long enough
uint8_t * enum_buf = NULL ;
2024-01-16 13:16:53 +07:00
if ( buffer & & length > 0 ) {
2023-12-26 20:14:03 +01:00
enum_buf = usbh_get_enum_buf ( ) ;
2024-01-16 13:16:53 +07:00
tu_memcpy_s ( enum_buf , CFG_TUH_ENUMERATION_BUFSIZE , buffer , length ) ;
2023-12-26 20:14:03 +01:00
}
tuh_xfer_t xfer = {
. daddr = p_cdc - > daddr ,
. ep_addr = 0 ,
. setup = & request_setup ,
. buffer = enum_buf ,
. complete_cb = complete_cb ,
// CH34x needs a special handling of bInterfaceNumber, because wIndex is used for other purposes and not for bInterfaceNumber
2024-01-16 13:16:53 +07:00
. user_data = ( uintptr_t ) ( ( p_cdc - > bInterfaceNumber & 0xff ) < < 8 ) | ( user_data & 0xff )
2023-12-26 20:14:03 +01:00
} ;
2024-01-16 13:16:53 +07:00
return tuh_control_xfer ( & xfer ) ;
2023-12-26 20:14:03 +01:00
}
2024-01-15 16:56:18 +07:00
static bool ch341_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 ) {
2023-12-26 20:14:03 +01:00
return ch34x_set_request ( p_cdc , TUSB_DIR_OUT , request , value , index , /* buffer */ NULL , /* length */ 0 , complete_cb , user_data ) ;
}
2024-01-15 16:56:18 +07:00
static bool ch341_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 ) {
2023-12-26 20:14:03 +01:00
return ch34x_set_request ( p_cdc , TUSB_DIR_IN , request , value , index , buffer , buffersize , complete_cb , user_data ) ;
}
2024-01-15 16:56:18 +07:00
static int32_t ch341_write_reg ( cdch_interface_t * p_cdc , uint16_t reg , uint16_t value , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2023-12-26 20:14:03 +01:00
return ch341_control_out ( p_cdc , CH341_REQ_WRITE_REG , /* value */ reg , /* index */ value , complete_cb , user_data ) ;
}
2024-01-15 16:56:18 +07:00
static int32_t ch341_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 ) {
2023-12-26 20:14:03 +01:00
return ch341_control_in ( p_cdc , CH341_REQ_READ_REG , reg , /* index */ 0 , buffer , buffersize , complete_cb , user_data ) ;
}
/*
* The device line speed is given by the following equation :
*
* baudrate = 48000000 / ( 2 ^ ( 12 - 3 * ps - fact ) * div ) , where
*
* 0 < = ps < = 3 ,
* 0 < = fact < = 1 ,
* 2 < = div < = 256 if fact = 0 , or
* 9 < = div < = 256 if fact = 1
*/
// calculate baudrate devisors
// Parts of this functions have been taken over from Linux driver /drivers/usb/serial/ch341.c
2024-01-15 18:25:15 +07:00
static inline uint32_t clamp_val ( uint32_t val , uint32_t minval , uint32_t maxval ) {
return tu_min32 ( tu_max32 ( val , minval ) , maxval ) ;
}
2024-01-15 16:56:18 +07:00
static int32_t ch341_get_divisor ( cdch_interface_t * p_cdc , uint32_t speed ) {
2023-12-26 20:14:03 +01:00
uint32_t fact , div , clk_div ;
bool force_fact0 = false ;
int32_t ps ;
static const uint32_t ch341_min_rates [ ] = {
CH341_MIN_RATE ( 0 ) ,
CH341_MIN_RATE ( 1 ) ,
CH341_MIN_RATE ( 2 ) ,
CH341_MIN_RATE ( 3 ) ,
} ;
/*
* Clamp to supported range , this makes the ( ps < 0 ) and ( div < 2 )
* sanity checks below redundant .
*/
speed = clamp_val ( speed , CH341_MIN_BPS , CH341_MAX_BPS ) ;
/*
* Start with highest possible base clock ( fact = 1 ) that will give a
* divisor strictly less than 512.
*/
fact = 1 ;
for ( ps = 3 ; ps > = 0 ; ps - - ) {
2024-01-15 16:56:18 +07:00
if ( speed > ch341_min_rates [ ps ] ) break ;
2023-12-26 20:14:03 +01:00
}
2024-01-15 16:56:18 +07:00
if ( ps < 0 ) return - EINVAL ;
2023-12-26 20:14:03 +01:00
/* Determine corresponding divisor, rounding down. */
2024-01-15 18:25:15 +07:00
clk_div = CH341_CLK_DIV ( ( uint32_t ) ps , fact ) ;
2023-12-26 20:14:03 +01:00
div = CH341_CLKRATE / ( clk_div * speed ) ;
/* Some devices require a lower base clock if ps < 3. */
2024-01-15 16:56:18 +07:00
if ( ps < 3 & & ( p_cdc - > ch34x . quirks & CH341_QUIRK_LIMITED_PRESCALER ) ) {
2023-12-26 20:14:03 +01:00
force_fact0 = true ;
2024-01-15 16:56:18 +07:00
}
2023-12-26 20:14:03 +01:00
/* Halve base clock (fact = 0) if required. */
if ( div < 9 | | div > 255 | | force_fact0 ) {
div / = 2 ;
clk_div * = 2 ;
fact = 0 ;
}
2024-01-15 16:56:18 +07:00
if ( div < 2 ) return - EINVAL ;
2023-12-26 20:14:03 +01:00
/*
* Pick next divisor if resulting rate is closer to the requested one ,
* scale up to avoid rounding errors on low rates .
*/
if ( 16 * CH341_CLKRATE / ( clk_div * div ) - 16 * speed > =
2024-01-15 16:56:18 +07:00
16 * speed - 16 * CH341_CLKRATE / ( clk_div * ( div + 1 ) ) ) {
2023-12-26 20:14:03 +01:00
div + + ;
2024-01-15 16:56:18 +07:00
}
2023-12-26 20:14:03 +01:00
/*
* Prefer lower base clock ( fact = 0 ) if even divisor .
*
* Note that this makes the receiver more tolerant to errors .
*/
if ( fact = = 1 & & div % 2 = = 0 ) {
div / = 2 ;
fact = 0 ;
}
2024-01-15 18:25:15 +07:00
return ( int32_t ) ( ( 0x100 - div ) < < 8 | fact < < 2 | ( uint32_t ) ps ) ;
2023-12-26 20:14:03 +01:00
}
// set baudrate (low level)
// do not confuse with ch34x_set_baudrate
// Parts of this functions have been taken over from Linux driver /drivers/usb/serial/ch341.c
2024-01-15 18:25:15 +07:00
static int32_t ch341_set_baudrate ( cdch_interface_t * p_cdc , uint32_t baud_rate , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2024-01-15 16:56:18 +07:00
if ( ! baud_rate ) return - EINVAL ;
2023-12-26 20:14:03 +01:00
2024-01-15 18:25:15 +07:00
int ret ;
ret = ch341_get_divisor ( p_cdc , baud_rate ) ;
if ( ret < 0 ) return - EINVAL ;
uint16_t val = ( uint16_t ) ret ;
2023-12-26 20:14:03 +01:00
/*
* CH341A buffers data until a full endpoint - size packet ( 32 bytes )
* has been received unless bit 7 is set .
*
* At least one device with version 0x27 appears to have this bit
* inverted .
*/
2024-01-15 16:56:18 +07:00
if ( p_cdc - > ch34x . version > 0x27 ) {
2024-01-15 18:42:39 +07:00
val = ( val | TU_BIT ( 7 ) ) ;
2024-01-15 16:56:18 +07:00
}
2023-12-26 20:14:03 +01:00
return ch341_write_reg ( p_cdc , CH341_REG_DIVISOR < < 8 | CH341_REG_PRESCALER , val , complete_cb , user_data ) ;
}
// set lcr register
// Parts of this functions have been taken over from Linux driver /drivers/usb/serial/ch341.c
2024-01-15 16:56:18 +07:00
static int32_t ch341_set_lcr ( cdch_interface_t * p_cdc , uint8_t lcr , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2023-12-26 20:14:03 +01:00
/*
* Chip versions before version 0x30 as read using
* CH341_REQ_READ_VERSION used separate registers for line control
* ( stop bits , parity and word length ) . Version 0x30 and above use
* CH341_REG_LCR only and CH341_REG_LCR2 is always set to zero .
*/
2024-01-15 16:56:18 +07:00
if ( p_cdc - > ch34x . version < 0x30 ) return 0 ;
2023-12-26 20:14:03 +01:00
return ch341_write_reg ( p_cdc , CH341_REG_LCR2 < < 8 | CH341_REG_LCR , lcr , complete_cb , user_data ) ;
}
// set handshake (modem controls)
// Parts of this functions have been taken over from Linux driver /drivers/usb/serial/ch341.c
2024-01-15 16:56:18 +07:00
static int32_t ch341_set_handshake ( cdch_interface_t * p_cdc , uint8_t control , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2023-12-26 20:14:03 +01:00
return ch341_control_out ( p_cdc , CH341_REQ_MODEM_CTRL , /* value */ ~ control , /* index */ 0 , complete_cb , user_data ) ;
}
// detect quirks (special versions of CH34x)
// Parts of this functions have been taken over from Linux driver /drivers/usb/serial/ch341.c
2024-01-15 16:56:18 +07:00
static int32_t ch341_detect_quirks ( tuh_xfer_t * xfer , cdch_interface_t * p_cdc , uint8_t step , uint8_t * buffer , uint16_t buffersize , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) {
2023-12-26 20:14:03 +01:00
/*
* A subset of CH34x devices does not support all features . The
* prescaler is limited and there is no support for sending a RS232
* break condition . A read failure when trying to set up the latter is
* used to detect these devices .
*/
2024-01-15 16:56:18 +07:00
switch ( step ) {
case 1 :
p_cdc - > ch34x . quirks = 0 ;
return ch341_read_reg_request ( p_cdc , CH341_REG_BREAK , buffer , buffersize , complete_cb , user_data ) ;
break ;
case 2 :
if ( xfer - > result ! = XFER_RESULT_SUCCESS ) {
p_cdc - > ch34x . quirks | = CH341_QUIRK_LIMITED_PRESCALER | CH341_QUIRK_SIMULATE_BREAK ;
}
return true ;
break ;
default :
TU_ASSERT ( false ) ; // suspicious step
break ;
2023-12-26 20:14:03 +01:00
}
}
// internal control complete to update state such as line state, encoding
// CH34x needs a special interface recovery due to abnormal wIndex usage
2024-01-15 16:56:18 +07:00
static void ch34x_control_complete ( tuh_xfer_t * xfer ) {
uint8_t const itf_num = ( uint8_t ) ( ( xfer - > user_data & 0xff00 ) > > 8 ) ;
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 , ) ;
TU_ASSERT ( p_cdc - > serial_drid = = SERIAL_DRIVER_CH34X , ) ; // ch34x_control_complete is only used for CH34x
2023-12-26 20:14:03 +01:00
if ( xfer - > result = = XFER_RESULT_SUCCESS ) {
switch ( xfer - > setup - > bRequest ) {
2024-01-15 16:56:18 +07:00
case CH341_REQ_WRITE_REG : // register write request
switch ( tu_le16toh ( xfer - > setup - > wValue ) ) {
case ( CH341_REG_DIVISOR < < 8 | CH341_REG_PRESCALER ) : // baudrate write
p_cdc - > line_coding . bit_rate = p_cdc - > ch34x . baud_rate ;
break ;
default :
TU_ASSERT ( false , ) ; // unexpected register write
break ;
}
2023-12-26 20:14:03 +01:00
break ;
2024-01-15 16:56:18 +07:00
default :
TU_ASSERT ( false , ) ; // unexpected request
2023-12-26 20:14:03 +01:00
break ;
}
xfer - > complete_cb = p_cdc - > user_control_cb ;
2024-01-15 16:56:18 +07:00
if ( xfer - > complete_cb ) xfer - > complete_cb ( xfer ) ;
2023-12-26 20:14:03 +01:00
}
}
2024-01-15 16:56:18 +07:00
static bool ch34x_set_baudrate ( cdch_interface_t * p_cdc , uint32_t baudrate , tuh_xfer_cb_t complete_cb , uintptr_t user_data ) /* do not confuse with ch341_set_baudrate */ {
2023-12-26 20:14:03 +01:00
TU_LOG_DRV ( " CDC CH34x Set BaudRate = %lu \r \n " , baudrate ) ;
uint32_t baud_le = tu_htole32 ( baudrate ) ;
p_cdc - > ch34x . baud_rate = baudrate ;
p_cdc - > user_control_cb = complete_cb ;
return ch341_set_baudrate ( p_cdc , baud_le , complete_cb ? ch34x_control_complete : NULL , user_data ) ;
}
2024-01-15 18:25:15 +07:00
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 ) {
2023-12-26 20:14:03 +01:00
TU_LOG_DRV ( " CDC CH34x Set Control Line State \r \n " ) ;
2024-01-15 18:25:15 +07:00
( void ) p_cdc ;
( void ) line_state ;
( void ) complete_cb ;
( void ) user_data ;
2023-12-26 20:14:03 +01:00
// todo later
return false ;
}
2024-01-15 16:56:18 +07:00
static void ch34x_process_config ( tuh_xfer_t * xfer ) {
2023-12-26 20:14:03 +01:00
uintptr_t const state = xfer - > user_data & 0xff ;
// CH34x needs a special handling of bInterfaceNumber, because wIndex is used for other purposes and not for bInterfaceNumber
uint8_t const itf_num = ( uint8_t ) ( ( xfer - > user_data & 0xff00 ) > > 8 ) ;
uint8_t const idx = tuh_cdc_itf_get_index ( xfer - > daddr , itf_num ) ;
cdch_interface_t * p_cdc = get_itf ( idx ) ;
uint8_t buffer [ CH34X_BUFFER_SIZE ] ;
cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM ;
TU_ASSERT ( p_cdc , ) ;
if ( state = = 0 ) {
// defaults
p_cdc - > ch34x . baud_rate = DEFAULT_BAUD_RATE ;
p_cdc - > ch34x . mcr = 0 ;
p_cdc - > ch34x . msr = 0 ;
p_cdc - > ch34x . quirks = 0 ;
p_cdc - > ch34x . version = 0 ;
/*
* Some CH340 devices appear unable to change the initial LCR
* settings , so set a sane 8 N1 default .
*/
p_cdc - > ch34x . lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8 ;
}
2024-01-15 16:56:18 +07:00
2023-12-26 20:14:03 +01:00
// This process flow has been taken over from Linux driver /drivers/usb/serial/ch341.c
2024-01-15 16:56:18 +07:00
switch ( state ) {
case CONFIG_CH34X_STEP1 : // request version read
TU_ASSERT ( ch341_control_in ( p_cdc , /* request */ CH341_REQ_READ_VERSION , /* value */ 0 , /* index */ 0 ,
buffer , CH34X_BUFFER_SIZE , ch34x_process_config , CONFIG_CH34X_STEP2 ) , ) ;
2023-12-26 20:14:03 +01:00
break ;
2024-01-15 16:56:18 +07:00
case CONFIG_CH34X_STEP2 : // handle version read data, request to init CH34x
p_cdc - > ch34x . version = xfer - > buffer [ 0 ] ;
TU_LOG_DRV ( " Chip version=%02x \r \n " , p_cdc - > ch34x . version ) ;
TU_ASSERT ( ch341_control_out ( p_cdc , /* request */ CH341_REQ_SERIAL_INIT , /* value */ 0 , /* index */ 0 ,
ch34x_process_config , CONFIG_CH34X_STEP3 ) , ) ;
2023-12-26 20:14:03 +01:00
break ;
2024-01-15 16:56:18 +07:00
case CONFIG_CH34X_STEP3 : // set baudrate with default values (see above)
TU_ASSERT ( ch341_set_baudrate ( p_cdc , p_cdc - > ch34x . baud_rate , ch34x_process_config ,
CONFIG_CH34X_STEP4 ) > 0 , ) ;
2023-12-26 20:14:03 +01:00
break ;
2024-01-15 16:56:18 +07:00
case CONFIG_CH34X_STEP4 : // set line controls with default values (see above)
TU_ASSERT ( ch341_set_lcr ( p_cdc , p_cdc - > ch34x . lcr , ch34x_process_config , CONFIG_CH34X_STEP5 ) > 0 , ) ;
2023-12-26 20:14:03 +01:00
break ;
2024-01-15 16:56:18 +07:00
case CONFIG_CH34X_STEP5 : // set handshake RTS/DTR
TU_ASSERT ( ch341_set_handshake ( p_cdc , p_cdc - > ch34x . mcr , ch34x_process_config , CONFIG_CH34X_STEP6 ) > 0 , ) ;
2023-12-26 20:14:03 +01:00
break ;
2024-01-15 16:56:18 +07:00
case CONFIG_CH34X_STEP6 : // detect quirks step 1
TU_ASSERT ( ch341_detect_quirks ( xfer , p_cdc , /* step */ 1 , buffer , CH34X_BUFFER_SIZE ,
ch34x_process_config , CONFIG_CH34X_STEP7 ) > 0 , ) ;
break ;
case CONFIG_CH34X_STEP7 : // detect quirks step 2 and set baudrate with configured values
TU_ASSERT ( ch341_detect_quirks ( xfer , p_cdc , /* step */ 2 , NULL , 0 , NULL , 0 ) > 0 , ) ;
# ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM
TU_ASSERT ( ch34x_set_baudrate ( p_cdc , line_coding . bit_rate , ch34x_process_config , CONFIG_CH34X_STEP8 ) , ) ;
2023-12-26 20:14:03 +01:00
# else
2024-01-15 16:56:18 +07:00
TU_ATTR_FALLTHROUGH ;
2023-12-26 20:14:03 +01:00
# endif
2024-01-15 16:56:18 +07:00
break ;
case CONFIG_CH34X_STEP8 : // set data/stop bit quantities, parity
# ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM
p_cdc - > ch34x . lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX ;
switch ( line_coding . data_bits ) {
case 5 :
p_cdc - > ch34x . lcr | = CH341_LCR_CS5 ;
break ;
case 6 :
p_cdc - > ch34x . lcr | = CH341_LCR_CS6 ;
break ;
case 7 :
p_cdc - > ch34x . lcr | = CH341_LCR_CS7 ;
break ;
case 8 :
p_cdc - > ch34x . lcr | = CH341_LCR_CS8 ;
break ;
default :
TU_ASSERT ( false , ) ; // not supported data_bits
p_cdc - > ch34x . lcr | = CH341_LCR_CS8 ;
break ;
}
if ( line_coding . parity ! = CDC_LINE_CODING_PARITY_NONE ) {
p_cdc - > ch34x . lcr | = CH341_LCR_ENABLE_PAR ;
if ( line_coding . parity = = CDC_LINE_CODING_PARITY_EVEN | |
line_coding . parity = = CDC_LINE_CODING_PARITY_SPACE ) {
p_cdc - > ch34x . lcr | = CH341_LCR_PAR_EVEN ;
}
if ( line_coding . parity = = CDC_LINE_CODING_PARITY_MARK | |
line_coding . parity = = CDC_LINE_CODING_PARITY_SPACE ) {
p_cdc - > ch34x . lcr | = CH341_LCR_MARK_SPACE ;
}
}
TU_ASSERT ( line_coding . stop_bits = = CDC_LINE_CODING_STOP_BITS_1 | | line_coding . stop_bits = =
CDC_LINE_CODING_STOP_BITS_2 , ) ; // not supported 1.5 stop bits
if ( line_coding . stop_bits = = CDC_LINE_CODING_STOP_BITS_2 ) {
p_cdc - > ch34x . lcr | = CH341_LCR_STOP_BITS_2 ;
}
TU_ASSERT ( ch341_set_lcr ( p_cdc , p_cdc - > ch34x . lcr , ch34x_process_config , CONFIG_CH34X_COMPLETE ) > 0 , ) ;
# else
TU_ATTR_FALLTHROUGH ;
# endif
break ;
2023-12-26 20:14:03 +01:00
case CONFIG_CH34X_COMPLETE :
2024-01-15 16:56:18 +07:00
set_config_complete ( p_cdc , idx , itf_num ) ;
2023-12-26 20:14:03 +01:00
break ;
2024-01-15 16:56:18 +07:00
2023-12-26 20:14:03 +01:00
default :
2024-01-15 16:56:18 +07:00
TU_ASSERT ( false , ) ;
2023-12-26 20:14:03 +01:00
break ;
}
}
# endif // CFG_TUH_CDC_CH34X
2024-01-15 16:56:18 +07:00
# endif