From 4e6408ea4940085f66c676772d77e3951e82ea11 Mon Sep 17 00:00:00 2001 From: IngHK Date: Tue, 16 Jan 2024 08:07:22 +0100 Subject: [PATCH] CDCh host: further work on CH340/CH341 support --- examples/host/cdc_msc_hid/src/tusb_config.h | 3 +- .../cdc_msc_hid_freertos/src/tusb_config.h | 3 +- src/class/cdc/cdc_debug.h | 65 ++ src/class/cdc/cdc_device.c | 21 +- src/class/cdc/cdc_host.c | 570 ++++++++---------- src/class/cdc/cdc_host.h | 4 +- src/class/cdc/serial/ch34x.h | 103 +--- 7 files changed, 370 insertions(+), 399 deletions(-) create mode 100644 src/class/cdc/cdc_debug.h diff --git a/examples/host/cdc_msc_hid/src/tusb_config.h b/examples/host/cdc_msc_hid/src/tusb_config.h index 76d59c316..ac14ef97e 100644 --- a/examples/host/cdc_msc_hid/src/tusb_config.h +++ b/examples/host/cdc_msc_hid/src/tusb_config.h @@ -120,8 +120,7 @@ //------------- CDC -------------// // Set Line Control state on enumeration/mounted: -// DTR ( bit 0), RTS (bit 1) -#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM ( CDC_CONTROL_LINE_STATE_RTS | CDC_CONTROL_LINE_STATE_DTR ) // Set Line Coding on enumeration/mounted, value for cdc_line_coding_t // bit rate = 115200, 1 stop bit, no parity, 8 bit data width diff --git a/examples/host/cdc_msc_hid_freertos/src/tusb_config.h b/examples/host/cdc_msc_hid_freertos/src/tusb_config.h index bb7c3388d..1b68dd2d8 100644 --- a/examples/host/cdc_msc_hid_freertos/src/tusb_config.h +++ b/examples/host/cdc_msc_hid_freertos/src/tusb_config.h @@ -125,8 +125,7 @@ //------------- CDC -------------// // Set Line Control state on enumeration/mounted: -// DTR ( bit 0), RTS (bit 1) -#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM ( CDC_CONTROL_LINE_STATE_RTS | CDC_CONTROL_LINE_STATE_DTR ) // Set Line Coding on enumeration/mounted, value for cdc_line_coding_t // bit rate = 115200, 1 stop bit, no parity, 8 bit data width diff --git a/src/class/cdc/cdc_debug.h b/src/class/cdc/cdc_debug.h new file mode 100644 index 000000000..879e49a50 --- /dev/null +++ b/src/class/cdc/cdc_debug.h @@ -0,0 +1,65 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (tinyusb.org) + * Copyright (c) 2023 IngHK 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_CDC_DEBUG_H_ +#define _TUSB_CDC_DEBUG_H_ + +#include "cdc.h" + +#ifdef __cplusplus + extern "C" { +#endif + +// logging of line coding +#define TU_LOG_LINE_CODING(PRE_TEXT,LINE_CODING) \ + TU_LOG_DRV(PRE_TEXT "Line Coding %lu Bd, %u%c%s\r\n", \ + LINE_CODING.bit_rate, \ + LINE_CODING.data_bits, \ + LINE_CODING.parity == CDC_LINE_CODING_PARITY_NONE ? 'N' : \ + LINE_CODING.parity == CDC_LINE_CODING_PARITY_ODD ? 'O' : \ + LINE_CODING.parity == CDC_LINE_CODING_PARITY_EVEN ? 'E' : \ + LINE_CODING.parity == CDC_LINE_CODING_PARITY_MARK ? 'M' : \ + LINE_CODING.parity == CDC_LINE_CODING_PARITY_SPACE ? 'S' : '?', \ + LINE_CODING.stop_bits == CDC_LINE_CODING_STOP_BITS_1 ? "1" : \ + LINE_CODING.stop_bits == CDC_LINE_CODING_STOP_BITS_1_5 ? "1.5" : \ + LINE_CODING.stop_bits == CDC_LINE_CODING_STOP_BITS_2 ? "2" : "?") + +// logging of baudrate +#define TU_LOG_BAUDRATE(PRE_TEXT,BAUDRATE) \ + TU_LOG_DRV(PRE_TEXT "Baudrate %lu Bd\r\n", BAUDRATE) + + // logging of control line state +#define TU_LOG_CONTROL_LINE_STATE(PRE_TEXT,LINE_STATE) \ + TU_LOG_DRV(PRE_TEXT "Control Line State RTS%c DTR%c\r\n", \ + LINE_STATE & CDC_CONTROL_LINE_STATE_RTS ? '+' : '-', \ + LINE_STATE & CDC_CONTROL_LINE_STATE_DTR ? '+' : '-' ) + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CDC_DEBUG_H_ */ diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c index c26264e60..b763419e7 100644 --- a/src/class/cdc/cdc_device.c +++ b/src/class/cdc/cdc_device.c @@ -32,6 +32,9 @@ #include "device/usbd_pvt.h" #include "cdc_device.h" +#if 0 // TODO activate and test +#include "cdc_debug.h" +#endif // Level where CFG_TUSB_DEBUG must be at least for this driver is logged #ifndef CFG_TUD_CDC_LOG_LEVEL @@ -360,7 +363,11 @@ bool cdcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t case CDC_REQUEST_SET_LINE_CODING: if (stage == CONTROL_STAGE_SETUP) { - TU_LOG_DRV(" Set Line Coding\r\n"); + #if 0 // TODO activate, test and remove else + TU_LOG_LINE_CODING(" Set ", p_cdc->line_coding); + #else + TU_LOG_DRV(" Set Line Coding\r\n"); + #endif tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); } else if ( stage == CONTROL_STAGE_ACK) @@ -372,7 +379,11 @@ bool cdcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t case CDC_REQUEST_GET_LINE_CODING: if (stage == CONTROL_STAGE_SETUP) { - TU_LOG_DRV(" Get Line Coding\r\n"); + #if 0 // TODO activate, test and remove else + TU_LOG_LINE_CODING(" Get ", p_cdc->line_coding); + #else + TU_LOG_DRV(" Get Line Coding\r\n"); + #endif tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); } break; @@ -397,7 +408,11 @@ bool cdcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t // Disable fifo overwriting if DTR bit is set tu_fifo_set_overwritable(&p_cdc->tx_ff, !dtr); - TU_LOG_DRV(" Set Control Line State: DTR = %d, RTS = %d\r\n", dtr, rts); + #if 0 // TODO activate, test and remove else + TU_LOG_CONTROL_LINE_STATE(" Set ", p_cdc->line_state); + #else + TU_LOG_DRV(" Set Control Line State: DTR = %d, RTS = %d\r\n", dtr, rts); + #endif // Invoke callback if ( tud_cdc_line_state_cb ) tud_cdc_line_state_cb(itf, dtr, rts); diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index f6a6773e0..d46bfecb4 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -2,7 +2,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) - * Copyright (c) 2023 Heiko Kuester (CH34x support) + * Copyright (c) 2023 IngHK 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 @@ -33,6 +33,7 @@ #include "host/usbh_pvt.h" #include "cdc_host.h" +#include "cdc_debug.h" // Level where CFG_TUSB_DEBUG must be at least for this driver is logged #ifndef CFG_TUH_CDC_LOG_LEVEL @@ -55,7 +56,7 @@ typedef struct { cdc_acm_capability_t acm_capability; uint8_t ep_notif; - uint8_t line_state; // DTR (bit0), RTS (bit1) + uint8_t line_state; // RTS, DTR (refer enums CDC_CONTROL_LINE_STATE_RTS, CDC_CONTROL_LINE_STATE_DTR) TU_ATTR_ALIGNED(4) cdc_line_coding_t line_coding; // Baudrate, stop bits, parity, data width tuh_xfer_cb_t user_control_cb; @@ -70,16 +71,9 @@ typedef struct { uint8_t rx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE]; CFG_TUH_MEM_ALIGN uint8_t rx_ep_buf[CFG_TUH_CDC_TX_EPSIZE]; } stream; - #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 +#if CFG_TUH_CDC_FTDI || CFG_TUH_CDC_CH34X + uint32_t baudrate_requested; +#endif } cdch_interface_t; CFG_TUH_MEM_SECTION @@ -106,12 +100,10 @@ enum { FTDI_PID_COUNT = sizeof(ftdi_pids) / sizeof(ftdi_pids[0]) }; -// 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_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_sio_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); #endif @@ -128,6 +120,7 @@ enum { 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_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, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); #endif @@ -144,6 +137,7 @@ enum { 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_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, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data ); #endif @@ -169,35 +163,40 @@ typedef struct { 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_line_coding)(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, 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[] = { { .process_set_config = acm_process_config, .set_control_line_state = acm_set_control_line_state, - .set_baudrate = acm_set_baudrate + .set_baudrate = acm_set_baudrate, + .set_line_coding = acm_set_line_coding }, #if CFG_TUH_CDC_FTDI { .process_set_config = ftdi_process_config, .set_control_line_state = ftdi_sio_set_modem_ctrl, - .set_baudrate = ftdi_sio_set_baudrate + .set_baudrate = ftdi_sio_set_baudrate, + .set_line_coding = ftdi_set_line_coding }, #endif #if CFG_TUH_CDC_CP210X { .process_set_config = cp210x_process_config, .set_control_line_state = cp210x_set_modem_ctrl, - .set_baudrate = cp210x_set_baudrate + .set_baudrate = cp210x_set_baudrate, + .set_line_coding = cp210x_set_line_coding }, #endif #if CFG_TUH_CDC_CH34X { .process_set_config = ch34x_process_config, .set_control_line_state = ch34x_set_modem_ctrl, - .set_baudrate = ch34x_set_baudrate + .set_baudrate = ch34x_set_baudrate, + .set_line_coding = ch34x_set_line_coding }, -#endif + #endif }; enum { @@ -304,6 +303,7 @@ bool tuh_cdc_get_dtr(uint8_t idx) { cdch_interface_t* p_cdc = get_itf(idx); TU_VERIFY(p_cdc); + TU_LOG_CONTROL_LINE_STATE("CDCh Local ", p_cdc->line_state); return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_DTR) ? true : false; } @@ -312,6 +312,7 @@ bool tuh_cdc_get_rts(uint8_t idx) { cdch_interface_t* p_cdc = get_itf(idx); TU_VERIFY(p_cdc); + TU_LOG_CONTROL_LINE_STATE("CDCh Local ", p_cdc->line_state); return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_RTS) ? true : false; } @@ -322,6 +323,7 @@ bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding) TU_VERIFY(p_cdc); *line_coding = p_cdc->line_coding; + TU_LOG_LINE_CODING("CDCh Get ", p_cdc->line_coding); return true; } @@ -440,7 +442,7 @@ static void cdch_internal_control_complete(tuh_xfer_t* xfer) case FTDI_SIO_SET_BAUD_RATE: // convert from divisor to baudrate is not supported - p_cdc->line_coding.bit_rate = _ftdi_requested_baud; + p_cdc->line_coding.bit_rate = p_cdc->baudrate_requested; break; default: break; @@ -465,12 +467,6 @@ static void cdch_internal_control_complete(tuh_xfer_t* xfer) break; #endif - #if CFG_TUH_CDC_CH34X - case SERIAL_DRIVER_CH34X: - TU_ASSERT(false, ); // see special ch34x_control_complete function - break; - #endif - default: break; } } @@ -487,7 +483,7 @@ bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_c cdch_serial_driver_t const* driver = &serial_drivers[p_cdc->serial_drid]; if ( complete_cb ) { - return driver->set_control_line_state(p_cdc, line_state, complete_cb, user_data); + TU_VERIFY(driver->set_control_line_state(p_cdc, line_state, complete_cb, user_data)); }else { // blocking xfer_result_t result = XFER_RESULT_INVALID; @@ -501,8 +497,10 @@ bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_c TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); p_cdc->line_state = (uint8_t) line_state; - return true; } + TU_LOG_CONTROL_LINE_STATE("CDCh Set ", p_cdc->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) { @@ -511,7 +509,7 @@ bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete 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); + TU_VERIFY(driver->set_baudrate(p_cdc, baudrate, complete_cb, user_data)); }else { // blocking xfer_result_t result = XFER_RESULT_INVALID; @@ -525,23 +523,24 @@ bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); p_cdc->line_coding.bit_rate = baudrate; - return true; } + TU_LOG_BAUDRATE("CDCh Set ", p_cdc->line_coding.bit_rate); + + 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); - // only ACM support this set line coding request - TU_VERIFY(p_cdc && p_cdc->serial_drid == SERIAL_DRIVER_ACM); - TU_VERIFY(p_cdc->acm_capability.support_line_request); + 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 acm_set_line_coding(p_cdc, line_coding, complete_cb, user_data); - }else { + TU_VERIFY(driver->set_line_coding(p_cdc, line_coding, complete_cb, user_data)); + } else { // blocking xfer_result_t result = XFER_RESULT_INVALID; - bool ret = acm_set_line_coding(p_cdc, line_coding, complete_cb, (uintptr_t) &result); + bool ret = driver->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 @@ -551,8 +550,10 @@ bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const* line_coding, TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); p_cdc->line_coding = *line_coding; - return true; } + TU_LOG_LINE_CODING("CDCh Set ", p_cdc->line_coding); + + return true; } //--------------------------------------------------------------------+ @@ -684,13 +685,8 @@ bool cdch_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *itf_d { return acm_open(daddr, itf_desc, max_len); } -<<<<<<< HEAD #if CFG_TUH_CDC_FTDI || CFG_TUH_CDC_CP210X || CFG_TUH_CDC_CH34X - else if ( 0xff == itf_desc->bInterfaceClass ) -======= - #if CFG_TUH_CDC_FTDI || CFG_TUH_CDC_CP210X else if ( TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass ) ->>>>>>> remotes/hathach/master { uint16_t vid, pid; TU_VERIFY(tuh_vid_pid_get(daddr, &vid, &pid)); @@ -816,6 +812,8 @@ static bool acm_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint1 TU_ASSERT(open_ep_stream_pair(p_cdc, (tusb_desc_endpoint_t const *) p_desc)); } + TU_LOG_DRV("[%u] CDCh ACM opened\r\n", daddr); + return true; } @@ -852,6 +850,12 @@ static void acm_process_config(tuh_xfer_t* xfer) TU_ATTR_FALLTHROUGH; case CONFIG_ACM_COMPLETE: + #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM + TU_LOG_LINE_CODING("CDCh ACM Init ", p_cdc->line_coding); + #endif + #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM + TU_LOG_CONTROL_LINE_STATE("CDCh ACM Init", p_cdc->line_state); + #endif // itf_num+1 to account for data interface as well set_config_complete(p_cdc, idx, itf_num+1); break; @@ -893,6 +897,7 @@ static bool acm_set_control_line_state(cdch_interface_t* p_cdc, uint16_t line_st 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"); + TU_VERIFY(p_cdc->acm_capability.support_line_request); tusb_control_request_t const request = { .bmRequestType_bit = { @@ -994,6 +999,12 @@ static bool ftdi_sio_reset(cdch_interface_t* p_cdc, tuh_xfer_cb_t complete_cb, u return ftdi_sio_set_request(p_cdc, FTDI_SIO_RESET, FTDI_SIO_RESET_SIO, 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) +{ + TU_ASSERT(false, false); // TODO + 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"); @@ -1035,7 +1046,7 @@ static bool ftdi_sio_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tu TU_LOG_DRV("CDC FTDI Set BaudRate = %lu, divisor = 0x%04x\r\n", baudrate, divisor); p_cdc->user_control_cb = complete_cb; - _ftdi_requested_baud = baudrate; + p_cdc->baudrate_requested = baudrate; TU_ASSERT(ftdi_sio_set_request(p_cdc, FTDI_SIO_SET_BAUD_RATE, divisor, complete_cb ? cdch_internal_control_complete : NULL, user_data)); @@ -1088,6 +1099,14 @@ static void ftdi_process_config(tuh_xfer_t* xfer) { case CONFIG_FTDI_COMPLETE: set_config_complete(p_cdc, idx, itf_num); + #if 0 // TODO set data format + #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM + TU_LOG_LINE_CODING("CDCh FTDI Init ", p_cdc->line_coding); + #endif + #endif + #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM + TU_LOG_CONTROL_LINE_STATE("CDCh FTDI Init ", p_cdc->line_state); + #endif break; default: @@ -1166,6 +1185,12 @@ static bool cp210x_ifc_enable(cdch_interface_t* p_cdc, uint16_t enabled, tuh_xfe 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) +{ + TU_ASSERT(false, false); // TODO + return false; +} + 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 = %lu\r\n", baudrate); uint32_t baud_le = tu_htole32(baudrate); @@ -1224,6 +1249,12 @@ static void cp210x_process_config(tuh_xfer_t* xfer) { case CONFIG_CP210X_COMPLETE: set_config_complete(p_cdc, idx, itf_num); + #if defined(CFG_TUH_CDC_LINE_CODING_ON_ENUM) && 0 // skip for now + TU_LOG_LINE_CODING("CDCh CP210x Init ", p_cdc->line_coding); + #endif + #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM + TU_LOG_CONTROL_LINE_STATE("CDCh CP210x Init ", p_cdc->line_state); + #endif break; default: break; @@ -1233,20 +1264,19 @@ static void cp210x_process_config(tuh_xfer_t* xfer) { #endif //--------------------------------------------------------------------+ -// CH34x +// CH34x (CH340 & CH341) //--------------------------------------------------------------------+ #if CFG_TUH_CDC_CH34X +#define CH34X_LOGS false + 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_READ_VERSION = 0, + CONFIG_CH34X_SERIAL_INIT, + CONFIG_CH34X_SPECIAL_REG_WRITE, + CONFIG_CH34X_FLOW_CONTROL, + CONFIG_CH34X_MODEM_CONTROL, CONFIG_CH34X_COMPLETE }; @@ -1259,17 +1289,22 @@ static bool ch34x_open ( uint8_t daddr, tusb_desc_interface_t const *itf_desc, u 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; // 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 ); + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep), false); + + TU_LOG_DRV("[%u] CDCh CH34x opened\r\n", daddr); + + return true; } -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 = { @@ -1297,212 +1332,66 @@ static bool ch34x_set_request ( cdch_interface_t* p_cdc, uint8_t direction, uint .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 - .user_data = (uintptr_t)( ( p_cdc->bInterfaceNumber & 0xff ) << 8 ) | ( user_data & 0xff ) + .user_data = user_data }; return tuh_control_xfer ( &xfer ); - return false; } -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 ) +static 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, /* buffer */ NULL, /* length */ 0, complete_cb, user_data ); } -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 ) +static 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 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 ) +static bool ch34x_write_reg ( cdch_interface_t* p_cdc, uint16_t reg, uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data ) { - return ch341_control_out ( p_cdc, CH341_REQ_WRITE_REG, /* value */ reg, /* index */ value, complete_cb, user_data ); + return ch34x_control_out ( p_cdc, CH34X_REQ_WRITE_REG, /* value */ reg, /* index */ value, complete_cb, user_data ); } -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 ) +//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, /* index */ 0, buffer, buffersize, complete_cb, user_data ); +//} + +uint8_t ch34x_xfer_get_itf_num ( tuh_xfer_t* xfer ) +// CH34x needs a special handling to get bInterfaceNumber, because wIndex is used for other purposes and not for bInterfaceNumber +// CH340 and CH341 derivates have always only one interface, so it's OK to check only daddr { - 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 -static int32_t ch341_get_divisor ( cdch_interface_t* p_cdc, uint32_t speed ) -{ - 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. - */ - inline uint32_t max ( uint32_t val, uint32_t maxval ) { return val > maxval ? val : maxval; } - inline uint32_t min ( uint32_t val, uint32_t minval ) { return val < minval ? val : minval; } - inline uint32_t clamp_val ( uint32_t val, uint32_t minval, uint32_t maxval ) { return min ( max ( val, minval ), maxval ); } - 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--) { - if (speed > ch341_min_rates[ps]) - break; - } - - if (ps < 0) - return -EINVAL; - - /* Determine corresponding divisor, rounding down. */ - clk_div = CH341_CLK_DIV(ps, fact); - div = CH341_CLKRATE / (clk_div * speed); - - /* Some devices require a lower base clock if ps < 3. */ - if (ps < 3 && (p_cdc->ch34x.quirks & CH341_QUIRK_LIMITED_PRESCALER)) - force_fact0 = true; - - /* Halve base clock (fact = 0) if required. */ - if (div < 9 || div > 255 || force_fact0) { - div /= 2; - clk_div *= 2; - fact = 0; - } - - if (div < 2) - return -EINVAL; - - /* - * 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 >= - 16 * speed - 16 * CH341_CLKRATE / (clk_div * (div + 1))) - div++; - - /* - * 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; - } - - return (0x100 - div) << 8 | fact << 2 | ps; -} - -// 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 -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 ) -{ - int val; - - if (!baud_rate) - return -EINVAL; - - val = ch341_get_divisor(p_cdc, baud_rate); - if (val < 0) - return -EINVAL; - - /* - * 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. - */ - if ( p_cdc->ch34x.version > 0x27 ) - val |= BIT(7); - - 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 -static int32_t ch341_set_lcr ( cdch_interface_t* p_cdc, uint8_t lcr, tuh_xfer_cb_t complete_cb, uintptr_t user_data ) -{ - /* - * 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. - */ - if ( p_cdc->ch34x.version < 0x30 ) - return 0; - - 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 -static int32_t ch341_set_handshake ( cdch_interface_t* p_cdc, uint8_t control, tuh_xfer_cb_t complete_cb, uintptr_t user_data ) -{ - 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 -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 ) -{ - /* - * 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. - */ - switch ( step ) + for ( uint8_t i=0; ich34x.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; + const cdch_interface_t* p_cdc = &cdch_data[i]; + if ( p_cdc->daddr == xfer->daddr ) return p_cdc->bInterfaceNumber; } + + return INTERFACE_INVALID_NUMBER; } // internal control complete to update state such as line state, encoding -// CH34x needs a special interface recovery due to abnormal wIndex usage -static void ch34x_control_complete(tuh_xfer_t* xfer) +static void ch34x_control_complete ( tuh_xfer_t* xfer ) { - uint8_t const itf_num = (uint8_t)( ( xfer->user_data & 0xff00 ) >> 8 ); + uint8_t const itf_num = ch34x_xfer_get_itf_num ( xfer ); uint8_t const idx = tuh_cdc_itf_get_index ( xfer->daddr, itf_num ); cdch_interface_t *p_cdc = get_itf ( idx ); + uint16_t value = tu_le16toh ( xfer->setup->wValue ); TU_ASSERT ( p_cdc, ); TU_ASSERT ( p_cdc->serial_drid == SERIAL_DRIVER_CH34X, ); // ch34x_control_complete is only used for CH34x - if (xfer->result == XFER_RESULT_SUCCESS) { - switch (xfer->setup->bRequest) { - 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; + if ( xfer->result == XFER_RESULT_SUCCESS ) { + switch ( xfer->setup->bRequest ) { + case CH34X_REQ_WRITE_REG: { // register write request + switch ( value ) { + case ( 0x1312 ): { // baudrate write + p_cdc->line_coding.bit_rate = p_cdc->baudrate_requested; + #if CH34X_LOGS + TU_LOG_BAUDRATE("CDCh CH34x Control Complete ", p_cdc->line_coding.bit_rate); + #endif break; } default: { @@ -1512,6 +1401,20 @@ static void ch34x_control_complete(tuh_xfer_t* xfer) } break; } + case CH34X_REQ_MODEM_CTRL: { // set modem controls RTS/DTR request + if ( ~value & CH34X_BIT_RTS ) + p_cdc->line_state |= CDC_CONTROL_LINE_STATE_RTS; + else + p_cdc->line_state &= ~CDC_CONTROL_LINE_STATE_RTS; + if ( ~value & CH34X_BIT_DTR ) + p_cdc->line_state |= CDC_CONTROL_LINE_STATE_DTR; + else + p_cdc->line_state &= ~CDC_CONTROL_LINE_STATE_DTR; + #if CH34X_LOGS + TU_LOG_CONTROL_LINE_STATE("CDCh CH34x Control Complete ", p_cdc->line_state); + #endif + break; + } default: { TU_ASSERT(false, ); // unexpected request break; @@ -1523,118 +1426,161 @@ static void ch34x_control_complete(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 ) // do not confuse with ch341_set_baudrate +static bool ch34x_get_factor_divisor ( uint32_t baval, uint8_t *factor, uint8_t *divisor ) +{ // calc baudrate factor and divisor + uint8_t a; + uint8_t b; + uint32_t c; + + switch (baval) { + case 921600: + a = 0xf3; + b = 7; + break; + case 307200: + a = 0xd9; + b = 7; + break; + default: + if (baval > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (baval > 750000 / 255) { + b = 2; + c = 750000; + } else if (baval > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + a = (unsigned char)(c / baval); + if (a == 0 || a == 0xFF) + return false; + if ((c / a - baval) > (baval - c / (a + 1))) + a++; + a = 256 - a; + break; + } + *factor = a; + *divisor = b; + + return true; +} + +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) { - TU_LOG_DRV("CDC CH34x Set BaudRate = %lu\r\n", baudrate); - uint32_t baud_le = tu_htole32(baudrate); - p_cdc->ch34x.baud_rate = baudrate; + TU_ASSERT(false, false); // TODO + return false; +} + +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->baudrate_requested = baudrate; p_cdc->user_control_cb = complete_cb; - return ch341_set_baudrate ( p_cdc, baud_le, complete_cb ? ch34x_control_complete : NULL, user_data ); + uint8_t factor, divisor; + TU_ASSERT ( ch34x_get_factor_divisor ( baudrate, &factor, &divisor ), false ); + TU_ASSERT ( ch34x_write_reg ( p_cdc, /* reg */ 0x1312, /* value */ (uint16_t)factor << 8 | 0x80 | divisor, + complete_cb ? ch34x_control_complete : NULL, user_data ), false ); + + return true; } 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 ) { - TU_LOG_DRV("CDC CH34x Set Control Line State\r\n"); - // todo later - return false; + p_cdc->user_control_cb = complete_cb; + uint16_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; + TU_ASSERT ( ch34x_control_out ( p_cdc, /* request */ CH34X_REQ_MODEM_CTRL, /* value */ (uint8_t)~control, + /* index */ 0, complete_cb ? ch34x_control_complete : NULL, user_data ), false ); + return true; } static void ch34x_process_config ( tuh_xfer_t* xfer ) { - 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; + uint8_t const itf_num = ch34x_xfer_get_itf_num ( xfer ); + uintptr_t const state = xfer->user_data; + uint8_t const idx = tuh_cdc_itf_get_index ( xfer->daddr, itf_num ); + cdch_interface_t *p_cdc = get_itf ( idx ); + cdc_line_coding_t const line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; + uint8_t buffer[2]; 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 8N1 default. - */ - p_cdc->ch34x.lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8; - } - // This process flow has been taken over from Linux driver /drivers/usb/serial/ch341.c 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 ), ); + case CONFIG_CH34X_READ_VERSION: // request version read + #if CH34X_LOGS + TU_LOG_DRV ( "[%u] CDCh CH34x Process Config started\r\n", p_cdc->daddr ); + #endif + TU_ASSERT ( ch34x_control_in ( p_cdc, /* request */ CH34X_REQ_READ_VERSION, /* value */ 0, + /* index */ 0, buffer, 2, ch34x_process_config, CONFIG_CH34X_SERIAL_INIT ), ); break; - 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 ), ); - break; - 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, ); - break; - 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, ); - break; - 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, ); - break; - 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 ), ); -#else - TU_ATTR_FALLTHROUGH; -#endif - 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; + case CONFIG_CH34X_SERIAL_INIT: { // handle version read data, request to init CH34x with line_coding and baudrate + uint8_t version = xfer->buffer[0]; + #if CH34X_LOGS + TU_LOG_DRV ( "[%u] CDCh CH34x Chip version=%02x\r\n", p_cdc->daddr, version ); + #endif + // only versions >= 0x30 are tested, below 0x30 seems having other programming, see WCH vendor, linux kernel and FreeBSD drivers + TU_ASSERT ( version >= 0x30, ); + uint8_t factor, divisor; + TU_ASSERT ( ch34x_get_factor_divisor ( line_coding.bit_rate, &factor, &divisor ), ); + uint8_t lcr = CH34X_LCR_ENABLE_RX | CH34X_LCR_ENABLE_TX; switch ( line_coding.data_bits ) { case 5: - p_cdc->ch34x.lcr |= CH341_LCR_CS5; + lcr |= CH34X_LCR_CS5; break; case 6: - p_cdc->ch34x.lcr |= CH341_LCR_CS6; + lcr |= CH34X_LCR_CS6; break; case 7: - p_cdc->ch34x.lcr |= CH341_LCR_CS7; + lcr |= CH34X_LCR_CS7; break; case 8: - p_cdc->ch34x.lcr |= CH341_LCR_CS8; + lcr |= CH34X_LCR_CS8; break; default: TU_ASSERT ( false, ); // not supported data_bits - p_cdc->ch34x.lcr |= CH341_LCR_CS8; + lcr |= CH34X_LCR_CS8; break; } if ( line_coding.parity != CDC_LINE_CODING_PARITY_NONE ) { - p_cdc->ch34x.lcr |= CH341_LCR_ENABLE_PAR; + lcr |= CH34X_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; + lcr |= CH34X_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; + lcr |= CH34X_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 + lcr |= CH34X_LCR_STOP_BITS_2; + TU_ASSERT ( ch34x_control_out ( p_cdc, /* request */ CH34X_REQ_SERIAL_INIT, /* value */ (uint16_t)lcr << 8 | 0x9c, + /* index */ (uint16_t)factor << 8 | 0x80 | divisor, ch34x_process_config, CONFIG_CH34X_SPECIAL_REG_WRITE ), ); + break; + } + case CONFIG_CH34X_SPECIAL_REG_WRITE: // do special reg write, purpose unknown, overtaken from WCH driver + TU_ASSERT ( ch34x_write_reg ( p_cdc, /* reg */ 0x0f2c, /* value */ 0x0007, ch34x_process_config, CONFIG_CH34X_FLOW_CONTROL ), ); + break; + case CONFIG_CH34X_FLOW_CONTROL: // no hardware flow control + TU_ASSERT ( ch34x_write_reg ( p_cdc, /* reg */ 0x2727, /* value */ 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 ), ); + break; + case CONFIG_CH34X_COMPLETE: + p_cdc->line_coding = line_coding; // CONFIG_CH34X_SERIAL_INIT not handled by ch34x_control_complete + #if CH34X_LOGS + TU_LOG_DRV("CDCh CH34x Process Config Complete\r\n"); + TU_LOG_LINE_CODING(" ", p_cdc->line_coding); + TU_LOG_CONTROL_LINE_STATE(" ", p_cdc->line_state); + #endif + set_config_complete ( p_cdc, idx, itf_num ); + break; + default: + TU_ASSERT ( false, ); break; - case CONFIG_CH34X_COMPLETE: - set_config_complete ( p_cdc, idx, itf_num ); - break; - default: - TU_ASSERT ( false, ); - break; } } diff --git a/src/class/cdc/cdc_host.h b/src/class/cdc/cdc_host.h index 9e5edd94e..8e3a2352a 100644 --- a/src/class/cdc/cdc_host.h +++ b/src/class/cdc/cdc_host.h @@ -37,7 +37,7 @@ // Class Driver Configuration //--------------------------------------------------------------------+ -// Set Line Control state on enumeration/mounted: DTR ( bit 0), RTS (bit 1) +// Set Line Control state on enumeration/mounted, refer enums CDC_CONTROL_LINE_STATE_RTS, CDC_CONTROL_LINE_STATE_DTR #ifndef CFG_TUH_CDC_LINE_CONTROL_ON_ENUM #define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0 #endif @@ -142,7 +142,7 @@ bool tuh_cdc_read_clear (uint8_t idx); // - The function will return true if transfer is successful, false otherwise. //--------------------------------------------------------------------+ -// Request to Set Control Line State: DTR (bit 0), RTS (bit 1) +// Request to Set Control Line State, refer enums CDC_CONTROL_LINE_STATE_RTS, CDC_CONTROL_LINE_STATE_DTR 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 baudrate diff --git a/src/class/cdc/serial/ch34x.h b/src/class/cdc/serial/ch34x.h index 94e18b252..262e012fd 100644 --- a/src/class/cdc/serial/ch34x.h +++ b/src/class/cdc/serial/ch34x.h @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 Heiko Kuester (tinyusb.org) + * Copyright (c) 2023 IngHK Heiko Kuester (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 @@ -27,87 +27,34 @@ #ifndef _CH34X_H_ #define _CH34X_H_ -#include - -#define BIT(nr) ( (uint32_t)1 << (nr) ) - -#define CH34X_BUFFER_SIZE 2 - -// The following defines have been taken over from Linux driver /drivers/usb/serial/ch341.c - -#define DEFAULT_BAUD_RATE 9600 - -/* flags for IO-Bits */ -#define CH341_BIT_RTS (1 << 6) -#define CH341_BIT_DTR (1 << 5) - -/******************************/ -/* interrupt pipe definitions */ -/******************************/ -/* always 4 interrupt bytes */ -/* first irq byte normally 0x08 */ -/* second irq byte base 0x7d + below */ -/* third irq byte base 0x94 + below */ -/* fourth irq byte normally 0xee */ - -/* second interrupt byte */ -#define CH341_MULT_STAT 0x04 /* multiple status since last interrupt event */ - -/* status returned in third interrupt answer byte, inverted in data - from irq */ -#define CH341_BIT_CTS 0x01 -#define CH341_BIT_DSR 0x02 -#define CH341_BIT_RI 0x04 -#define CH341_BIT_DCD 0x08 -#define CH341_BITS_MODEM_STAT 0x0f /* all bits */ - -/* Break support - the information used to implement this was gleaned from - * the Net/FreeBSD uchcom.c driver by Takanori Watanabe. Domo arigato. - */ +// 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 +#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 } +#endif // USB requests -#define CH341_REQ_READ_VERSION 0x5F // dec 95 -#define CH341_REQ_WRITE_REG 0x9A -#define CH341_REQ_READ_REG 0x95 -#define CH341_REQ_SERIAL_INIT 0xA1 -#define CH341_REQ_MODEM_CTRL 0xA4 +#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 -// CH34x registers -#define CH341_REG_BREAK 0x05 -#define CH341_REG_PRESCALER 0x12 -#define CH341_REG_DIVISOR 0x13 -#define CH341_REG_LCR 0x18 -#define CH341_REG_LCR2 0x25 - -#define CH341_NBREAK_BITS 0x01 +// modem control bits +#define CH34X_BIT_RTS ( 1 << 6 ) +#define CH34X_BIT_DTR ( 1 << 5 ) // line control bits -#define CH341_LCR_ENABLE_RX 0x80 -#define CH341_LCR_ENABLE_TX 0x40 -#define CH341_LCR_MARK_SPACE 0x20 -#define CH341_LCR_PAR_EVEN 0x10 -#define CH341_LCR_ENABLE_PAR 0x08 -#define CH341_LCR_STOP_BITS_2 0x04 -#define CH341_LCR_CS8 0x03 -#define CH341_LCR_CS7 0x02 -#define CH341_LCR_CS6 0x01 -#define CH341_LCR_CS5 0x00 - -#define CH341_QUIRK_LIMITED_PRESCALER BIT(0) -#define CH341_QUIRK_SIMULATE_BREAK BIT(1) - -#define CH341_CLKRATE 48000000 -#define CH341_CLK_DIV(ps, fact) (1 << (12 - 3 * (ps) - (fact))) -#define CH341_MIN_RATE(ps) (CH341_CLKRATE / (CH341_CLK_DIV((ps), 1) * 512)) - -/* Supported range is 46 to 3000000 bps. */ -#define CH341_MIN_BPS DIV_ROUND_UP(CH341_CLKRATE, CH341_CLK_DIV(0, 0) * 256) -#define CH341_MAX_BPS (CH341_CLKRATE / (CH341_CLK_DIV(3, 0) * 2)) - -#define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP -#define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) - -// error codes -#define EINVAL 22 /* Invalid argument */ +#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_STOP_BITS_2 0x04 +#define CH34X_LCR_CS8 0x03 +#define CH34X_LCR_CS7 0x02 +#define CH34X_LCR_CS6 0x01 +#define CH34X_LCR_CS5 0x00 #endif /* _CH34X_H_ */